Skip to content

Instantly share code, notes, and snippets.

@soareschen
Last active August 29, 2015 14:02
Show Gist options
  • Save soareschen/db4cce1705e4253b8f7b to your computer and use it in GitHub Desktop.
Save soareschen/db4cce1705e4253b8f7b to your computer and use it in GitHub Desktop.
Quiver DSL Experimentation
/*
* This is an experimentation example of
* the new component definition DSL for Quiver.
* Here we will define a simple hello handler
* with a few middleware to transform the
* input/output.
*
* - The base handler get the name as text from
* standard input stream and output the greeting
* with "hello" in front of the name.
*
* - The html escape filter escapes the name input
* before it reach the main handler.
*
* - The uppercase filter transform the entire
* greeting output to upppercase.
*
* - The compress filter apply gzip compression
* to the final text stream result.
*/
// load a third party component module
var escapeLib = require('escape-component')
// call begin from quiver-define to start
// a new component definition context.
// We use a very short identifier, tentatively
// underscore _, because it will be used a lot
// in component definition.
var _ = require('quiver-define').begin({
// all components defined will have
// their name normalized with given namespace
namespace: 'my-namespace',
// One can import component from other namespace
using: [
// all component names from the escape
// module are imported so that
// unqualified names can be used
escapeLib,
// or individual components from
// other namespace can be imported
// without requiring the module
'compress::gzip compress out'
],
// all components defined will be exported
// to through module.exports or some other object
exportTo: module.exports
})
// define a simple handler component
_('simple handler', {
name: 'greet hello',
// input/output type of the simple handler
type: 'text/text',
// return mode can be async, sync, promise, generator.
// default return mode is async
returnMode: 'sync',
handler: function(args, name) {
return 'hello, <b>' + name + '</b>'
},
// multiple middlewares can be applied
// with last one as innermost middleware
middlewares: [
// resolve to 'escape::html escape in'
'html escape in',
// inline filter of type transform filter
_('transform filter', {
// mode out means the filter pipe the result
// through another handler specified below.
// transform mode can be in, out, inout
mode: 'out',
// middleware used by transform filter
// can also be defined inline or by name
handler: _('simple handler', {
// no name equals anonymous handler
type: 'text/text',
handler: function(args, input, callback) {
callback(null, input.toUpperCase())
}
})
}),
// resolve to 'compress::gzip compress out'
// middleware loaded from config at building time
'gzip compress out'
]
})
/*
* Alternatively a much cleaner way to define
* a component is to define each building blocks
* as separate components and reference them
* by name. However the formal approach can
* sometimes be more expressive and is
* quite handy in rapid prototyping.
*/
_('simple handler', {
name: 'greet hello',
type: 'text/text',
handler: function(args, name, callback) {
callback(null, 'hello, <b>' + name + '</b>')
},
middlewares: [
'html escape in',
// resolve to 'my-namespace::to uppercase'
// defined elsewhere
'uppercase out',
'gzip compress out'
]
})
// No need to manually export the components
// at the end, as they are exported automatically.
/*
* The first example was an attempt to
* get the cleanest DSL syntax possible.
* Along the deprecation of the `with` keyword
* in JavaScript, it is hard to activate
* more than one identifier to be used as
* DSL keyword. As a result all DSL keywords
* have to be become prefixed with a common
* identifier and the syntax becomes uglier.
*
* The previous example do some straight forward
* "syntactic sugar" to make DSL keywords look
* slightly better. The original style:
*
* _('keyword action', { ... })
*
* is semantically equivalent to:
*
* _['keyword action']({ ... })
*
* or using camel case convention:
*
* _.keywordAction({ ... })
*
* This example present the alternative syntax
* to allow easier comparison of which DSL
* approach is better.
*/
var escapeLib = require('escape-component')
var _ = require('quiver-define').begin({
namespace: 'my-namespace',
using: [
escapeLib,
'compress::gzip compress out'
],
exportTo: module.exports
})
_.simpleHandler({
name: 'greet hello',
type: 'text/text',
returnMode: 'sync',
handler: function(args, name) {
return 'hello, <b>' + name + '</b>'
},
middlewares: [
'html escape in',
_.transformFilter({
mode: 'out',
handler: _.simpleHandler({
type: 'text/text',
handler: function(args, input, callback) {
callback(null, input.toUpperCase())
}
})
}),
'gzip compress out'
]
})
/*
* This is another DSL attempt that use
* the more conventional chained method style
* to define DSL keywords. However in my
* opinion the approach is less stylish and
* fragile.
*
* Using the chained method as constructor,
* it is hard to know where the construction
* arguments are finished and therefore the
* workaround is to add an end() method to
* finish the construction.
*
* The chained method is also hard to use
* for nested construction, as demonstrated
* in this example with anonymous inline
* handler. It result in additional close
* bracket hell that look similar to Lisp.
*/
var escapeLib = require('escape-component')
var _ = require('quiver-define')
.begin()
.namespace('my-namespace')
.using(escapeLib)
.using('compress::gzip compress out')
.exportTo(module.exports)
.end()
_.simpleHandler('greet hello')
.type('text/text')
.returnMode('sync')
.handler(function(args, name) {
return 'hello, <b>' + name + '</b>'
})
.middleware('html escape in')
.middleware(_.transformFilter()
.mode('out')
.handler(_.simpleHandler()
.type('text/text')
.handler(function(args, input, callback) {
callback(null, input.toUpperCase())
})))
.middleware('gzip compress out')
.end()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment