NIWA
NIWA Intertwined Web Applications
NIWA is an application server for simple NodeJs apps that can communicate with each other.
Usage
- maual
- git clone or download
npm update
ornpm install
- npm run start
- cli
npm install -g niwa niwa <work directory> <port>
- apilet starter=; // function(niwaWorkDir=__dirname,overridePort=null);
Work directory
If no directory is provided the install directory is used. Config, Logs and application data is stored in this directory.
structure
.
├─ config
├─ server.json
├─ apps.json
├─ users.json
├─ permissions.json
└─ <appName>.config.json
├─ apps
└─ ...
├─ logs
└─ ...
└─ <work> // applications should persist files under a directory with its own name
You can use an empty directory as a work directory.
In order to start without providing a port you need a config/server.json
.
config/server.json
{
"port":8081,
"logLevel":"INFO", // default logger level
"allowCommunication":true, // default for communications
"autoStart":false // default to start app on server start
}
defaults
logLevel:"INFO"allowCommunication:trueautoStart:false
Apps
An Application is a directory full of static assets with few exceptions.
Each app is deployed in a separate NodeJs process (using Morgas nodeWorker
).
To deploy an app you can place it in a directory under apps
or define it in the config/apps.json
(or both).
config/apps.json
{
"<name>":{
"path":"<relative from apps or absolute>",
//overwrite defaults
"autoStart":boolean,
"logLevel":"<level>", // bunyan log levels
"communication": boolean || {
"<name>":boolean || ["<context>"]
}
}
}
styles
*.less
files are automatically compiled when they are requested with a url containing /css/
instead of /less/
.
e.g.: app/some/css/path/styles.less
=>app/some/less/path/styles.less //compiled
rest services
Defining a rest service means simply putting a <name>.js
file in the app/rest
directory in your application.
Those files are loaded when the application gets deployed.
Such a file can export a function or a object structure of functions. Each context of the requested url is matched to a key in that structure until a function is found.
The first context matches to the filename (without the extension). The remainder of the path as an array is also provided as the second argument of the function.
module{ return "hello world";}
A function may return any serializable (JSON.stringify
) value or an instance of ServiceResult
.
context notes
- running as a Morgas worker
process.cwd() // application directory
niwa/util
is added as resource folder- ServiceResult
- configManager
- dependencyManager
- mimeTypes (irrelevant)
- global
worker
extensions
util/ServiceResult
A small class for rest responses.
let ServiceResult=µ;return status:403data:"Please log in first";
util/configManager
Creates a rest api for a persistent config.
The config is saved under <work directory>/config/<app context>.json
with file rotation of 3.
Parameter Morgas.Config
or a config description (see Morgas.Config.parse
)
returns
api=function(param) // rest api
api.ready //Promise that is resolves to Morgas.Conig instance
api.addChangeListener=function(path,fn)
api.removeChangeListener=function(pathOfFn,fn)
api.notify=function(path, oldValue, newValue) // triggers changeListeners
api.save=function() // returns Promise
Example service
moduleexports=µ name:"string" range: type:number default:3 min:0 max:10 step:05 check: type:"boolean" list: type:"array" model:"string" ;
dependencyManager
Creates a rest api to serve js files with all needed dependencies.
It uses morgas/lib/dependencyParser
to parse files
Parameter Array of file or directory paths and a base path from which navigation begins
returns
api=function(param) // rest api
api.addResource=function(moduleRegister,moduleDependencies,directory,name=null)
Morgas and MorgasGui resouces are already registered
Example service
moduleexports=µ"js""js";
worker
extensions
global worker returns trigger function(eventName,data) for server send eventsworker.module=function(moduleName,methodName,args=[]) // call moduleworker.getCommunicationList=function(name) // returns a Promise resolving to an array of contexts{ return worker;};worker // communicate with (call methods from) other applications worker.serverStarted=function() // overwrite for callback when server is started and communication is possible
package.json
Having a package.json
for an application is optional. But it is needed for communication and additional configuration.
application name
The name of the application is defined in niwa.name
or name
of its package.json
setup script
If you define a niwa.setup
the script gets called (required) when starting the application.
It is possible to export a Promise for asynchronous setups.
The script is called before any rest service is loaded.
dependencies
You can define application dependencies in the niwa.dependencies
array.
Currently this is only a treated as a hint and is therefore optional.
Communication
Applications with a name can communicate with each other. It has to be allowed in the apps.json.
Example
app1
worker{ ifcontext=="someContext" return Promise; return "value";};
app2
worker
Modules
Modules are extensions that can be called from applications.
session
Session object
Guest users have always an empty string as name
methods
- create() => Session object
- get(token) => Session object
- getOrCreate(token) => Session object
- delete(token) => boolean
- refresh(token) => boolean // mostly internal
user
methods
- logIn(sessionToken,username,password="") => void (reject value is a ServiceResult)
- logOut(sessionToken) => void (reject value is a ServiceResult)
- register(sessionToken,username,password) => void (permission
"registerUser"
needed) - delete(sessionToken,username) => void (permission
"deleteUser"
needed) - list(sessionToken) => void (permission
"readPermissions"
needed)
permissions
methods
- check(sessionToken,toCheck=[]) => void (reject value is a ServiceResult)
- checkAll(sessionToken,toCheck=[]) => Boolean[] (reject value is a ServiceResult)
- getAll(sessionToken) => void (permission
"readPermissions"
needed) - addUser(sessionToken,name) => void (permission
"addUser"
needed) - setUser(sessionToken,name,roles,permissions) => void (permission
"setUser"
needed) - deleteUser(sessionToken,username) => void (permission
"deleteUser"
needed) - addRole(sessionToken,name) => void (permission
"addRole"
needed) - setRole(sessionToken,name,roles,permissions) => void (permission
"setRole"
needed) - deleteRole(sessionToken,name) => void (permission
"deleteRole"
needed)
Server send events
EventSources can be accessed via <app context>/event/<event name>
.
Ordinary get requests are also supported.
usage
- app
let notify=worker.eventSource("myEvent",()=>"init value");
notify("update",value);
- client
let source=new EventSource("event/myEvent");
source.addEventListener("init",event=>alert(event.data));
source.addEventListener("update",event=>alert(event.data));
Special paths
host:port/morgas/*
serves Morgas files
host:port/morgas/gui/*
serves MorgasGui files
host:port/morgas/gui/css/theme/<theme name>
serves MorgasGui compiled theme style
host:port/morgas/gui/css/<module name>[/<theme name>]
serves MorgasGui compiled module styles