Nocturnal Pumpkin Maelstrom

    atypical

    1.2.1 • Public • Published

    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(thisarguments)  # our more verbose `super` invocation 
     
      push: TypeSafe.method (bird)->
        @push.protect arguments                      # we want to protect before we call super 
        Aviary.__super__.push.apply(thisarguments) # 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

    Install

    npm i atypical

    DownloadsWeekly Downloads

    1

    Version

    1.2.1

    License

    ISC

    Last publish

    Collaborators

    • ondreian