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

infoserver

v0.0.7

Published

static file server that spews a bunch of metadata with files

Downloads

2

Readme

InfoServer

File manager server & middleware

Usage

npm install --save infoserver

Then, in your js:


import infoServer from 'infoserver';

infoServer(rootDir,{/**options**/}).then(api=>
    api.commands.getMeta('/some/file/path')
        .then(result=>{console.log(results)})
        .catch(err=>{throw err})
})

or as an express middleware:


var app = require('express').app
import infoServer from 'infoserver';

infoServer(rootDir,{/**options**/}).then(api=>
    app.use('/meta',api.middleware);
})

(go to examples/express and run npm install && node index.js to check an example of a bare bones express app)


infoServer(rootDir,options) → Promise

  • rootDir is your public directory
  • options is an object with the following switches:
    • adapter: provide an adapter database for collections (see below for spec).
    • persist: either a boolean, or a file path. Used by the default memory adapter to write/load the database to/from a json file. Only use it to prototype, provide your own adapter for production
    • filters: an array of filters used by getMeta and getMetaRecursive.

The promise resolves to an api generated by apido.


commands

All commands from fs-meta, plus:


Selections

infoserver exposes a selections api to group files together. They work like virtual directories in the sense that they can contain files, or other selections.

All selections commands use an adapter for persistence. Only a memory adapter is provided by default, to create your own, see the spec below.

All arrays can be specified, in a GET array, either by repeating the key (?files=x&files=y), or by separating them with a comma (?files=x,y).

api.selections.commands.add(groupName[,files[,groups]])

Adds a group called groupName and adds the files and groups specified. If groupName does not exist, it is created. If it already has files or groups, new files and groups are appended.

Also accessible through api.runPath('/selections/add',{groupName,files,groups}) or on the http url /selections/add/:groupName?files=x&files=y&groups=x&groups=y

api.selections.commands.get([type,items])

if nothing is specified, returns all groups that are children of the root group. items is an array of files paths if type is "file", or an array of groups names if type is "group"

Also accessible through api.runPath('/selections/get',{type,items}) or on the http url /selections/get/:type?items=a&items=b

api.selections.commands.remove(groupName[,files[,groups]])

If files or groups are not specified, deletes the group designed by groupName, as well as any files that are only in that specific group. if files and groups are specified, removes the specified files and sub-groups from the group. Orphan files are removed, but orphan groups aren't.

Also accessible through api.runPath('/selections/remove',{groupName,files,groups}) or on the http url /selections/remove/:groupName?files=x&files=y&groups=x&groups=y

Objects

An example response from selections/get/a:

{
    response:"success",
    //api meta data...
    "result":{
        "groups": {
            "groupA": {
                "name": "groupA",
                "files": ["path/to/a","path/to/b","path/to/c"],
                "groups": []
                }
            },
        "files": {
            "path/to/a": {
                "path": "path/to/a",
                "parents": ["groupA"]
            },
            "path/to/b": {
                "path": "path/to/b",
                "parents": ["groupA"]
            },
            "path/to/c": {
                "path": "path/to/c",
                "parents": ["groupA","groupB"]
            }
        }
    }
} 

Here's an easy function to re-nest everything in a cyclic structure if you wanted to:

function nest({result},cache){
    cache = cache || {
        groups:{}
    ,   files:{}
    }
    function add(collection,value){
        const key = collection == 'files' ? 'path' : 'name';
        const obj = {[key]:value};
        cache[collection][value] = obj;
        return obj; 
    }
    for(group of result.groups){
        let {name} = group;
        let files = group.files.map(path=>
            result.files[path] || cache.files[path] || add('files',path)
        )
        let groups = group.groups.map(name=>
            result.groups[name] || cache.groups[name]|| add('groups',name)
        )
        if(cache.groups[name]){
            Object.assign(cache.groups[name],{files,groups});
        }else{
            cache.groups[name] = {name,files,groups}
        }
    }
    for(file of result.paths){
        let {path} = file;
        file.parents = file.parents.map(name=>
            result.groups[name] || cache.groups[name] || add('groups',name)
        )
        if(cache.files[path]){
            cache.files[path].parents = parents;
        }else{
            cache.files[path] = {path,parents}
        }        
    }
    return cache;
}

Adapter

An adapter has the following signature:

export default Promise.promisify(function customAdapter(fs,opts,cb){
     const commands = {
        add(groupName,files,groups,cb){}
    ,   getFiles(files,cb){}
    ,   getGroups(groups,cb){}
    ,   getRoot(cb){}
    ,   remove(groupName,files,groups,cb){}
    }
    cb(null,commands);
})
  • add is guaranteed to receive a group name, but files and groups are optional. If a group doesn't exist, it is expected that add creates it on the fly. If it already exists, then groups and files should be added to the existing groups and files.
  • getFiles receives an array of files paths
  • getGroups receives an array of group names
  • remove is guaranteed to receive a group name, but files and groups are optional. If groups and files are empty, then the function should remove the group specified by groupName (as well as cascade all relations). If either files or groups are specified, then the group should remove all

Tests & Compile

First:

npm install --dev

Compile:

npm run compile

tests:

Not implemented yet

run example:

cd examples/express
npm install
cd ../..
npm start

Then open http://localhost:3000 in your browser

To get debug messages, you can set:

DEBUG=infoserver:* node yourApp.js

MIT License

Copyright © Jad Sarout

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.