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

vas

v2.1.0

Published

simple composable data services using muxrpc

Downloads

29

Readme

features

  • API is a data structure: easy to understand and simple to extend
  • functional: methods, permissions, handlers are just functions, no magic
  • fractal: compose one API from many smaller APIs
  • database-agnostic: create API services on top of anything
  • authentication: identify who the current user is
  • authorization: permit what a user can do
  • http stack: same paradigm for http request handlers like front-end bundlers, blob stores, etc
  • omakse: consistent flavoring with pull streams all the way down

for a user interface complement, see inu

demos

if you want to share anything using vas, add your thing here!

example

var vas = require('vas')
var pull = vas.pull
var values = require('object-values')

var service = {
  name: 'things',
  manifest: {
    all: 'source',
    get: 'async'
  },
  methods: function (server, config) {
    return { all, get }

    function all () {
      const things = values(config.data)
      return pull.values(things)
    }

    function get (id, cb) {
      cb(null, config.data[id])
    }
  },
  permissions: function (server, config) {
    return { get }

    function get (id) {
      if (id === 'nobody') {
        return new Error('nobody is not allowed.')
      }
    }
  },
  handlers: function (server, config) {
    return [
      function (req, res, next) {
        console.log('cookie:', req.headers.cookie)
        next()
      }
    ]
  }
}

// could also attach db connection, file descriptors, etc.
var config = {
  data: {
    1: 'human',
    2: 'computer',
    3: 'JavaScript'
  }
}

var port = 6000
var url = `ws://localhost:${port}`
var server = vas.listen(service, config, { port })
var client = vas.connect(service, config, { url })

client.things.get(1, (err, value) => {
  if(err) throw err
  console.log('get', value)
  // get human
})

pull(
  client.things.all(),
  pull.drain(v => console.log('all', v))
)
// all human
// all computer
// all JavaScript

setTimeout(function () {
  server.close()
  client.close()
}, 1000)

for a more complete example, see ./example, which you can run with npm run example and query using command-line using npm run example:cli -- things.find.

concepts

let's say we're writing a todo app (so lame right).

we want to be able to get all the todo items, update a todo item, and add another one.

if we think of these methods as functions, it might look like this (using knex):

const toPull = require('stream-to-pull-stream')
const Db = require('knex')

const db = Db({
  client: 'sqlite3',
  connection: {
    filename: './mydb.sqlite'
  }
})

const methods = {
  getAll,
  update,
  add
}

function getAll () {
  return toPull(db('todos').select().stream())
}

function update (nextTodo, cb) {
  db('todos')
    .where('id', nextTodo.id)
    .update(nextTodo)
    .asCallback(cb)
}

function add (todo, cb) {
  db('todos').insert(todo).asCallback(cb)
}

what if we could call these functions directly from the front-end?

to do so, we need to specify which functions are available and of what type they are, which is called a manifest.

const manifest = {
  getAll: 'source',
  update: 'async',
  add: 'async'
}

where 'source' corresponds to a source pull stream and 'async' corresponds to a function that receives an error-first callback.

this manifest provides us with enough information to construct a mirrored function on the client:

pull(
  getAll(),
  pull.log()
)

together, this could become a service, complete with a name and version:

const service = {
  name: 'todos',
  version: '1.0.0',
  manfest,
  methods
}

what if we had multiple services that need to share some configuration, such as a single database connection?

to do so, we want to pass a config object to the service methods, in particular a function that receives the config and returns the method functions.

combine these concepts together and welcome to vas. :)

usage

a vas service is a definition for a duplex stream that responds to requests.

a vas service is defined by an object with the following keys:

  • name: a string name
  • version (optional): a string semantic version
  • manifest: an object muxrpc manifest
  • methods: a methods(server, config) pure function that returns an object of method functions to pass into muxrpc
  • permissions: a permissions(server, config) pure function that returns an object of permission functions which correspond to methods. each permission function accepts the same arguments as the method and can return an optional new Error(...) if the method should not be called.
  • handlers a handlers(server, config) pure function that returns an array of http request handler functions, each of shape (req, res, next) => { next() }.
  • authenticate: a authenticate(server, config) pure function that returns an authentication function, of shape (req, cb) => cb(err, id). only the first authenticate function will be used for a given set of services. the id returned by authenticate will be available as this.id in method or permission functions and req.id in handler functions.
  • services: any recursive sub-services

