Skip to content
Browse files

initial commit

  • Loading branch information...
0 parents commit 7b32ecf84828b5f0ebc14260972c488de820770c @dimsmol committed Feb 16, 2012
2 .gitignore
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules/
2 .npmignore
@@ -0,0 +1,2 @@
+.DS_Store
+.git*
0 Makefile
No changes.
0 Readme.md
No changes.
0 TODO.md
No changes.
82 example/examples.js
@@ -0,0 +1,82 @@
+// old atempts, left for some ideas
+
+
+User = typedef({
+ id:
+ name:
+ username:
+});
+
+// warn about unused args
+// warn about 404s
+
+// try non-declarative way!
+// start from single url definition
+
+// result (or message) format:
+{
+ meta: {
+ httpResponseCode: Number
+ },
+ error: Object, // usually an exception thrown
+ data: Object,
+ objects: {
+ Object: Object, // id-to-object map of referenced objects
+ }
+}
+// any object can have _type property, indicating type
+// api method can return:
+new HintedResult({
+ data: Object,
+ objects: {
+ objectTypeString: {
+ objectId: object
+ }
+ },
+ objectIds: {
+ objectTypeString: [objectsId,]
+ }
+}) // to hint upper middleware to provide referenced objects along with response
+var unwrap = function(possibleHinted) {
+ if (possibleHinted instanceof HintedResult)
+ {
+ return possibleHinted.data;
+ }
+
+ return possibleHinted;
+}
+
+// possible errors list
+// multiple queries in one sharing common dict of referenced objects
+
+{
+ path: 'user', // list of users
+ req: [auth],
+ opt: {read: [range]},
+ read: [User.username.opt, {_returns: List(User._short)}],
+ update: [User.except('password'), {_returns: User.id}],
+
+ code: require('./api/user').UserList
+}
+
+{
+ path: 'user/:id', // particular user
+ args: {id: User.id},
+ req: [auth],
+ read: {_returns: User.except('password')},
+ update: User.except('email', 'password'),
+ del: {},
+ props: [User.username, User.name],
+
+ code: require('./api/user').User
+}
+
+{
+ path: 'user/:id/email', // user's email
+ args: {id: User.id},
+ req: [auth, secure],
+ call: [ctx.authInfo, args.id,
+ User.email, User.password],
+
+ code: require('./api/user').User.updateEmail,
+}
64 lib/app.js
@@ -0,0 +1,64 @@
+var abstractMethod = require('./tools/abstract_method').abstractMethod;
+
+var Lowlevel = require('./lowlevel');
+var Units = require('./units').Units;
+
+
+var App = function () {
+ this.config = null;
+ this.lowlevel = null;
+ this.units = null;
+ this.contract = null;
+};
+
+App.prototype.defineConfig = abstractMethod;
+App.prototype.applyContract = abstractMethod;
+
+App.prototype.start = function () {
+ this.lowlevel.start();
+};
+
+App.prototype.init = function () {
+ this.defineConfig();
+ this.prepareLowlevel();
+ this.prepareUnits();
+ this.applyContract();
+};
+
+App.prototype.prepareLowlevel = function () {
+ this.defineLowlevel();
+ this.initLowlevel();
+};
+
+App.prototype.defineLowlevel = function () {
+ this.lowlevel = new Lowlevel(this.config);
+};
+
+App.prototype.initLowlevel = function () {
+ this.lowlevel.init();
+};
+
+App.prototype.prepareUnits = function () {
+ this.defineUnits();
+ this.addUnits();
+ this.initUnits();
+};
+
+App.prototype.defineUnits = function () {
+ this.units = new Units();
+};
+
+App.prototype.addUnits = function () {
+};
+
+App.prototype.initUnits = function () {
+ this.units.init();
+};
+
+App.prototype.setContract = function (contract) {
+ this.contract = contract;
+ this.lowlevel.setHandler(contract);
+};
+
+
+module.exports = App;
45 lib/contract.js
@@ -0,0 +1,45 @@
+var Resource = require('./resource').Resource;
+
+
+var Contract = function (path) {
+ this.path = path;
+ this.items = [];
+};
+
+Contract.prototype.add = function (item) {
+ if (typeof item === 'object')
+ {
+ item = new Resource(item);
+ }
+
+ this.items.push(item);
+};
+
+Contract.prototype.handle = function (ctx, next) {
+ var handlerChain = this.resolve(ctx);
+ if (handlerChain != null)
+ {
+ handlerChain.execute(ctx, next);
+ }
+ else
+ {
+ next();
+ }
+};
+
+Contract.prototype.resolve = function (ctx) {
+ for (var k in this.items)
+ {
+ var item = this.items[k];
+ var result = item.resolve(ctx);
+ if (result != null)
+ {
+ return result;
+ }
+ }
+
+ return null;
+};
+
+
+module.exports = Contract;
16 lib/ctx.js
@@ -0,0 +1,16 @@
+var Ctx = function () {
+ this.web = null;
+ this.socket = null;
+
+ this.path = null;
+ this.method = null;
+
+ this.isProcessed = false;
+};
+
+Ctx.prototype.processed = function () {
+ this.isProcessed = true;
+};
+
+
+module.exports = Ctx;
80 lib/handlers.js
@@ -0,0 +1,80 @@
+var socketSupport = function (ctx, next) {
+ var message = JSON.parse(ctx.socket.message);
+ var meta = message.meta;
+
+ ctx.meta = meta;
+ ctx.path = meta.path;
+ ctx.method = meta.method;
+
+
+ result = {
+ ctx: ctx,
+ data: message.data
+ };
+
+ next(null, ctx);
+};
+
+var socketStickConnection = function () {
+ var connectionUserData = connection.userData;
+
+ if (connectionUserData.userId == null)
+ {
+ if (userId == null)
+ {
+ throw Error('Invalid auth data');
+ }
+ else
+ {
+ if (connectionUserData.endPointId != null)
+ {
+ throw Error('Connection improperly initialized');
+ }
+
+ connectionUserData.userId = userId;
+ connectionUserData.endPointId = endPointId;
+
+ this.connections.onIdentificationProvided(connection);
+ }
+ }
+ else if (connectionUserData.userId != userId || connectionUserData.endPointId != endPointId)
+ {
+ throw Error('Invalid auth data');
+ }
+};
+
+var auth = function (ctx, next) {
+ var authToken = ctx.meta.auth;
+ if (authToken != null)
+ {
+ userId = this.authenticate(authToken);
+ }
+
+ ctx.auth = {
+ userId: userId
+ };
+
+ next(null, ctx);
+};
+
+var clientData = function (ctx, next) {
+ var meta = ctx.meta;
+
+ ctx.clientInfo = {
+ endPointId: meta.endPointId,
+ serial: meta.serial,
+ userId: ctx.auth.userId
+ };
+
+ return next();
+};
+
+var data = function (ctx, next) {
+
+ return next();
+};
+
+var ret = function (ctx, next) {
+
+ return next();
+};
13 lib/handlers/handler.js
@@ -0,0 +1,13 @@
+var Handler = function () {
+};
+
+Handler.prototype.handle = function (ctx, next) {
+ next(ctx);
+};
+
+Handler.prototype.setup = function (resource) {
+ return true;
+};
+
+
+module.exports = Handler;
25 lib/handlers/impl.js
@@ -0,0 +1,25 @@
+var inherits = require('util').inherits;
+
+var Handler = require('./handler');
+
+
+var Impl = function (f) {
+ this.impl = f;
+};
+inherits(Impl, Handler);
+
+Impl.prototype.setup = function (chain) {
+ chain.impl = this.impl;
+ return false;
+};
+
+
+var impl = function (f) {
+ return new Impl(f);
+};
+
+
+module.exports = {
+ Impl: Impl,
+ impl: impl
+};
33 lib/handlers/ret.js
@@ -0,0 +1,33 @@
+var inherits = require('util').inherits;
+
+var Handler = require('./handler');
+
+
+var Ret = function () {
+};
+inherits(Ret, Handler);
+
+Ret.prototype.handle = function (ctx, next) {
+ if (ctx.currentHandlerChain.impl == null)
+ {
+ throw new Error('No impl defined');
+ }
+
+ var handleResult = function (error, result) {
+ ctx.web.res.send(result);
+ ctx.processed();
+ next(ctx);
+ };
+
+ ctx.currentHandlerChain.impl(ctx, handleResult);
+};
+
+var ret = function () {
+ return new Ret();
+};
+
+
+module.exports = {
+ Ret: Ret,
+ ret: ret
+};
45 lib/lowlevel.js
@@ -0,0 +1,45 @@
+var Lowlevel = function (config) {
+ this.config = config;
+
+ this.webServer = null;
+ this.web = null;
+
+ this.socketServer = null;
+ this.socket = null;
+};
+
+Lowlevel.prototype.init = function () {
+ var express = this.config.lib.express;
+ var sockjs = this.config.lib.sockjs;
+
+ var settings = this.config.settings;
+
+ this.webServer = express.createServer();
+ this.web = this.config.web.configure(this.webServer, settings);
+
+ if (settings.socket && !settings.socket.disable)
+ {
+ this.socketServer = sockjs.createServer({
+ prefix: settings.getSocketPrefix()
+ });
+ this.socket = this.config.socket.configure(this.socketServer);
+
+ this.socketServer.installHandlers(this.webServer);
+ }
+};
+
+Lowlevel.prototype.setHandler = function (handler) {
+ this.web.setHandler(handler);
+ if (this.socket != null)
+ {
+ this.socket.setHandler(handler);
+ }
+};
+
+Lowlevel.prototype.start = function () {
+ var listenSettings = this.config.settings.listen;
+ this.webServer.listen(listenSettings.port, listenSettings.address);
+};
+
+
+module.exports = Lowlevel;
137 lib/resource.js
@@ -0,0 +1,137 @@
+var Handler = require('./handlers/handler');
+
+
+var HandlerChain = function (resource) {
+ this.resource = resource;
+ this.handlers = [];
+
+ this.impl = null;
+};
+
+HandlerChain.prototype.add = function (handler) {
+ if (handler instanceof Handler)
+ {
+ if (handler.setup(this))
+ {
+ this.handlers.push(handler);
+ }
+ }
+};
+
+HandlerChain.prototype.beforeExecute = function (ctx) {
+ ctx.currentHandlerChain = this;
+};
+
+HandlerChain.prototype.afterExecute = function (ctx) {
+ ctx.currentHandlerChain = null;
+};
+
+HandlerChain.prototype.execute = function (ctx, next) {
+ this.beforeExecute(ctx);
+
+ var self = this;
+
+ var handlers = this.handlers;
+ var i = 0;
+ var nextInChain = function (ctx) {
+ if (i >= handlers.length || ctx.isProcessed)
+ {
+ self.afterExecute(ctx);
+ ctx.currentHandlerChain = null;
+
+ if (!ctx.isProcessed)
+ {
+ next();
+ }
+
+ return;
+ }
+
+ var f = handlers[i++];
+ if (f instanceof Handler)
+ {
+ f.handle(ctx, nextInChain);
+ }
+ else
+ {
+ f(ctx, nextInChain);
+ }
+ };
+
+ nextInChain(ctx);
+};
+
+
+var MethodMapper = function () {
+};
+
+MethodMapper.prototype.getLogicalMethod = function (ctx) {
+ return {
+ GET: 'get',
+ POST: 'update',
+ DELETE: 'del'
+ }[ctx.method];
+};
+
+
+var Resource = function (info) {
+ this.path = info.path;
+
+ if (info.call != null)
+ {
+ if (info.get != null || info.update != null || info.del != null)
+ {
+ throw new BadResourceError('Call-resource must not include other type handlers');
+ }
+ }
+
+ this.handlers = {
+ call: this.processHandlers(info.call),
+ get: this.processHandlers(info.get),
+ update: this.processHandlers(info.update),
+ del: this.processHandlers(info.del)
+ };
+
+ this.methodMapper = new MethodMapper();
+};
+
+Resource.prototype.processHandlers = function (handlers) {
+ if (handlers == null)
+ {
+ return null;
+ }
+
+ var chain = new HandlerChain(this);
+
+ for (var k in handlers)
+ {
+ chain.add(handlers[k]);
+ }
+
+ return chain;
+};
+
+Resource.prototype.resolve = function (ctx) {
+ var result = null;
+ var logicalMethod = this.methodMapper.getLogicalMethod(ctx);
+
+ if (this.path == ctx.path)
+ {
+ for (var method in this.handlers)
+ {
+ if (method == logicalMethod)
+ {
+ result = this.handlers[method];
+ break;
+ }
+ }
+ }
+
+ return result;
+};
+
+
+module.exports = {
+ HandlerChain: HandlerChain,
+ Resource: Resource
+};
72 lib/socket/connections.js
@@ -0,0 +1,72 @@
+var ConnectionsDict = require('./connections_dict');
+
+
+var Connections = function() {
+ this.connections = {};
+
+ this.userConnections = new ConnectionsDict();
+ this.endPointConnections = new ConnectionsDict();
+};
+
+Connections.prototype.difference = function(connections, connectionsToNotInclude) {
+ var result = {};
+ for (var id in connections)
+ {
+ if (!(id in connectionsToNotInclude))
+ {
+ result[id] = connections[id];
+ }
+ }
+
+ return result;
+};
+
+Connections.prototype.getEndPointKey = function(userId, endPointId) {
+ return [userId, endPointId].join('\n');
+};
+
+Connections.prototype.add = function(connection) {
+ var id = connection.id;
+ this.connections[id] = connection;
+};
+
+Connections.prototype.remove = function(connection) {
+ var id = connection.id;
+ delete this.connections[id];
+
+ var userId = connection.userData.userId;
+
+ if (userId != null)
+ {
+ this.userConnections.remove(userId, connection);
+
+ var endPointId = connection.userData.endPointId;
+ if (endPointId != null)
+ {
+ var endPointKey = this.getEndPointKey(userId, endPointId);
+ this.endPointConnections.remove(endPointKey, connection);
+ }
+ }
+};
+
+Connections.prototype.onIdentificationProvided = function(connection) {
+ var userId = connection.userData.userId;
+ var endPointId = connection.userData.endPointId;
+
+ this.userConnections.add(userId, connection);
+
+ var endPointKey = this.getEndPointKey(userId, endPointId);
+ this.endPointConnections.add(endPointKey, connection);
+};
+
+Connections.prototype.getUserConnections = function(userId) {
+ return this.userConnections.get(userId);
+};
+
+Connections.prototype.getEndPointConnections = function(userId, endPointId) {
+ var endPointKey = this.getEndPointKey(userId, endPointId);
+ return this.endPointConnections.get(endPointKey);
+};
+
+
+module.exports = Connections;
22 lib/socket/connections_dict.js
@@ -0,0 +1,22 @@
+var inherits = require('util').inherits;
+
+var DoubleDict = require('../tools/double_dict');
+
+
+var ConnectionsDict = function() {
+ ConnectionsDict.super_.call(this);
+};
+inherits(ConnectionsDict, DoubleDict);
+
+ConnectionsDict.prototype.add = function(key, connection) {
+ var subKey = connection.id;
+ ConnectionsDict.super_.prototype.add.call(this, key, subKey, connection);
+};
+
+ConnectionsDict.prototype.remove = function(key, connection) {
+ var subKey = connection.id;
+ ConnectionsDict.super_.prototype.remove.call(this, key, subKey);
+};
+
+
+module.exports = ConnectionsDict;
57 lib/socket/mechanics.js
@@ -0,0 +1,57 @@
+var Connections = require('./connections');
+var Transport = require('./transport');
+
+
+var Mechanics = function() {
+ this.connections = null;
+ this.transport = null;
+ this.handler = null;
+
+ this.init();
+};
+
+Mechanics.prototype.init = function() {
+ this.defineConnections();
+ this.defineTransport();
+};
+
+Mechanics.prototype.defineConnections = function() {
+ this.connections = new Connections();
+};
+
+Mechanics.prototype.defineTransport = function() {
+ this.transport = new Transport();
+};
+
+Mechanics.prototype.setHandler = function (handler) {
+ this.handler = handler;
+};
+
+Mechanics.prototype.onConnect = function(connection) {
+ console.log('Connected:', connection.id);
+ connection.userData = {};
+ this.connections.add(connection);
+};
+
+Mechanics.prototype.onDisconnect = function(connection) {
+ console.log('Disconnected:', connection.id);
+ this.connections.remove(connection);
+};
+
+Mechanics.prototype.onMessage = function(connection, message) {
+ console.log('\nConnection <'+connection.id+'> message:\n', message);
+
+/* var ctx = new Ctx();
+
+ ctx.path =
+ ctx.method =
+
+ ctx.socket = {
+ };
+
+ var result = this.handler.handle(ctx, next);
+ this.transport.sendResult(connection, ctx, result);*/
+};
+
+
+module.exports = Mechanics;
47 lib/socket/transport.js
@@ -0,0 +1,47 @@
+var Transport = function() {
+};
+
+Transport.prototype.encode = function(data, meta) {
+ if (meta == null)
+ {
+ meta = {};
+ }
+ if (data == null)
+ {
+ data = null;
+ }
+
+ return JSON.stringify({
+ meta: meta,
+ data: data
+ });
+};
+
+Transport.prototype.send = function(recipientConnections, data) {
+ if (recipientConnections == null || recipientConnections.length == 0)
+ {
+ return;
+ }
+
+ var message = this.encode(data);
+
+ for (var k in recipientConnections)
+ {
+ var connection = recipientConnections[k];
+ connection.write(message);
+ }
+};
+
+Transport.prototype.sendResult = function(connection, ctx, result) {
+ var message = this.encode(result, {
+ requestSerial: ctx.clientInfo.serial
+ });
+ connection.write(message);
+};
+
+Transport.prototype.authenticate = function(authToken) {
+ return authToken; // TODO replace with normal auth mechanism!!!
+};
+
+
+module.exports = Transport;
17 lib/tools/abstract_method.js
@@ -0,0 +1,17 @@
+var inherits = require('util').inherits;
+
+
+var AbstractMethodCallError = function () {
+};
+inherits(AbstractMethodCallError, Error);
+
+
+var abstractMethod = function () {
+ throw new AbstractMethodCallError();
+};
+
+
+module.exports = {
+ AbstractMethodCallError: AbstractMethodCallError,
+ abstractMethod: abstractMethod
+};
38 lib/tools/double_dict.js
@@ -0,0 +1,38 @@
+var DoubleDict = function() {
+ this.dict = {};
+};
+
+DoubleDict.prototype.get = function(key, createIfNotExists) {
+ var result = this.dict[key];
+
+ if (result == null)
+ {
+ result = {};
+ if (createIfNotExists)
+ {
+ this.dict[key] = result;
+ }
+ }
+
+ return result;
+};
+
+DoubleDict.prototype.add = function(key, subKey, object) {
+ this.get(key, true)[subKey] = object;
+};
+
+DoubleDict.prototype.remove = function(key, subKey) {
+ var objects = this.get(key);
+
+ if (objects.length > 0)
+ {
+ delete objects[subKey];
+ if (objects.length == 0)
+ {
+ delete this.objects[key];
+ }
+ }
+};
+
+
+module.exports = DoubleDict;
81 lib/units.js
@@ -0,0 +1,81 @@
+var inherits = require('util').inherits;
+
+var abstractMethod = require('./tools/abstract_method').abstractMethod;
+
+
+var DuplicateUnitError = function (key) {
+ this.key = key;
+};
+inherits(DuplicateUnitError, Error);
+
+
+var UnitRequiredError = function (key) {
+ this.key = key;
+};
+inherits(UnitRequiredError, Error);
+
+
+var Units = function () {
+ this.units = {};
+ this.needInit = {};
+};
+
+Units.prototype.addReady = function (key, unit) {
+ this.add(key, unit, true);
+};
+
+Units.prototype.add = function (key, unit, skipInit) {
+ if (key in this.units)
+ {
+ throw new DuplicateUnitError(key);
+ }
+
+ this.units[key] = unit;
+
+ if (!skipInit)
+ {
+ this.needInit[key] = true;
+ }
+};
+
+Units.prototype.get = function (key) {
+ return this.units[key];
+};
+
+Units.prototype.require = function (key) {
+ var unit = this.get(key);
+ if (unit == null)
+ {
+ throw new UnitRequiredError(key);
+ }
+ return unit;
+};
+
+Units.prototype.init = function () {
+ for (var key in this.needInit)
+ {
+ if (this.needInit[key])
+ {
+ this.units[key].init();
+ }
+ }
+};
+
+
+var Unit = function (units) {
+ this.units = units;
+};
+
+Unit.prototype.init = abstractMethod;
+
+Unit.prototype.require = function (key) {
+ return this.units.require(key);
+};
+
+
+module.exports = {
+ DuplicateUnitError: DuplicateUnitError,
+ UnitRequiredError: UnitRequiredError,
+ Units: Units,
+ Unit: Unit
+};
40 lib/web/mechanics.js
@@ -0,0 +1,40 @@
+var Ctx = require('../ctx');
+
+
+var Mechanics = function () {
+ this.handler = null;
+ this.middleware = this.createMiddleware();
+};
+
+Mechanics.prototype.setHandler = function (handler) {
+ this.handler = handler;
+};
+
+Mechanics.prototype.middlewareHandle = function (req, res, next) {
+ if (this.handler == null)
+ {
+ throw new Error('No handler set');
+ }
+
+ var ctx = new Ctx();
+
+ ctx.path = req.path;
+ ctx.method = req.method;
+
+ ctx.web = {
+ req: req,
+ res: res
+ };
+
+ this.handler.handle(ctx, next);
+};
+
+Mechanics.prototype.createMiddleware = function () {
+ var self = this;
+ return function (req, res, next) {
+ self.middlewareHandle(req, res, next);
+ };
+};
+
+
+module.exports = Mechanics;
26 package.json
@@ -0,0 +1,26 @@
+{
+ "name": "apis",
+ "version": "0.0.1",
+ "description": "Library for creation web and websocket restful APIs",
+ "keywords": ["api", "rest", "web", "websocket"],
+ "author": {
+ "name": "Dmitry Smolin",
+ "email": "dimsmol@gmail.com"
+ },
+ "preferGlobal": false,
+ "private": false,
+ "engines": {
+ "node": "~0.6.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/dimsmol/apis"
+ },
+ "directories": {
+ "lib": "./lib",
+ "bin": "./bin",
+ "doc": "./doc",
+ "example": "./example"
+ },
+ "main": "./lib/index"
+}

0 comments on commit 7b32ecf

Please sign in to comment.
Something went wrong with that request. Please try again.