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

atypical

v1.2.1

Published

Type safe methods in a very light module

Downloads

2

Readme

Atypical

Build Status

Type Safe Methods for functional "Classes"

A lightweight (97 lines of javascript) type protection utility

  • checkout ./tests to see some working examples
  • TypeSafe.method(fn) wraps the function with some TypeSafe 'decorators' but retain context on the current object's prototype
  • TypeSafe.Array.of(klass) returns a special decorator that allows TypeSafe to check if it is an Array comprised solely of the given class down the road
  • @[method].protect(arguments) is how you choose when to check your arguments
  • one caveat is that wrapping a function in TypeSafe breaks the ability for Coffeescript to compile a super so you must return to a more verbose declaration
  • TypeSafe.method.description() builds a description of the method
  • TypeSafe.id() returns the current "id" of the function (useful for debugging)
  • TypeSafe.id('example') would id the function as "example"

Enough talking let's see some code...

First let's set up a class to inherit from:

TypeSafe = require 'atypical'

class Habitat
  constructor: (animals)->
    @animals = []
    @push animals if animals

  add: (animal)->
    @animals.push animal

  push: (animals)->
    @add for animal in animals if animals
    @

The Magic

class Aviary extends Habitat
  add: TypeSafe.method (bird)->
    @add.protect arguments                       # we want to protect our Aviary
    Aviary.__super__.add.apply(this, arguments)  # our more verbose `super` invocation

  push: TypeSafe.method (bird)->
    @push.protect arguments                      # we want to protect before we call super
    Aviary.__super__.push.apply(this, arguments) # our more verbose `super` invocation

# These descriptions add more context to the errors

Aviary::add.describe
  arg  : 0                           # the position of the argument
  name : 'bird'                      # the name of the argument      (for debugging)
  desc : "The state of the Example"  # a description of the argument (for debugging and document generation perhaps?)
  type : Bird                        # the type of the argument      (the instanceof to check against)

Aviary::push.describe
  arg  : 0
  name : 'birds'
  desc : "an Array of Birds to add"
  type : TypeSafe.Array.of(Bird)

and we need some animals:


class Animal
  constructor: (@name)->

class Bird extends Animal

class Reptile extends Animal

Now let's see what happens when we run some examples:


aviary = new Aviary
aviary.add new Bird 'goshawk' # all good, because it was a Bird
aviary.push new Bird          # throws error

The error would be like this:

TypeError: Wrong Argument Type: expects Array of Bird instances -- top level was Bird
  at [object Object].type.check (C:\dev\npm-modules\atypical\src\index.coffee:24:19)
  at TypeSafe.module.exports.TypeSafe.protect (C:\dev\npm-modules\atypical\src\index.coffee:48:27)
  at Aviary.push

# this would run fine
aviary.push [
  new Bird "pigeon"
  new Bird "kiwi"
]

# this would throw an error
aviary.push [
  new Bird "ostrich"
  new Reptile "python"
]

The error thrown would be like this:

TypeError: Wrong Argument Type: expects Array of Bird instances -- element at index [1] was instance of Reptile
  at [object Object].type.check (C:\dev\npm-modules\atypical\src\index.coffee:27:23)
  at TypeSafe.module.exports.TypeSafe.protect

You can also use it to protect types passed into normal functions:


example = TypeSafe.method (src, dest, cb)->
  example.protect(arguments)

example.describe {
  arg  : 0
  name : 'oldPath'
  desc : "the original path of the file you want to name"
  type : String
}

example.describe {
  arg  : 1
  name : 'newPath'
  desc : "the destination path of the file you want to name"
  type : String
}

example.describe {
  arg  : 2
  name : 'callback'
  desc : "the callback to be ran after completion of the rename"
  type : Function
}

# this would be fine
example 'test1', 'test2', ->

# this would throw the example error below
example 'test1', 'test2'

The result of a mismatched arguments.length error for an anonymous function would read like this:


Error: Wrong Argument Length: expects 3 and was 2

Description Of TypeSafe::anonymous:

 arg: 0
name: oldPath
desc: the original path of the file you want to name
type: String

 arg: 1
name: newPath
desc: the destination path of the file you want to name
type: String

 arg: 2
name: callback
desc: the callback to be ran after completion of the rename
type: Function


  at TypeSafe.module.exports.TypeSafe.protect (C:\dev\npm-modules\atypical\src\index.coffee:61:17)
  at C:\dev\npm-modules\atypical\tests\index.coffee:74:11
  at Object.<anonymous> (C:\dev\npm-modules\atypical\tests\index.coffee:98:1)
  at Object.<anonymous> (C:\dev\npm-modules\atypical\tests\index.coffee:1:1)
  at Module._compile (module.js:456:26)

However, let's go back to our Aviary example and name one of the methods so we know where, what, and the description is if it breaks down the road

Aviary::push.id "Aviary#push"  # name this method something sensible

aviary = new Aviary()
aviary.push()         # pass no arguments, even though it expects and ArrayOf Birds

The resulting error would then look like this:


Error: Wrong Argument Length: expects 1 and was 0

Description Of Aviary#push:

 arg: 0
name: birds
desc: an Array of Birds to add
type: ArrayOfBird


  at TypeSafe.module.exports.TypeSafe.protect (C:\dev\npm-modules\atypical\src\index.coffee:64:17)
  at Aviary.push (C:\dev\npm-modules\atypical\tests\index.coffee:32:11)
  at Object.<anonymous> (C:\dev\npm-modules\atypical\tests\index.coffee:75:20)
  at Object.<anonymous> (C:\dev\npm-modules\atypical\tests\index.coffee:1:1)
  at Module._compile (module.js:456:26)

That would be useful for tracking an anonymous function that is called very deeply with unexpected results, or writing your own document generator by simply requiring, and iterating over your module