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

@jayyuen1/js-interface

v0.0.2

Published

A tiny library that helps us achieve a certain level of type safety around interfaces (via duck typing). This library also helps us keep JavaScript objects synchronized with any interface definitions that they explicitly implement.

Downloads

7

Readme

js-interface

A tiny library that helps us

  • achieve a certain level of type safety around interfaces (via duck typing)
  • keeps JavaScript objects synchronized with any interface definitions that they explicitly implement

Guiding Principle

When we create an object/prototype with the intention of having it implement a certain interface, then we should fail-fast if the created object/prototype does not correctly implement that interface.

Definitions

At present, this library considers an "interface" to be a set of function signatures, where each signature consists of the function name and the number of parameters that the function accepts.

An object/prototype is considered to "implement" an interface if the object/prototype contains a matching function for each signature in the interface.

Properties are NOT currently checked (only functions are checked). In the future, the library may be extended to check for both functions AND properties.

Installation

npm install @jayyuen1/js-interface

Tests

npm test

Usage

Importing/Requiring the module

To load the library: var JSInterface = require('@jayyuen1/js-interface')

Or even better, use 'const' to make JSInterface an immutable variable (since you will probably never have a need to re-assign it): const JSInterface = require('@jayyuen1/js-interface')

Defining an interface

To define an interface:

    const MathInterface = {
      add: ["numOne", "numTwo"],
      subtract: ["numOne", "numTwo"]
    }

Presumably, you are writing an application in which multiple objects/classes/prototypes need to implement one or more interfaces. (If not, then your application is probably simple enough for you to NOT need to use this library!)

In this case, it might make more sense to put each interface definition into its own file, and then 'require' it wherever needed (assuming that you are building your application in nodejs or something similar). For example, I can create 'MathInterface.js' and populate it with this:

    module.exports = {
      add: ["numOne", "numTwo"],
      subtract: ["numOne", "numTwo"]
    }

Once this file has been created, you can get a reference to the interface definition using 'require':

    const MathInterface = require('MathInterface')

In all of the code examples that follow, we will assume that we have already obtained a reference to our interface and have assigned it to the MathInterface variable (using either of the two approaches shown above).

Checking for an interface

To confirm that an interface has been implemented by an object (whether directly or through prototype inheritance), use the 'isImplementedIn' function:


    // Create an object that implements the interface
    const mathImpl = {
      add: function(x, y) { return x + y },
      subtract: function(a, b) { return a - b }
    }

    // Check whether the object implements the interface (and of course it does!)
    console.log(JSInterface.isImplementedIn(mathImpl, MathInterface)) // true
    

Here is an example in which we implement the interface via prototype inheritance. Specifically,

  • We create a class that inherits from another class.
  • We implement part of the interface in the superclass, and the remaining part in the subclass
    const util = require('util') // NodeJS 'util' library, which we will use to
                                 // implement object inheritance
    
    // FIRST, we create our superclass.
    
    function MathSuperClass() {
    }

    MathSuperClass.prototype.add = function(x, y) { // half of our interface
      return x + y
    }

    // NEXT, we create our subclass.
    function MathSubClass() {
    }

    util.inherits(MathSubClass, MathSuperClass)

    MathSubClass.prototype.subtract = function(a, b) { // the other half of
                                                       // our interface
      return a - b
    }
    
    
    // FINALLY, we create an instance of our subclass, and check whether it
    // implements our interface definition (and of course it does!)
    const mathObject = new MathSubClass()
    console.log(JSInterface.isImplementedIn(mathObject, MathInterface)) // true

Implementing an interface

Of course, you can implement an interface using the methods shown in the earlier examples.

HOWEVER... as your program changes, interface definitions may change. When this happens, you will need to make corresponding changes to all of the objects that you wrote for purposes of implementing those interfaces. It can be difficult to remember to change all of them... AND, if you forget to change one, the resulting problems in your program might be hard to troubleshoot and resolve.

THEREFORE... you might find it easier to maintain your code (and prevent errors) by designing it to fail-fast at object/class/prototype creation time, whenever your interface implementation has become out-of-synch with the interface definition.

Use the 'implementIn' function to help you do this:


    const obj = {}

    // The following line returns false, because our object does not yet have
    // the functions needed to implement our interface
    console.log(JSInterface.isImplementedIn(obj, MathInterface)) 
    

    JSInterface.implementIn(obj, MathInterface, {
      add: function(x, y) { return x + y },
      subtract: function(a, b) { return a - b }
    })

    // The following line returns true, because our object now implements
    // the interface
    console.log(JSInterface.isImplementedIn(obj, MathInterface)) 

What happens when we attempt to implement an interface with implementation code that is out-of-synch with the interface definition? We fail fast:

    const anotherObj = {}

    // The following line throws an exception, because we forgot to supply an
    // implementation for the 'subtract' function.
    // The exception will contain the error message:
    //                           "Missing Function: subtract(numOne, numTwo)"
    JSInterface.implementIn(anotherObj, MathInterface, {
      add: function(x, y) { return x + y }
    })
    

The 'implementIn' function can also be used to implement an interface directly on a prototype:

    function MyClass() {
    }

    JSInterface.implementIn(MyClass.prototype, MathInterface, {
      add: function(x, y) { return x + y },
      subtract: function(a, b) { return a - b }
    })

    const myObj = new MyClass()
    
    console.log(JSInterface.isImplementedIn(myObj, MathInterface)) // true

Extra Stuff

  • The 'reasonsNotImplementedIn' function can be used instead of the 'isImplementedIn' function, to check whether an interface is correctly implemented by an object. It returns null if the interface IS correctly implemented... but if NOT correctly implemented, then we get back a message that tels us what's wrong or missing.
  • The 'throwErrIfNotImplementedIn' function performs the same check as 'isImplementedIn' and 'reasonsNotImplementedIn', but throws an exception if an interface is not fully implemented

    const someObj = {
        add: function(x, y, z) { return x + y + z }
    }
    
    var reasons = JSInterface.reasonsNotImplementedIn(someObj, MathInterface)
    
    console.log(reasons) // "Incorrect number of parameters (3) in
                         // implementation for function: add(numOne, numTwo) 
                         // | Missing Function: subtract(numOne, numTwo)"

    // The following line throws an exception that contains the message:
    //   "Incorrect number of parameters (3) in implementation for function:
    //    add(numOne, numTwo) | Missing Function: subtract(numOne, numTwo)"
    JSInterface.throwErrIfNotImplementedIn(someObj, MathInterface)