New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

bootstruct

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bootstruct - npm Package Compare versions

Comparing version 1.2.4 to 1.2.5

2

index.js

@@ -38,3 +38,3 @@ 'use strict';

RC = new Ctrl(rootMap, 'RC', null);
RC = new Ctrl(rootMap, '/', null);

@@ -41,0 +41,0 @@ return function handler (req, res) {

@@ -52,24 +52,7 @@ 'use strict';

/*
┌─────────────────────────
│ removes file extension
*/
function removeExt (entry) {
var lastDot = entry.lastIndexOf('.');
var entryHandlers = {
first: function (first) {
this.first = require(first.path);
},
return entry.substr(0, lastDot);
}
/*
┌──────────────────────────────────────────────
│ returns lowerCased extension-less entryname
*/
module.exports.normalizeEntryName = function (entryName, isFile) {
var name = entryName.toLowerCase();
return (isFile) ? removeExt(name) : name;
};
module.exports.handlers = {
index: function (index) {

@@ -79,7 +62,8 @@ this.index = require(index.path);

first: function (first) {
this.first = require(first.path);
all: function (all) {
if (!this.index) {
this.index = require(all.path);
}
},
/* todo: "verbs" handler currently supports only a folder entry */
verbs: function (verbs) {

@@ -89,19 +73,12 @@ var self = this;

forIn(verbs.entries, function (verbName, verbMap) {
if (verbMap.type === 1) {
// -3 remove tail '.js'
verbName = verbName.substr(0, verbName.length-3);
}
verbName = normalizeEntryName(verbName, verbMap.type);
// set the verb only if not set yet.
// this makes verbs[verbName] overriden-able by a specific verb entry
if (verbName === 'all' || verbName === 'noVerb') {
if (!self[verbName]) {
self[verbName] = require(verbMap.path);
}
/*
│ set only if not set yet.
│ this makes 'verbs' overriden-able by a specific verb entry.
│ 'all' is an exception because it's actually an 'index' and not stored in ctrl.verbs.
*/
if (!self[verbName] && !self.verbs[verbName]) {
entryHandlers[verbName.toLowerCase()].call(self, verbMap);
}
else {
if (!hasOwn.call(self.verbs, verbName)) {
self.verbs[verbName] = require(verbMap.path);
}
}
});

@@ -126,4 +103,4 @@ },

noverb: function (noverb) {
this.noVerb = require(noverb.path);
noverb: function (noVerb) {
this.verbs.noVerb = require(noVerb.path);
},

@@ -134,2 +111,29 @@

}
};
};
/*
┌─────────────────────────
│ removes file extension
*/
function removeExt (entry) {
var lastDot = entry.lastIndexOf('.');
return entry.substr(0, lastDot);
}
/*
┌────────────────────────────────────────────────
│ returns lowerCased extension-less entryname.
*/
function normalizeEntryName (entryName, isFile) {
var name = entryName.toLowerCase();
return (isFile) ? removeExt(name) : name;
}
// ----------------------------------------------------
module.exports.entryHandlers = entryHandlers;
module.exports.normalizeEntryName = normalizeEntryName;

@@ -6,17 +6,21 @@ 'use strict';

===========
private methods:
parseFolderMap - on init
setupChains - on init
removeSelfName - on request (checkIn)
isTarget - on request (checkIn)
runVerb - called with ctx: this = ctrl
runSub - called with ctx: this = ctrl
run = {
.sub() - on request (in chain)
.verb() - on request (in chain)
}
both being pushed to chains and called with: .call(ctrl, io)
parseFolderMap() - on init
setupChains() - on init
removeSelfName() - on request (checkIn)
constructor (exported)
Constructor (exported)
exposed API (constructor.prototype):
checkIn
next
checkOut
exposed API:
Constructor.prototype = {
checkIn()
isTarget()
next()
checkOut()
}

@@ -26,6 +30,6 @@ */

var CtrlProto;
var forIn = require('../utils/forIn');
var entriesHandler = require('./entriesHandler');
var handlers = entriesHandler.handlers;
var normalize = entriesHandler.normalizeEntryName;
var forIn = require('../utils/forIn');
var entriesHandler = require('./entriesHandler');
var entryHandlers = entriesHandler.entryHandlers;
var normalizeEntryName = entriesHandler.normalizeEntryName;

@@ -46,8 +50,78 @@ var hasOwn = Object.prototype.hasOwnProperty;

//-----------------
// private methods
//-----------------
// both called with: .call(ctrl, io) from chain
var run = {
sub: function (io) {
var subCtrl;
var next = io.params[0];
// get called on init
subCtrl = this.subCtrls[next.toLowerCase()];
if (typeof subCtrl === 'function') { // method
subCtrl.call(this, io);
}
else { // subCtrl
subCtrl.checkIn(io);
}
},
verb: function (io) {
var verbs = this.verbs;
var verb = verbs[io.req.method.toLowerCase()];
if (verb) {
verb.call(this, io);
}
else if (verbs.noVerb) {
verbs.noVerb.call(this, io);
}
else {
this.next(io);
}
}
};
/*
│ because ctrl.verbs also contain 'noVerb'
*/
function hasVerbs (ctrl) {
var verbs = ctrl.verbs;
if (verbs.get || verbs.post || verbs.put || verbs.delete){
return true;
}
return false;
}
function hasSubCtrls (ctrl) {
return ctrl.subKeys.length > 0;
}
/*
┌───────────────────────────────────
│ id is unique, name isn't.
│ examples:
│ id: '/', '//foo', '//foo/bar'
│ name: '/', 'foo', 'bar'
*/
function setMetaProps (ctrl, name, parent) {
// RC stands for the Root Controller
if (!parent) { // RC
ctrl.RC = ctrl;
ctrl.isRoot = true;
ctrl.id = '/';
}
else {
ctrl.RC = parent.RC;
ctrl.isRoot = false;
ctrl.id = parent.id + '/' + name;
}
}
// runs on init
/*
┌─────────────────────────────────────────

@@ -73,26 +147,48 @@ │ the main ctrl init function.

// normalize = lowerCase, remove extension
name = normalize(key, isFile);
// normalizeEntryName = lowerCase, remove extension
name = normalizeEntryName(key, isFile);
// if such entryHandler exists
if (hasOwn.call(handlers, name)) {
handlers[name].call(ctrl, ctrlEntries[key]);
return;
if (hasOwn.call(entryHandlers, name)) {
entryHandlers[name].call(ctrl, entryMap);
}
else if (isFile){
ctrl.methods[name] = require(entryMap.path);
else {
ctrl.subCtrls[name] = (isFile)? require(entryMap.path) : entryMap;
ctrl.subKeys.push(name);
}
else { // folder
ctrl.subCtrls[name] = new Ctrl(entryMap, key, ctrl);
}
ctrl.subKeys.push(name);
});
}
// get called on init
/*
┌──────────────────────────────────────────────────────────────────────────────────────────
│ delegated means when exists in a ctrl, all of its sub-ctrl will get it too (inheritance)
*/
function delegateNoVerb (ctrl) {
var verbs = ctrl.verbs;
// if doesn't own: take from parent
if (!verbs.noVerb && !ctrl.isRoot) {
verbs.noVerb = ctrl.parent.verbs.noVerb;
}
}
// runs on init
/*
┌────────────────────────────────────────────────────────────────────────────────────────────
│ Controllers can be used in 2 ways: as target or not
│ so they can have 2 different chains of methods, 1 for each case.
│ examples:
│ 0. subCHain = [first, runSub, last]
│ 1. targetChain = [first, index, runVerb, last]
│ "setupChains" fn creates an array of 2 chains (2 arrays) of methods to run in both cases.
│ ends with:
│ this.chains = [subChain, targetChain];
*/
function setupChains (ctrl) {
var subChain = ctrl.subChain = [];
var targetChain = ctrl.targetChain = [];
var subChain = [];
var targetChain = [];

@@ -110,9 +206,8 @@

}
if (hasProps(ctrl.verbs)) {
targetChain.push(runVerb);
if (hasVerbs(ctrl)) {
targetChain.push(run.verb);
}
// subCtrls or methods
if (ctrl.subKeys.length > 0) {
subChain.push(runSub);
if (hasSubCtrls(ctrl)) {
subChain.push(run.sub);
}

@@ -126,7 +221,18 @@

ctrl.targetChainLen = targetChain.length;
ctrl.subChainLen = subChain.length;
// 0: subChain
// 1: targetChain
ctrl.chains = [subChain, targetChain];
}
// get called on each request (checkIn)
function initSubCtrls (ctrl) {
var subCtrls = ctrl.subCtrls;
forIn(subCtrls, function (name, subCtrlMap) {
if (typeof subCtrlMap != 'function') {
subCtrls[name] = new Ctrl(subCtrlMap, name, ctrl);
}
});
}
/*

@@ -151,3 +257,3 @@ ┌─────────────────────────────────────────────────────────────────────

// if (first == ctrl.name)
if (first && first.toLowerCase() === ctrl.name.toLowerCase()) {
if (first && first.toLowerCase() === ctrl.name) {

@@ -159,62 +265,6 @@ // remove first

// get called on each request (checkIn)
/*
based on the next param, is this ctrl is the target ctrl for the current request?
*/
function isTarget (ctrl, io) {
var next = io.params[0];
// if next is in ctrl.subKeys
if (next && ctrl.subKeys.indexOf(next.toLowerCase()) > -1) {
return false;
}
return true;
}
// ------------------------------------------
// on init: this fn is pushed in targetChain
// on request: called with .call(ctrl, io)
// ------------------------------------------
function runVerb (io) {
var verb = this.verbs[io.req.method.toLowerCase()];
if (verb) {
verb.call(this, io);
}
else {
this.next(io);
}
}
// ----------------------------------------
// on init: this fn is pushed in subchain
// on request: called with .call(ctrl, io)
// ----------------------------------------
function runSub (io) {
var sub;
var next = io.params[0];
next && (next = next.toLowerCase());
if (next) {
// is(next = subCtrls)?
if (hasOwn.call(this.subCtrls, next)) {
this.subCtrls[next].checkIn(io);
}
// is(next = method)?
else if (hasOwn.call(this.methods, next)){
this.methods[next].call(this, io);
}
// todo: shared method?
}
}
// ==========================================

@@ -224,6 +274,7 @@ // Ctrl Constructor & Constructor.prototype

function Ctrl (folderMap, name, parent) {
// defaults
name = name || '/'
parent = parent || null;
// meta
this.id = (parent) ? (parent.id+'/'+name) : 'RC';
this.parent = parent;

@@ -239,12 +290,16 @@ this.name = name;

this.index = null;
this.verbs = {};
this.noVerb = {};
this.subCtrls = {};
this.methods = {};
this.last = null;
this.verbs = Object.create(null);
this.subCtrls = Object.create(null);
// init
setMetaProps(this, name, parent);
parseFolderMap(this, folderMap);
delegateNoVerb(this);
setupChains(this);
initSubCtrls(this);
}

@@ -267,3 +322,3 @@

idx : 0,
isTarget : isTarget(this, io)
isTarget : this.isTarget(io)
};

@@ -276,5 +331,25 @@

/*
┌────────────────────────────────────────────────────────────────────────────
│ test on checkIn, if this ctrl is the target ctrl for the current request.
│ checks if the next param is an existing sub
│ returns
│ 0: sub
│ 1: target
*/
CtrlProto.isTarget = function (io) {
var next = io.params[0];
// if next is in this.subKeys
if (next && this.subKeys.indexOf(next.toLowerCase()) > -1) {
return 0;
}
return 1;
};
/*
┌──────────────────────────────────────────────────
│ called by io.next()
│ runs the next fn in chain, based on the profile
*/

@@ -284,23 +359,23 @@ CtrlProto.next = function(io, profile) {

// set this as the current handling ctrl
// set current handling ctrl
io._ctrl = this;
// get profile
profile = profile || io._profiles[this.id];
// is target?
i = profile.idx++;
// is target? 0: sub, 1: target
isTarget = (profile.isTarget);
// get corresponding chain
chain = (isTarget) ? this.targetChain : this.subChain;
chain = this.chains[isTarget];
i = profile.idx++;
// if there's more in the chain to run - run it.
if (i < chain.length) {
// if there's more in the chain to run - run it. else - checkOut
if (chain[i]) {
chain[i].call(this, io);
return;
}
this.checkOut(io);
else {
this.checkOut(io);
}
};

@@ -314,6 +389,4 @@

CtrlProto.checkOut = function(io) {
var parent = this.parent;
if (parent) {
parent.next(io);
if (!this.isRoot) {
this.parent.next(io);
}

@@ -324,3 +397,9 @@ else { // RC ends response

// todo: think. is it right for the RC to always end the response?
/*
* Think: Is it right for the RC to always end the response?
*
* This makes Bootstruct a closed app and
* unable to pass the request/response to an external middleware.
* indtead of ending the response, consider calling "next" (that must be passed on Bootstruct creation)
*/
};

@@ -327,0 +406,0 @@

@@ -91,3 +91,3 @@ 'use strict';

folderObj.entries[entryName.toLowerCase()] = newObj;
folderObj.entries[entryName] = newObj;
});

@@ -94,0 +94,0 @@

{
"name": "bootstruct",
"version": "1.2.4",
"description": "Routing by structure",
"version": "1.2.5",
"description": "Routing by structure. A name-convention framework for Node.js.",
"main": "index.js",

@@ -6,0 +6,0 @@ "author": "Taitu Lizenbaum taitulism@gmail.com",

@@ -1,36 +0,19 @@

Bootstruct
==========
>*"Routing by structure"*
A name-convention framework for Node.js
Table of contents
-----------------
>*Routing by structure.*
* [Get started](#get-started)
* [Overview](#overview)
* [Controllers](#controllers)
* [get, post, put, delete](#get-post-put-delete)
* [index](#index)
* [first & last](#first--last)
* [verbs](#verbs)
* [io](#io)
* [io.params](#ioparams)
* [io other props](#io-other-props)
* [Summary](#summary)
* [The Shorter Version](#the-shorter-version)
* [Important notes](#important-notes)
A name-convention framework for Node.js
Get started
-----------
1. Start a new project: Create a folder with an extra ordinary name like: "myApp".
2. Install Bootstruct:
1. Install Bootstruct in a new folder:
```sh
$ npm install bootstruct
```
If this means nothing to you, welcome to Node!
3. In your project's folder, create a `server.js` file with the following content:
2. Create an `index.js` file and an `app` folder.
3. Copy the following to your `index.js` file:
```js

@@ -41,4 +24,4 @@ var http = require('http');

// create a new Bootstruct app from `myApp` folder
var app = bts('myApp');
// this creates a new Bootstruct app from `app` folder
var app = bts('app');

@@ -51,5 +34,4 @@

```
4. Create a folder named `app` in your project's folder.
5. Inside `app`, create a file named `get.js` and make it export a single function that accepts a single argument:
4. Inside `app` folder, create a `get.js` file with the following content:
```js

@@ -60,14 +42,9 @@ module.exports = function (io) {

```
`io` is an object that holds the native request/response as properties:
&nbsp; &nbsp; &nbsp; `io.req`
&nbsp; &nbsp; &nbsp; `io.res`
Both by reference, untouched.
If you used Node before, the `io.res.end` part should be very clear now.
6. Start your server up:
5. Start your server up:
```
$ node server.js
$ node index.js
```
** You're now ready for GET requests to `yourdomain.com:1001/` **
**You're now ready for GET requests to `yourdomain.com:1001/`**

@@ -79,5 +56,44 @@ >NOTE: You can use `post`, `put` and `delete` (.js) as well. They are all reserved names for files and folders in Bootstruct.

#Overview
---------
With Bootstruct you structure your files and folders in a certain way to get a certain behavior.
Table of contents
-----------------
* [Get started](#get-started)
* [Overview](#overview)
* [get, post, put, delete](#get-post-put-delete)
* [A "stale" folder](#a-stale-folder)
* [Scale It Up](#scale-it-up)
* ['verbs' folder](#verbs-folder)
* [noVerb](#noverb)
* [first & last](#first--last)
* [Reserved Entry Names](#reserved-entry-names)
* [io](#io)
* [io.params](#ioparams)
* [io other props](#io-other-props)
* [Bootstruct Flow](#bootstruct-flow)
* [The Shorter Version](#the-shorter-version)
* [Important notes](#important-notes)
intro
-----
"Coding by convention" or "configuration over code" or whatever. They are all fine and it's all a matter of personal taste and project's needs.
Bootstruct does it with names convention. "Routing by structure" if you'd like.
As such, learning Bootstruct is more about understanding how it reads your folder-structure and behave based on this structure than code and syntax.
Overview
--------
What Bootstruct does?
* Bootstruct saves you from coding your routes
* Bootstruct enforces a good code seperation by design
* Bootstruct gives you intuitive control over requests' flow
* Bootstruct provides you with nice RESTfull URLs
To support routes like:

@@ -89,24 +105,16 @@

you don't need to learn any new syntax, just structure your files and folders like this:
```js
.
├── node_modules
├── app <──
│ ├── index.js
you don't have to write any code but the handler functions themselves. Just structure your app folder like the following and export your handlers from the `index.js` files like in the [Get started](#get-started) example above:
```
├── app
│ ├── index.js <── called for all requests to: '/'
│ └── foo
│ ├── index.js
│ ├── index.js <── called for all requests to: '/foo'
│ └── bar
│ └── index.js
├── index.js
└── package.json
│ └── index.js <── called for all requests to: '/foo/bar'
```
**The `index.js` file will run only in the requested folder.**
If you're familiar with express/connect, the equivalent would be:
```js
// NOT Bootstruct! express/connect equivalent:
// NOT Bootstruct! **express/connect** equivalent:
app.all('/', function () {

@@ -126,246 +134,234 @@ // ...

"Coding by convention" or "configuration over code" or whatever. They are all fine and it's all a matter of personal taste and project's needs.
Bootstruct does it with names convention. "Routing by structure" if you'd like.
>NOTE: You can use `all` instead of `index` if you'd like.
As such, learning Bootstruct is more about file-names, folder-names and folder-structure than code and syntax. Understand Bootstruct's flow.
As your app scales up, you can turn your files into folders. If the `get.js` file from the [Get started](#get-started) example would get bigger, you could replace it with a `get` folder containing an `index.js` file with whatever contents the original `get.js` file had. Anyway this is going to be "require"d as the folder's `get` method.
>NOTE: This is why Bootstruct doesn't matter if it's a file or a folder and in these docs files and folders are referred as **"entries"**.
get, post, put, delete
----------------------
You probably want to be more specific about different types of request methods. Just add verb-files where you need them (e.g. `get.js`, `post.js`):
You probably want to support different types of request methods individually, don't you?
Well, just add verb-entries where you need them (for example a `get.js` file or a `post` folder).
```
├── app
│ ├── index.js <── called for ALL requests to: '/'
│ ├── get.js <── called for GET requests to: '/'
│ └── foo
│ ├── index.js <── called for ALL requests to: '/foo'
│ ├── get.js <── called for GET requests to: '/foo'
│ ├── post.js <── called for POST requests to: '/foo'
│ └── bar
│ └── index.js <── called for ALL requests to: '/foo/bar'
```
>NOTE: `index.js` is called before all verbs (when exists).
You don't have to use an `index.js`:
```
├── app
│ ├── get.js <── called for GET requests to: '/'
│ └── foo
│ ├── get.js <── called for GET requests to: '/foo'
│ ├── post.js <── called for POST requests to: '/foo'
│ └── bar
│ └── get.js <── called for GET requests to: '/foo/bar'
```
Bootstruct provides you with an even greater controll on requests' flow. The `index` and the verb entries only run for the requested folder (or the target folder). On a request to `/foo/bar`, `bar` is the target folder and requests are just "passing by" its parent folders `app` and `app/foo`.
If an `index.js` exists it will **always** run before the verb and they are both called only for the target folder. The target-folder is the last folder whose name found in the request pathname (e.g. `bar` on request to: `/foo/bar`).
If you want a folder to do something even if it's not the target folder, create a `first` entry in it. It will run for every request this folder was called in, even if it's not the target folder.
**express/connect** equivalent would be:
`first` is called in a folder when a request comes in whether or not this folder is the target folder or should the request be passed on. `first` is called when a request "checks-in" at a folder and `last` is called when it "checks-out", after the target folder is done with its `index` and verb files.
```js
// NOT Bootstruct! express/connect equivalent:
app.all('/', function () {
// ...
});
All of these special names we give our entries are reserved entry names in Bootstruct and play a cretain roll in your app's flow.
app.get('/', function () {
// ...
});
###Reserved Entry Names:
1. first
2. index
3. get
4. post
5. put
6. delete
7. last
app.all('/foo', function () {
// ...
});
Entries with custom names (e.g. `foo`, `bar`) are parsed as Bootstruct controllers.
app.get('/foo', function () {
// ...
});
app.post('/foo', function () {
// ...
});
app.all('/foo/bar', function () {
// ...
});
#Controllers
------------
A controller is an object that is parsed out from a folder. You can say it's a kind of a representation of a folder.
Folder's entries become controller's sub-controllers and methods.
Bootstruct builds its controller objects when it initializes (on require).
The Root-Controller (RC) is a javascript object that Bootstruct parses out from the `app` folder.
Let's say that when the `app` folder is empty - RC is empty:
```js
RC = {} // empty object
```
and when we create `index` and `get` entries inside it:
```js
RC = {
index: fn
get: fn
}
```
Controllers can have sub-controllers as folders can have sub-folders.
Example structure:
A "stale" folder
----------------
If `bar`, for example, is a "stale" folder (has no sub-folders and no other methods but `index`), you can cut the overhead of a folder and turn it into a file:
Before:
```
.
├── app
├── get.js
└── foo
└── get.js
│ ├── index.js
│ └── foo
│ ├── index.js
│ └── bar <── folder
│ └── index.js
```
In a "let's-say" code:
```js
RC = {
get: fn
sub_controllers: {
foo: {
get: fn
}
}
}
After:
```
>NOTE: `foo` will be parsed as a sub-controller of RC because `foo` is not a reserved entry name.
If you'd log the current filename in both `get.js` files:
```js
module.exports = function (io) {
console.log(__filename);
io.res.end();
};
├── app
│ ├── index.js
│ └── foo
│ ├── index.js
│ └── bar.js <── file
```
and run the following GET requests:
1. /
2. /foo
3. /foo/bar
You should get these logs:
1. .../app/get.js
2. .../app/foo/get.js
3. .../app/foo/get.js
When addressing the root, the RC's `get` will run.
When addressing `foo`, foo's `get` will run.
When addressing a `bar` (which doesn't exist), `get` will run in the last controller found (`foo`).
Scale It Up
-----------
If any method file gets bigger, turn it into a folder:
>__IMPORTANT NOTE__: The last controller found in the URL parts is the only controller that also runs its `index` and verb methods. All of its parent-controllers only run their wrapping methods, `first` and `last`.
Before:
```
├── app
│ ├── index.js
│ ├── get.js
│ ├── post.js <── file
│ ├── put.js
│ └── delete.js
```
After:
```
├── app
│ ├── index.js
│ ├── get.js
│ ├── post <── folder
│ │ ├── index.js
│ │ ├── dependency_1.js
│ │ └── dependency_2.js
│ ├── put.js
│ └── delete.js
```
get, post, put, delete
----------------------
These 4 verb names are reserved for entries that exports functions, like in the get-started example.
These are some of the methods a controller can have.
For code separation you could use folders with these names as well, just make sure to export your function from an `index.js` file within.
Example structure:
'verbs' folder
--------------
If you use all verbs, also having multiple sub-folders can hurt your eyes:
```
.
├── app
├── get.js
├── post
│ ├── module.js
│ └── index.js
├── put.js
└── delete.js
│ ├── about
│ ├── contact
│ ├── delete.js
│ ├── get.js
│ ├── index.js
│ ├── messages
│ ├── post.js
│ ├── profile
│ └── put.js
```
Example file:
```js
module.exports = function (io){
// do your thing...
io.res.end();
};
For the sake of your eyes, you can use a `verbs` folder, just as a namespace to contain the verbs entries:
```
├── app
│ ├── about
│ ├── contact
│ ├── index.js
│ ├── messages
│ ├── profile
│ └── verbs <──
│ ├── get.js
│ ├── post.js
│ ├── put.js
│ └── delete.js
```
>NOTE: `index` and `all` both can exist inside a `verbs` folder.
index
-----
`index` works the same and should also export a function. Controllers run their `index` method before any kind of verb.
Example structure:
In case of duplicates, the entry outside `verbs` will take place:
```
.
├── app
├── index.js
├── get.js
└── post.js
│ ├── all.js <── this will run
│ └── verbs
│ ├── all.js <── this won't
│ ├── get.js
│ └── post.js
```
`index.js` contents:
```js
module.exports = function (io) {
io.res.write('from index \n');
// explained later but might ring a bell
io.next();
};
```
`get.js` contents:
```js
module.exports = function (io) {
io.res.end('from get');
};
```
`post.js` contents:
```js
module.exports = function (io) {
io.res.end('from post');
};
noVerb
------
Consider:
```
On a `GET` request to `'/'` the response would be:
├── app
│ ├── all.js
│ └── get.js
```
from index
from get
Now let's say you get a `POST` request. Naturally, Bootstruct will skip the verb and will only run the `all` method. If you want to handle unsupported verbs requests individually you can use a `noVerb` entry. Controllers run their `noVerb` method on unsupported verbs requests, but **only if they have at least one verb**.
```
On a `POST` request it would be:
├── app
│ ├── all.js
│ ├── get.js
│ └── noVerb.js <── gets called for any type of requests other than `GET`
```
from index
from post
```
├── app
│ ├── all.js
│ ├── *
│ └── noVerb.js <── doesn't get called because no verbs at all
```
This saves you some of the logic you would normally put in you `all` method, regarding `request.method`.
The `index` method runs before any verb does. When you're done in `index` you use `io.next` to make Bootstruct call the next method in line: the verb method.
>NOTE: You can use a `noVerb` entry inside a `verbs` folder as well.
**TIP:** `405` is the server status code for "Method not allowed".
first & last
------------
These, as their names suggest, will be called before and after the `index` and the verb methods as intuitively expected. `first` runs before the `index`.
`last` runs after the verb.
Example structure:
`noVerb` is a special method in Bootstruct because when exists, it's being delegated from the parent folder to all of its sub-folders recursively.
```
.
├── app
├── first.js
├── index.js
├── get.js
└── last.js
│ ├── get.js
│ ├── noVerb.js <── gets called for unsupported requests to both: '/' and '/foo'
│ └── foo
│ ├── get.js
│ └── bar.js
```
Export the same function in all files:
```js
module.exports = function (io) {
console.log(__filename);
io.next();
};
```
On a GET request to '/' you'll get the following logs, in this order:
path/.../app/first.js
path/.../app/index.js
path/.../app/get.js
path/.../app/last.js
first & last
------------
As we saw earlier, the `index` and the verb entries (files or folders) only run for the target-folder.
verbs
-----
For an even better code separation, you could move all of your verbs into a `verbs` folder.
When you'll have sub-controllers in the same containing folder, adding a `verbs` folder would be more easy on the eye. `verbs` is only a namespace for verb files so it should always be a folder.
If you want a folder to do something even if it's not the target-folder but its name was addressed in the request (e.g. `foo` in `foo/bar`), you could use 2 other methods: `first` and `last`. Both are called when a folder is requested whether or not it's the target-folder or should the request be passed on. `first` is called before the target-folder is done with its `index` and the verb method. `last` is called after the target-folder is done.
Example structure:
```
.
├── app
├── verbs
│ ├── index.js
│ ├── get.js
│ ├── post.js
│ ├── put.js
│ └── delete.js
├── foo
│ ├── ...
│ └── ...
├── bar
│ ├── ...
│ └── ...
│ ├── index.js
│ ├── get.js
│ └── foo
│ ├── first.js <──
│ ├── index.js
│ ├── get.js
│ ├── post.js
│ ├── bar
│ │ ├── first.js <──
│ │ ├── index.js
│ │ └── last.js <──
│ └── last.js <──
```
We'll see how these `first` and `last` methods fit in the flow in a sec.

@@ -375,76 +371,82 @@

Reserved Entry Names
--------------------
All of these names are all reserved names for entries (files or folders) in Bootstruct. You name your entries with these names and get a certain behavior.
#methods
********
Excluding `verbs`, entries with **reserved** names being translated into Bootstruct's **known methods** and Bootstruct runs them in a certain order (`first`, `last` etc.).
Bootstruct treats entries with **NON-reserved** names as sub-controllers or **custom methods** of the current controller.
Methods are just functions that handle the `io`. They could be a file or a folder (with an `index.js` file). Anyways, you should export that function because it's gonna be `require`d by a controller.
Methods in Bootstruct are like single-method-controllers, the `index` and they also have no sub-controllers.
>NOTE: if a folder contains an `index.js` file, or in other words: if a controller has an `.index` method, it will be the only method to run the `io`. Reserved entry names means nothing to Bootstruct when an `index.js` is found in the same folder.
Example structure:
```
.
├── app
├── first.js
├── foo
│ └── get.js
├── bar.js
└── last.js
1. first - first thing to run in a folder
2. verbs - just a namespace folder to hold your verb handlers
3. index - called on all HTTP requests ─┐
4. all - called on all HTTP requests │
5. get - called on `GET` HTTP requests │
6. post - called on `POST` HTTP requests ├─ on target folder only
7. put - called on `PUT` HTTP requests │
8. delete - called on `DELETE` HTTP requests │
9. noVerb - called on unsupported verbs requests ─┘
10. last - last thing to run in a folder
```
`bar.js` is a non-reserved name file so it should export a function that handles an io.
_Custom_ named entries (like `foo` or `bar`) become controllers which are URL namespace handlers for requests containing their name (e.g. `/foo` and `/foo/bar`).
Let's say this function also logs the file's path, same as before.
So on request to:
/bar
Reserved entry names are parsed as those controllers' different methods and they are called when needed according to their role listed above. Methods expected to export a single function that accept `io` as a single argument (see [Get started](#get-started) for an example) and they pass this `io` from one to another.
you'll see these logs:
An example of a **pseudo** object that describes `foo` controller with a `bar` sub-controller:
```js
var foo = {
first: require('foo/first'),
index: require('foo/index'),
verbs: {
get: require('foo/get'),
post: require('foo/post'),
},
subControllers: {
bar: {
first: require('foo/bar/first'),
index: require('foo/bar/index'),
verbs: {
get: require('foo/bar/get'),
post: require('foo/bar/post'),
},
subControllers: null,
last: require('foo/bar/last')
}
},
last: require('foo/last')
};
```
Something very similar is generated on Bootstruct init, when your `app` folder is being parsed.
.../app/first.js
.../app/bar.js
.../app/last.js
>NOTE: You might want to use custom method files (e.g. `bar`) for simple stuff like handling a simple "about" page (`about.js`).
io
--
**express/connect** middleware functions accept 2-3 arguments: `request`, `response` and `next`.
Bootstruct methods handles only a single argument: `io`.
`io` is an object that holds the native request/response as properties and a `next` method (and more):
&nbsp; &nbsp; &nbsp; `io.req`
&nbsp; &nbsp; &nbsp; `io.res`
Both by reference, untouched.
`io.next()` is for you to call from within your methods when they are done and the `io` is ready for the next method.
With **express/connect**:
#io
***
`io` is an object that is being created on each request and being passed through your app's routes.
It holds the `request` and the `resonse` objects (`io.req` and `io.res`) and a `.next()` method to pass it to its next checkpoint.
Think of a flow chart that describes your app different possible routes. `io` is the object that walks along those routes.
```js
// NOT Bootstruct!
app.get('/foo', function(req, res, next){
res.send('hello world');
next();
});
```
(request,response)
└──┬──┘
function root (io) {
┌───┘
first(io)
GET (io)
last (io)
} │
With **Bootstruct**:
```js
// file: /app/foo/get.js
module.exports = function (io) {
io.res.send('hello world');
io.next();
};
```

@@ -457,50 +459,37 @@

---------
Bootstruct refers the different URL parts as parameters so it splits the URL (pathname only, not the queryString) by slashes and stores the returned array in `io.params`.
Bootstruct refers the different URL parts as parameters so it splits the URL by slashes (pathname only) and stores the returned array in `io.params`.
Bootstruct also:
* merges repeating slashes
* trims slashes (preceding & trailing)
On request to: `/foo/bar/aaa/bbb`
On request to:
`http://yourdomain.com/aaa/bbb/ccc?flag=1&type=normal`
`io.params` starts as: `['foo', 'bar', 'aaa', 'bbb']`.
it'll be equal to: `['aaa', 'bbb', 'ccc']`.
Starting at your `app` folder, Bootstruct uses `io.params` to check if `app` folder is the target-folder by checking the first item for an existing sub-folder. If the first item (e.g. `foo`) is a sub-folder, `app` is not the target. Next, `foo` removes its name from `io.params` (always the first item) and checks the new first item (e.g. `bar`) for a sub-folder and so on. This way the target-folder (`bar`) is left with the params that are not controllers in your app (e.g. `['aaa', 'bbb']`).
Then Bootstruct checks the first parameter and if you have a controller with that name (e.g. `aaa`), it will handle the request only after removing its name from `io.params`. At this stage `io.params = ['bbb', 'ccc']`. Then the next param is being checked against an existing sub-controller (e.g. is there a folder name `bbb` inside `aaa`).
A Request to: `/foo/bar/john`:
with **express/connect**:
```js
// NOT Bootstruct!
app.get('/foo/bar/:name', function(req, res, next){
console.log(req.params.name); // --> 'john'
next();
});
```
Considering Bootstruct's nature, this is how Bootstruct routes the io through your different folders/controllers structure: It always checks the next item in `io.params` for a matching controller's name.
Every time an io "checks-in" at a controller (with RC as an exception), the controller removes its name from the `io.params` array. It's always the first item.
On `foo` controller check-in, io.params changes: `[foo, bar, baz] ===> [bar, baz]`.
Then the controller (starting with the RC) checks the first item:
* If it has a sub-controller with a matching name (e.g. `foo`), it will pass the io to that sub-controller for another "check-in".
* if there is no sub-controller with that name (e.g. `bar`), what's left in io.params is for you to handle as requests' parameters.
Example structure (same as the last one):
and with **Bootstruct**:
```js
// file: .../app/foo/bar/get.js
module.exports = function (io) {
console.log(io.params[0]); // --> 'john'
io.next();
};
```
.
├── app
├── get.js
└── foo
└── get.js
```
Run these 2 requests:
1. /bar
2. /foo/bar
You should get:
1. path/.../app/get.js
Params: [bar]
2. path/.../app/foo/get.js
Params: [bar]
io other props
--------------
* io._ctrl - (internal) The current handler
* io._ctrl - (internal) The current handling controller
* io._profiles - (internal) io's state in all controllers

@@ -511,25 +500,30 @@

#Summary
********
Bootstruct Flow
---------------
Consider a structure:
```
.
├── app
├── first.js
├── index.js
├── get.js
├── last.js
└── foo
├── first.js
├── index.js
├── get.js
├── last.js
└── bar
├── first.js
├── index.js
├── get.js
└── last.js
│ ├── first.js
│ ├── index.js
│ ├── get.js
│ ├── last.js
│ └── foo
│ ├── first.js
│ ├── index.js
│ ├── get.js
│ ├── last.js
│ └── bar
│ ├── first.js
│ ├── index.js
│ ├── get.js
│ └── last.js
```
There are 3 levels of nested folders as before: `app/foo/bar`.
Each has the following methods:
* `first`
* `index`
* a verb (`get`)
* `last`
>NOTE: This is a full use case. You don't have to use all of the controller's possible methods for every folder.
>NOTE: This is a full use case. You don't have to use all of the possible methods for every folder.

@@ -543,5 +537,8 @@ Consider all of these files contain:

```
All functions logs the filename they're exported from and moves on.
i.e. logs the current file path and moves on to the next method. This will make Bootstruct's flow "visible" to you.
The following are examples of requested URLs (GET requests) and their expected logs given the above structure:
The following are examples of different requests supported by the given structure (GET requests only) and their expected logs.
>NOTE: To make it more easy on the eye, preceding `long/path/to/app` and `.js` extensions were removed from the log.
```

@@ -580,38 +577,5 @@ url: /

A pseudo javascript object that describes your root-controller for this case would be:
```js
RC = {
first: fn,
sub_controllers: {
foo: {
first: fn,
sub_controllers: {
bar: {
first: fn,
sub_controllers: {},
verbs: {
index: fn,
get: fn
},
last : fn
}
}
verbs: {
index: fn,
get: fn
},
last : fn
}
},
verbs: {
index: fn,
get: fn
},
last : fn
}
```
The Shorter Version

@@ -621,8 +585,8 @@ -------------------

1. Check-in: Controllers run their `first` method.
2. Controllers check the next URL part. Is there a matching sub-controller?
&nbsp; &nbsp; &nbsp; If so, the controller passes the `io` to that sub-controller for a check-in. **Back to 1**.
&nbsp; &nbsp; &nbsp; If not, current controller is the target-controller. It will run its `index` method and then its `verb` method.
3. Check-out: Controllers run their `last` method.
4. Controllers pass the `io` back to their parent controller for a check-out. **Back to 3**.
1. **Check-in**: A folder run its `first` method.
2. **Check: IsTarget?**: Bootstruct checks the next URL part. Is there a matching sub-folder in the current one?
&nbsp; &nbsp; &nbsp; **If so**, the folder passes the `io` to that sub-folder for a check-in. **Back to 1**.
&nbsp; &nbsp; &nbsp; **If not**, current folder is the target-folder. It will run its `index` method and its `verb` methods.
3. **Check-out**: The folder run its `last` method.
4. the folder passes the `io` back to its parent folder for a check-out. **Back to 3**.

@@ -632,7 +596,6 @@

Important notes:
----------------
* Bootstruct is CaSe-InSeNsItIvE when it comes to URLs and file names.
* Bootstruct ignores trailing slashes in URLs.
* Bootstruct ignores trailing slashes in URLs and merges repeating slashes.
* Bootstruct ignores entries that their names start with an underscore and doesn't parse them (e.g. `_ignored.js`).

@@ -647,5 +610,44 @@ * You can use the `io` to hold different properties through its cycle.

****
***********************************************************************
Questions, suggestions, bugs, hugs, criticism or kudos are all welcome.
*taitulism(at)gmail(dot)com*

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc