@leansdk/leanrc
Version:
LeanRC is a MVC framework for creating graceful applications
675 lines (633 loc) • 21.4 kB
text/coffeescript
# This file is part of LeanRC.
#
# LeanRC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# LeanRC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with LeanRC. If not, see <https://www.gnu.org/licenses/>.
# example in use
###
```coffee
Test.context.use Basis::SessionsUtil.middleware
class Test::ApplicationRouter extends Module::Router
@inheritProtected()
@module Test
@map ->
@namespace 'version', module: '', prefix: ':v', ->
@resource 'invitations', except: 'delete', ->
@post 'confirm', at: 'collection'
@member ->
@post 'sendInvite'
@resource 'descendants', only: 'list', ->
@get 'count', at: 'collection'
module.exports = Test::ApplicationRouter.initialize()
```
###
module.exports = (Module)->
{
AnyT, PointerT
FuncG, MaybeG, InterfaceG, EnumG, ListG, UnionG, SubsetG, SampleG
RouterInterface
ConfigurableMixin
Class
Utils: { _, inflect }
} = Module::
class Router extends Module::Proxy
@inheritProtected()
@include ConfigurableMixin
@implements RouterInterface
@module Module
ipsPath = PointerT @protected path: MaybeG(String),
default: '/'
ipsName = PointerT @protected name: MaybeG(String),
default: ''
ipsModule = PointerT @protected module: MaybeG String
iplOnly = PointerT @protected only: MaybeG UnionG String, ListG String
iplVia = PointerT @protected via: MaybeG UnionG String, ListG String
iplExcept = PointerT @protected except: MaybeG UnionG String, ListG String
ipoAbove = PointerT @protected above: MaybeG Object
ipsAt = PointerT @protected at: MaybeG EnumG 'collection', 'member'
ipsResource = PointerT @protected resource: MaybeG String
ipsTag = PointerT @protected tag: MaybeG String
ipsTemplates = PointerT @protected templates: MaybeG String
ipsParam = PointerT @protected param: MaybeG String
iplRouters = PointerT @protected routers: MaybeG ListG SubsetG Router
iplPathes = PointerT @protected pathes: MaybeG ListG InterfaceG {
method: String
path: String
resource: String
action: String
tag: String
template: String
keyName: MaybeG String
entityName: String
recordName: MaybeG String
}
iplResources = PointerT @protected resources: MaybeG ListG SampleG Router
iplRoutes = PointerT @protected routes: MaybeG ListG InterfaceG {
method: String
path: String
resource: String
action: String
tag: String
template: String
keyName: MaybeG String
entityName: String
recordName: MaybeG String
}
@public path: MaybeG(String),
get: -> @[ipsPath]
@public name: MaybeG(String),
get: -> @[ipsResource] ? @[ipsName]
@public above: MaybeG(Object),
get: -> @[ipoAbove]
@public tag: MaybeG(String),
get: -> @[ipsTag]
@public templates: MaybeG(String),
get: -> @[ipsTemplates]
@public param: MaybeG(String),
get: -> @[ipsParam]
@public defaultEntityName: FuncG([], String),
default: ->
[..., vsEntityName] = @[ipsName]
.replace /\/$/, ''
.split '/'
inflect.singularize vsEntityName
@public @static map: FuncG([MaybeG Function]),
default: (lambda)->
lambda ?= ->
@public map: Function,
default: lambda
return
@public map: Function,
default: -> return
@public root: FuncG([InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
}]),
default: ({to, at, resource, action})->
return
@public defineMethod: FuncG([
MaybeG ListG InterfaceG {
method: String
path: String
resource: String
action: String
tag: String
template: String
keyName: MaybeG String
entityName: String
recordName: MaybeG String
}
String
String
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (container, method, path, {to, at, resource, action, tag:asTag, template, keyName, entityName, recordName}={})->
unless path?
throw new Error 'path is required'
path = path.replace /^[/]/, ''
if to?
unless /[#]/.test to
throw new Error '`to` must be in format `<resource>#<action>`'
[resource, action] = to.split '#'
if not resource? and (vsResource = @[ipsResource]) isnt ''
resource = vsResource
if not resource? and (vsName = @[ipsName]) isnt ''
resource = vsName
unless resource?
throw new Error 'options `to` or `resource` must be defined'
unless action?
action = path
unless /[/]$/.test resource
resource += '/'
keyName ?= @[ipsParam]?.replace /^\:/, ''
entityName ?= @defaultEntityName()
unless _.isString(recordName) or _.isNull(recordName)
recordName = @defaultEntityName()
vsParentTag = if @[ipsTag]? and @[ipsTag] isnt ''
@[ipsTag]
else
''
vsTag = if asTag? and asTag isnt ''
"/#{asTag}"
else
''
tag = "#{vsParentTag}#{vsTag}"
path = switch at ? @[ipsAt]
when 'member'
"#{@[ipsPath]}:#{inflect.singularize inflect.underscore resource.replace(/[/]/g, '_').replace /[_]$/g, ''}/#{path}"
when 'collection'
"#{@[ipsPath]}#{path}"
else
"#{@[ipsPath]}#{path}"
template ?= resource + action
container.push {method, path, resource, action, tag, template, keyName, entityName, recordName}
return
@public get: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'get', asPath, aoOpts
return
@public post: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'post', asPath, aoOpts
return
@public put: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'put', asPath, aoOpts
return
@public delete: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'delete', asPath, aoOpts
return
@public head: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'head', asPath, aoOpts
return
@public options: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'options', asPath, aoOpts
return
@public patch: FuncG([
String,
MaybeG InterfaceG {
to: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
action: MaybeG String
tag: MaybeG String
template: MaybeG String
keyName: MaybeG String
entityName: MaybeG String
recordName: MaybeG String
}
]),
default: (asPath, aoOpts)->
# @[iplPathes] ?= []
@defineMethod @[iplPathes], 'patch', asPath, aoOpts
return
@public resource: FuncG([
String
MaybeG UnionG(InterfaceG({
path: MaybeG String
module: MaybeG String
only: MaybeG UnionG String, ListG String
via: MaybeG UnionG String, ListG String
except: MaybeG UnionG String, ListG String
tag: MaybeG String
templates: MaybeG String
param: MaybeG String
at: MaybeG EnumG 'collection', 'member'
resource: MaybeG String
above: MaybeG Object
}), Function)
MaybeG Function
]),
default: (asName, aoOpts = null, lambda = null)->
vcModule = @Module
if _.isFunction aoOpts
lambda = aoOpts
aoOpts = {}
aoOpts = {} unless aoOpts?
{
path, module:vsModule
only, via, except
tag:asTag, templates:alTemplates, param:asParam
at, resource:asResource, above
} = aoOpts
path = path?.replace /^[/]/, ''
vsPath = if path? and path isnt ''
"#{path}/"
else if path? and path is ''
''
else
"#{asName}/"
vsFullPath = switch at ? @[ipsAt]
when 'member'
[..., previously, empty] = @[ipsPath].split '/'
"#{@[ipsPath]}:#{inflect.singularize inflect.underscore previously}/#{vsPath}"
when 'collection'
"#{@[ipsPath]}#{vsPath}"
else
"#{@[ipsPath]}#{vsPath}"
vsParentName = @[ipsName]
vsParentTemplates = if @[ipsTemplates]? and @[ipsTemplates] isnt ''
"#{@[ipsTemplates]}/"
else
''
vsParentTag = if @[ipsTag]? and @[ipsTag] isnt ''
@[ipsTag]
else
''
vsName = if vsModule? and vsModule isnt ''
"#{vsModule}/"
else if vsModule? and vsModule is ''
''
else
"#{asName}/"
vsTemplates = if alTemplates? and alTemplates isnt ''
alTemplates
else if alTemplates? and alTemplates is ''
''
else
if vsModule? and vsModule isnt ''
vsModule
else if vsModule? and vsModule is ''
''
else
asName
vsTag = if asTag? and asTag isnt ''
"/#{asTag}"
else
''
vsParam = if asParam? and asParam isnt ''
asParam
else
':' + inflect.singularize inflect.underscore (asResource ? "#{vsParentName}#{vsName}").replace(/[/]/g, '_').replace /[_]$/g, ''
# @[iplRouters] ?= []
class ResourceRouter extends Router
@inheritProtected()
@module vcModule
@protected path: String,
default: vsFullPath
@protected name: String,
default: "#{vsParentName}#{vsName}"
@protected module: String,
default: vsModule
@protected only: MaybeG(UnionG String, ListG String),
default: only
@protected via: MaybeG(UnionG String, ListG String),
default: via
@protected except: MaybeG(UnionG String, ListG String),
default: except
@protected above: MaybeG(Object),
default: above
@protected tag: String,
default: "#{vsParentTag}#{vsTag}"
@protected templates: String,
default: "#{vsParentTemplates}#{vsTemplates}".replace /[\/][\/]/g, '/'
@protected param: String,
default: vsParam
@protected resource: MaybeG(String),
default: asResource
@map lambda
ResourceRouter.constructor = Class
@[iplRouters].push ResourceRouter
return
@public namespace: FuncG([
MaybeG String
UnionG(InterfaceG({
module: MaybeG String
prefix: MaybeG String
tag: MaybeG String
templates: MaybeG String
at: MaybeG EnumG 'collection', 'member'
above: MaybeG Object
}), Function)
MaybeG Function
]),
default: (asName, aoOpts = null, lambda = null)->
vcModule = @Module
if aoOpts?.constructor is Function
lambda = aoOpts
aoOpts = {}
aoOpts = {} unless aoOpts?
{
module:vsModule, prefix
tag:asTag, templates:alTemplates
at, above
} = aoOpts
vsParentPath = @[ipsPath]
vsPath = if prefix? and prefix isnt ''
"#{prefix}/"
else if prefix? and prefix is ''
''
else
"#{asName}/"
vsParentName = @[ipsName]
vsParentTemplates = if @[ipsTemplates]? and @[ipsTemplates] isnt ''
"#{@[ipsTemplates]}/"
else
''
vsParentTag = if @[ipsTag]? and @[ipsTag] isnt ''
@[ipsTag]
else
''
vsName = if vsModule? and vsModule isnt ''
"#{vsModule}/"
else if vsModule? and vsModule is ''
''
else
"#{asName}/"
vsTemplates = if alTemplates? and alTemplates isnt ''
alTemplates
else if alTemplates? and alTemplates is ''
''
else
if vsModule? and vsModule isnt ''
vsModule
else if vsModule? and vsModule is ''
''
else
asName
vsTag = if asTag? and asTag isnt ''
"/#{asTag}"
else
''
# @[iplRouters] ?= []
class NamespaceRouter extends Router
@inheritProtected()
@module vcModule
@protected path: String,
default: "#{vsParentPath}#{vsPath}"
@protected name: String,
default: "#{vsParentName}#{vsName}"
@protected except: MaybeG(UnionG String, ListG String),
default: ['all']
@protected tag: String,
default: "#{vsParentTag}#{vsTag}"
@protected templates: String,
default: "#{vsParentTemplates}#{vsTemplates}".replace /[\/][\/]/g, '/'
@protected at: MaybeG(EnumG 'collection', 'member'),
default: at
@protected above: MaybeG(Object),
default: above
@map lambda
NamespaceRouter.constructor = Class
@[iplRouters].push NamespaceRouter
return
@public member: FuncG(Function),
default: (lambda)->
@namespace null, module: '', prefix: '', templates: '', at: 'member', lambda
return
@public collection: FuncG(Function),
default: (lambda)->
@namespace null, module: '', prefix: '', templates: '', at: 'collection', lambda
return
@public resources: ListG(SampleG Router),
get: -> return @[iplResources]
@public routes: ListG(InterfaceG {
method: String
path: String
resource: String
action: String
tag: String
template: String
keyName: MaybeG String
entityName: String
recordName: MaybeG String
}),
get: ->
if @[iplRoutes]? and @[iplRoutes].length > 0
return @[iplRoutes]
else
vlRoutes = []
vlRoutes = vlRoutes.concat @[iplPathes] ? []
vlResources = []
@[iplRouters]?.forEach (ResourceRouter)=>
resourceRouter = ResourceRouter.new()
vlResources.push resourceRouter
vlRoutes = vlRoutes.concat resourceRouter.routes ? []
vlResources = vlResources.concat resourceRouter.resources ? []
@[iplRoutes] = vlRoutes
@[iplResources] = vlResources
return @[iplRoutes]
constructor: (args...)->
super args...
@init args...
@map()
if _.isString @[iplOnly]
@[iplOnly] = [@[iplOnly]]
if _.isString @[iplVia]
@[iplVia] = [@[iplVia]]
if _.isString @[iplExcept]
@[iplExcept] = [@[iplExcept]]
voMethods =
list: 'get'
detail: 'get'
create: 'post'
update: 'put'
delete: 'delete'
voPaths =
list: ''
detail: null
create: ''
update: null
delete: null
# @[iplPathes] ?= []
if @[ipsName]? and @[ipsName] isnt ''
vsKeyName = @[ipsParam]?.replace /^\:/, ''
vsEntityName = @[ipoAbove]?.entityName
vsEntityName ?= @defaultEntityName()
vsRecordName = @[ipoAbove]?.recordName
if _.isNil(vsRecordName) and not _.isNull vsRecordName
vsRecordName = @defaultEntityName()
if @[iplOnly]?
@[iplOnly].forEach (asAction)=>
vsPath = voPaths[asAction]
vsPath ?= @[ipsParam]
@defineMethod @[iplPathes], voMethods[asAction], vsPath,
action: asAction
resource: @[ipsResource] ? @[ipsName]
template: @[ipsTemplates] + '/' + asAction
keyName: vsKeyName
entityName: vsEntityName
recordName: vsRecordName
else if @[iplExcept]?
for own asAction, asMethod of voMethods
do (asAction, asMethod)=>
if not @[iplExcept].includes('all') and not @[iplExcept].includes asAction
vsPath = voPaths[asAction]
vsPath ?= @[ipsParam]
@defineMethod @[iplPathes], asMethod, vsPath,
action: asAction
resource: @[ipsResource] ? @[ipsName]
template: @[ipsTemplates] + '/' + asAction
keyName: vsKeyName
entityName: vsEntityName
recordName: vsRecordName
else if @[iplVia]?
@[iplVia].forEach (asCustomAction)=>
vsPath = voPaths[asCustomAction]
vsPath ?= @[ipsParam]
if asCustomAction is 'all'
for own asAction, asMethod of voMethods
do (asAction, asMethod)=>
@defineMethod @[iplPathes], asMethod, vsPath,
action: asAction
resource: @[ipsResource] ? @[ipsName]
template: @[ipsTemplates] + '/' + asAction
keyName: vsKeyName
entityName: vsEntityName
recordName: vsRecordName
else
@defineMethod @[iplPathes], voMethods[asCustomAction], vsPath,
action: asCustomAction
resource: @[ipsResource] ? @[ipsName]
template: @[ipsTemplates] + '/' + asAction
keyName: vsKeyName
entityName: vsEntityName
recordName: vsRecordName
else
for own asAction, asMethod of voMethods
do (asAction, asMethod)=>
vsPath = voPaths[asAction]
vsPath ?= @[ipsParam]
@defineMethod @[iplPathes], asMethod, vsPath,
action: asAction
resource: @[ipsResource] ? @[ipsName]
template: @[ipsTemplates] + '/' + asAction
keyName: vsKeyName
entityName: vsEntityName
recordName: vsRecordName
return
@public init: FuncG([MaybeG(String), MaybeG AnyT]),
default: (args...)->
@super args...
@[iplRouters] = []
@[iplPathes] = []
return
@initialize()