many vas services can refer to a single service or an Array of services

vas = require('vas')

the top-level vas module is a grab bag of all vas/* modules.

you can also require each module separately like require('vas/listen').

vas.listen(services, config, options)

creates a server with createServer(services, config), then

listens to a port and begins to handle requests from clients using pull-ws-server

options is an object with the following (optional) keys:

  • port: port to open WebSocket server
  • onListen: function to call once server is listening, receives (err, httpServer, wsServer).
  • createHttpServer: function to create http server, of shape (handlers) => server. default is (handlers) => http.createServer(Stack(...handlers))
  • serialize: a duplex pull stream to stringify and parse json objects being sent to and from methods

vas.connect(client, config, options)

creates a client with createClient(services, config), then

connects the client to a server over websockets using pull-ws-server

options is an object with the following (optional) keys:

  • url: string or object to refer to WebSocket server
  • onConnect: function to call once client is connected
  • serialize: a duplex pull stream to stringify and parse json objects being sent to and from methods

vas.command(services, config, options, argv)

run a command on a server as a command-line interface using muxrpcli

options are either those passed to vas.listen or vas.connect, depending on if argv[0] === 'server'

argv is expected to be process.argv.


server = vas.createServer(services, config, options)

a vas server is an instantiation of a service that responds to requests.

createServer returns an object that corresponds to the (recursive) services and respective methods returned by methods.

options is an object with the following (optional) keys:

  • serialize: a duplex pull stream to stringify and parse json objects being sent to and from methods

client = vas.createClient(services, config, options)

a vas client is a composition of manifests to makes requests.

createClient returns an object that corresponds to the (recursive) services and respective methods in manifest.

options is an object with the following (optional) keys:

  • serialize: a duplex pull stream to stringify and parse json objects being sent to and from methods

server.createStream(id)

client.createStream(id)

returns a duplex pull stream using muxrpc

for a server, if id is passed in, will bind each method or permission function with id as this.id.

frequently asked questions (FAQ)

how to reduce browser bundles

by design, service definitions are re-used between client and server creations.

this leads to all the server code being included in the browser, when really we only need the service names and manifests to create the client.

to reduce our bundles to only this information (eliminating any require calls or other bloat in our service files), use the evalify browserify transform.

to evalify only service files, where service files are always named service.js, install evalify and add the following to your package.json

{
  "browserify": {
    "transform": [
      ["evalify", { "files": ["**/service.js"] } ]
    ]
  }
}

how to do authentication

authentication is answers the question of who you are.

here's an example of how to do this in vas, stolen stolen from holodex/app/dex/user/service:

(where config.tickets corresponds to an instance of ticket-auth)

const Route = require('http-routes')

const service = {
  name: 'user',
  manifest: {
    whoami: 'sync'
  },
  authenticate: function (server, config) {
    return (req, cb) => {
      config.tickets.check(req.headers.cookie, cb)
    }
  },
  methods: function (server, config) {
    return { whoami }

    function whoami () {
      return this.id
    }
  },
  handlers: (server, config) => {
    return [
      Route([
        // redeem a user ticket at /login/<ticket> and set cookie.
        ['login/:ticket', function (req, res, next) {
          config.tickets.redeem(req.params.ticket, function (err, cookie) {
            if(err) return next(err)
            // ticket is redeemed! set it as a cookie, 
            res.setHeader('Set-Cookie', cookie)
            res.setHeader('Location', '/') // redirect to the login page.
            res.statusCode = 303
            res.end()
          })
        }],
        // clear cookie.
        ['logout', function (req, res, next) {
          res.setHeader('Set-Cookie', 'cookie=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;')
          res.setHeader('Location', '/') // redirect to the login page.
          res.statusCode = 303
          res.end()
        }],
        // return current user. (for debugging)
        ['whoami', function (req, res, next) {
          res.end(JSON.stringify(req.id) + '\n')
        }]
      ])
    ]
  }
}

install

npm install --save vas

inspiration

license

The Apache License

Copyright © 2016 Michael Williams

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.