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

schematic-joi-model

v1.2.2

Published

Runtime property validation and model composition utilizing Joi schemas

Downloads

148

Readme

schematic-joi-model

Overview

This extendable class does two specific things:

  1. makes it easy to create and manage objects that conform to a specific schema
    1. schema is used to validate arguments passed to the constructor
    2. schema validation errors are thrown after being casted to a helpful representation
    3. all properties of arguments that match keys of the model schema are assigned to the object during construction
  2. makes it easy to compose models
    1. by simply defining the dependencies of each key, the model automatically casts the properties into those models

Installation

npm install --save schematic-joi-model

Usage Examples

For more thorough documentation and examples, please see the src/*.test.ts files.

Basic Model

// model definition:
const imageSchema = Joi.object().keys({
  uuid: Joi.string().uuid().required(),
  path: Joi.string(),
});
interface ImageConstructorParams {
  uuid: string;
  path?: string;
}
class Image extends SchematicJoiModel<ImageConstructorParams> {
  public static schema = imageSchema;
}

// model usage:
new Image({ uuid: 'ff8b3da2-aba8-43f5-b1fc-5609d23c684f' }); // returns: Image { uuid: 'ff8b3da2-aba8-43f5-b1fc-5609d23c684f', path: undefined }
new Image({ uuid: 'not a uuid' }); // throws validation error

Composed Model

Building on the above example with the Image model:

const userSchema = Joi.object().keys({
  uuid: Joi.string().uuid().required(),
  name: Joi.string(),
  age: Joi.number().integer(),
  avatar: Image.schema,
});
interface UserConstructorParams {
  uuid: string;
  name: string;
  age: number;
  avatar: Image;
}
class User extends SchematicJoiModel<UserConstructorParams> {
  // properties
  public uuid!: string;
  public name!: string;
  public age!: number;
  public avatar!: Image;

  // metadata for validation and property assignment
  public static schema = userSchema;
  public static dependencies = {
    avatar: Image,
  };
}


new User({
  uuid: '4e4cb5f9-5949-4b47-af77-d1eec4ab8fb5',
  name: 'bessy',
  age: 21,
  avatar: {
    uuid: 'b4380823-917d-4e0c-bf9a-aa53fae6ff98',
  },
});
/*
returns:
  User {
    uuid: '4e4cb5f9-5949-4b47-af77-d1eec4ab8fb5',
    name: 'bessy',
    age: 21,
    avatar: Image {
     uuid: 'b4380823-917d-4e0c-bf9a-aa53fae6ff98',
     path: undefined
    }
  }
*/

Notice that the avatar property of the instance of the instantiated User model is an instance of the Image model

Highly Constrainted Model

Some models you may define with full data - and some may be valid to define with only a uuid and expect the full data to be derivable after the fact. In order to not allow partial information, a model can be created with the following syntax:

const fullDataSchema = Joi.object().keys({
  uuid: Joi.string().uuid().required(),
  make: Joi.string().valid(['ford', 'gmc', 'honda', 'toyota']).required(),
  model: Joi.string().required(),
});
const rootDataOnlySchema = Joi.object().keys({
  make: Joi.string().valid(['ford', 'gmc', 'honda', 'toyota']).required(),
  model: Joi.string().required(),
});
const uuidOnlySchema = Joi.object().keys({
  uuid: Joi.string().uuid().required(),
});
const fullSchema = Joi.alternatives().try([fullDataSchema, rootDataOnlySchema, uuidOnlySchema]);
interface FullDataCarConstructorParams {
  uuid: string;
  make: string;
  model: string;
}
interface RootDataCarConstructorParams {
  make: string;
  model: string;
}
interface UuidOnlyCarConstructorParams {
  uuid: string;
}
type CarConstructorParams = FullDataCarConstructorParams | RootDataCarConstructorParams | UuidOnlyCarConstructorParams;
class Car extends SchematicJoiModel<CarConstructorParams> {
  public uuid?: string;
  public make?: string;
  public model?: string;
  public static schema = fullSchema;
}

// we can now initalize the model with only the RootData
const car = new Car({ make: 'honda', model: 'odessy' });
expect(car).toMatchObject({ make: 'honda', model: 'odessy' }); // true

// or with only the uuid
const car = new Car({ uuid: '88d0a45a-e374-4e30-83de-6840c2ab8cc3' });
expect(car).toMatchObject({ uuid: '88d0a45a-e374-4e30-83de-6840c2ab8cc3' }); // true

// or with the full data
const car = new Car({ uuid: '88d0a45a-e374-4e30-83de-6840c2ab8cc3', make: 'honda', model: 'odessy' });
expect(car).toMatchObject({ uuid: '88d0a45a-e374-4e30-83de-6840c2ab8cc3', make: 'honda', model: 'odessy' }); // true

// but not the partial data
try {
  new Car({ uuid: '88d0a45a-e374-4e30-83de-6840c2ab8cc3', model: 'odessy' }); // tslint:disable-line no-unused-expression
  throw new Error('should not reach here');
} catch (error) {
  expect(error.constructor).toEqual(ValidationError); // true
}

To Do

deduplication of type definitions

It is clear to see that we are duplicating the logic defining the types of the properties of the model objects between the Joi Schemas and Typescript Typedefs. It would be ideal if we could assign the Typescript model properties' typedefs directly from the Joi schema.

automatic dependency definitions

It would be convinient to eliminate the 'dependencies' property alltogether by somehow manipulating the Model.schema property to pass meta-data that we could then retrieve to determine:

  1. that this property should be casted into a model instance
  2. which model instance to cast it into Perhaps Joi has a way to add metadata to schema objects that we could then retrieve from the Joi.describe() method?