Skip to content

Commit

Permalink
feat(koa): koa integration to provide middleware for signal network a…
Browse files Browse the repository at this point in the history
…s an http interface

affects: patois.api, @tao.js/koa

expose a signal network over http using koa as the app server
  • Loading branch information
eudaimos committed Aug 11, 2019
1 parent 311f738 commit 079cdb5
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 11 deletions.
1 change: 1 addition & 0 deletions examples/patois.api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@koa/cors": "^2.2.2",
"@tao.js/core": "file:../../packages/tao",
"@tao.js/koa": "file:../../packages/koa-tao",
"@tao.js/socket.io": "file:../../packages/tao-socket-io",
"@tao.js/utils": "file:../../packages/tao-utils",
"ioredis": "^4.0.0",
Expand Down
50 changes: 44 additions & 6 deletions examples/patois.api/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as redis from './data/redis';
import * as spaces from './lib/models/spaces';
import * as spaceCache from './lib/models/space-cache';
import { toASCII } from 'punycode';
// import taoKoa from '@tao.js/koa';
import taoKoa from '@tao.js/koa';

const { PORT } = process.env;

Expand All @@ -25,7 +25,7 @@ const connectingToRedis = redis.init();
const app = new Koa();
app.use(cors());
app.use(bodyParser());
app.use(noTrailingSlash());
// app.use(noTrailingSlash());

const restRouter = new Router();

Expand Down Expand Up @@ -106,6 +106,8 @@ TAO.addAsyncHandler({ t: 'Space', a: 'Enter', o: 'Portal' }, (tao, data) => {
);
});

const taoMiddleware = taoKoa(TAO, {});

// taoKoa.path({
// incoming: { t: 'Space', a: 'Find', o: 'Portal' },
// outgoing: { t: 'Space', a: ['List', 'Enter', 'Fail'], o: 'Portal' },
Expand All @@ -122,6 +124,28 @@ TAO.addAsyncHandler({ t: 'Space', a: 'Enter', o: 'Portal' }, (tao, data) => {
// { t: 'Space', a: 'Enter' }
// ],
// });
// // this would be a REST-based middleware
// taoMiddleware.addResponseHandler({ t: 'Space', a: ['List', 'Enter', 'Fail'], o: 'Portal' }, (tao, data, ctx, next) => {
// ctx.body = {
// tao,
// data,
// };
// next();
// });

// other version would be plumbed
// taoMiddleware.addResponseHandler({ t: 'Space', a: ['List', 'Enter', 'Fail'], o: 'Portal' }, (tao, data) => {
taoMiddleware.addResponseHandler(
{
t: 'Space',
a: ['Fetch', 'Retrieve', 'List', 'Enter', 'Fail'],
o: 'Portal'
},
(tao, data) => {
// return new AppCtx('rain', 'make', 'it');
console.log('responded with:', { tao, data });
}
);

// For client Interaction
TAO.addInlineHandler(
Expand All @@ -139,13 +163,18 @@ TAO.addInlineHandler(
// Space: data
// }
// );
// return new AppCtx('Space', Array.isArray(data) ? 'Fetch' : 'Retrieve', tao.o, { Space: data });
return new AppCtx(
'Space',
Array.isArray(data) ? 'List' : 'Enter',
Array.isArray(data) ? 'Fetch' : 'Retrieve',
tao.o,
{ Space: data }
);
// return new AppCtx(
// 'Space',
// Array.isArray(data) ? 'List' : 'Enter',
// tao.o,
// { Space: data }
// );
} catch (apiErr) {
console.error('Failed to retrieve Space:', apiErr);
return new AppCtx('Space', 'Fail', tao.o, {
Expand Down Expand Up @@ -174,8 +203,8 @@ TAO.addInterceptHandler(
}
// use the cache hit to go to the next AppCtx in the protocol chain
console.log('CACHE HIT on:', Space._id);
// return new AppCtx('Space', 'Retrieve', 'Portal', Space);
return new AppCtx('Space', 'Enter', 'Portal', Space);
return new AppCtx('Space', 'Retrieve', 'Portal', Space);
// return new AppCtx('Space', 'Enter', 'Portal', Space);
}
);

Expand All @@ -190,6 +219,13 @@ const retrieveHandler = (tao, data) =>
new AppCtx('Space', 'Enter', tao.o, data);
const fetchHandler = (tao, data) => new AppCtx('Space', 'List', tao.o, data);

TAO.addInlineHandler(
{ t: 'Space', a: 'Retrieve', o: 'Portal' },
retrieveHandler
);

TAO.addInlineHandler({ t: 'Space', a: 'Fetch', o: 'Portal' }, fetchHandler);

function initClientTAO(clientTAO, id) {
const clientHandler = (tao, data) => {
console.log(`clientTAO[${id}].handling:`, tao);
Expand Down Expand Up @@ -325,6 +361,8 @@ async function saveSpaceHandler(tao, { Space }) {
});
}

app.use(taoMiddleware.middleware());

const server = http.createServer(app.callback());
const io = IO(server);
wireTaoJsToSocketIO(TAO, io, {
Expand Down
6 changes: 5 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/koa-tao/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib
dist
bundles
5 changes: 5 additions & 0 deletions packages/koa-tao/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
src
test
.babelrc
package-lock.json
29 changes: 25 additions & 4 deletions packages/koa-tao/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,35 @@
"name": "@tao.js/koa",
"version": "0.8.1",
"description": "middleware to hook tao into koa server",
"private": true,
"main": "index.js",
"homepage": "https://tao.js.org/",
"repository": {
"type": "git",
"url": "https://github.com/zzyzxlab/tao.js.git",
"directory": "packages/koa-tao"
},
"main": "lib",
"module": "dist",
"files": [
"lib",
"dist"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build:clean": "rimraf dist lib",
"build:package": "rollup --config",
"build": "npm run build:clean && npm run build:package"
},
"author": "eudaimos",
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@tao.js/core": "^0.8.0"
},
"devDependencies": {
"@tao.js/core": "file:../tao"
"@tao.js/utils": "file:../tao-utils"
},
"dependencies": {
"cartesian": "^1.0.1"
}
}
40 changes: 40 additions & 0 deletions packages/koa-tao/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import external from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';

export default [
// CommonJS (for Node) and ES module (for bundlers) build.
// (We could have three entries in the configuration array
// instead of two, but it's quicker to generate multiple
// builds from a single configuration where possible, using
// an array for the `output` option, where we can specify
// `file` and `format` for each target)
{
input: {
index: 'src/index.js'
},
output: [
{
dir: pkg.main,
format: 'cjs',
sourcemap: true
},
{
dir: pkg.module,
format: 'esm',
sourcemap: true
}
],
plugins: [
external(),
babel({
runtimeHelpers: true,
exclude: ['node_modules/**']
}),
resolve(),
commonjs()
]
}
];
15 changes: 15 additions & 0 deletions packages/koa-tao/src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const noop = () => {};

export function normalizeAC({ t, term, a, action, o, orient }) {
return {
term: term || t,
action: action || a,
orient: orient || o
};
}

export const cleanInput = ({ term, action, orient }) => {
const incoming = { term, action, orient };
Object.keys(incoming).forEach(k => incoming[k] == null && delete incoming[k]);
return incoming;
};
148 changes: 148 additions & 0 deletions packages/koa-tao/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { AppCtx } from '@tao.js/core';
import cartesian from 'cartesian';
import { Channel, Transponder } from '@tao.js/utils';
import { noop, normalizeAC, cleanInput } from './helpers';

// const noop = () => {};

const DEFAULT_ROOT = 'tao';
const ROUTE_POSITION = 1;
const ROUTE_RESPONSES = 'responses';
const ROUTE_CONTEXT = 'context';

async function getBodyData(ctx, bodyProp) {
let data = null;
if (bodyProp && ctx.request[bodyProp]) {
if (typeof ctx.request[bodyProp] === 'function') {
data = await ctx.request[bodyProp]();
} else {
data = ctx.request[bodyProp];
}
}
if (!data && ctx.request.json) {
if (typeof ctx.request.json === 'function') {
data = await ctx.request.json();
} else {
data = ctx.request.json;
}
}
if (!data && ctx.request.body) {
if (typeof ctx.request.body === 'function') {
data = await ctx.request.body();
} else {
data = ctx.request.body;
}
}
return data;
}

function handleResponsesRequest(responseTrigrams, ctx, next) {
// const out = Array.from(responseTrigrams.values());
ctx.body = {
// out,
responses: Array.from(responseTrigrams.values())
.filter(r => r.count > 0)
.map(r => r.ac.unwrapCtx())
};
return next();
}

async function handleContext(transponder, bodyProp, ctx, next) {
const { tao, data } = await getBodyData(ctx, bodyProp);
try {
const ac = await transponder.setCtx(tao, data);
ctx.body = {
tao: ac.unwrapCtx(),
data: ac.data
};
} catch (err) {
console.error('Error:', err);
ctx.status = 404;
}
return next();
}

export default function taoMiddleware(TAO, opt = {}) {
const responseTrigrams = new Map();
const bodyProp = opt.json;
const rootPath = opt.root || DEFAULT_ROOT;
const rootTest = new RegExp(`/${rootPath}/([^/]+)/?(.*)?`, 'i');
const transponder = new Transponder(TAO, opt.name, 3000);
transponder.addInlineHandler({}, (tao, data) =>
console.log('taoMiddleware::hitting the first with:', tao, data)
);

return {
middleware() {
return async (ctx, next) => {
const path = ctx.path.match(rootTest);
if (!path) {
return next();
}
const route = path[ROUTE_POSITION];
if (!route) {
ctx.status = 404;
return next();
}
// change if routes start accepting additional path parameters
if (path[ROUTE_POSITION + 1]) {
ctx.status = 400;
ctx.body = { message: 'extra path parameters are not supported' };
return next();
}
switch (route) {
case ROUTE_RESPONSES:
if (ctx.method !== 'GET') {
ctx.status = 405;
return next();
}
return handleResponsesRequest(responseTrigrams, ctx, next);
case ROUTE_CONTEXT:
if (ctx.method !== 'POST') {
ctx.status = 405;
return next();
}
return handleContext(transponder, bodyProp, ctx, next);
default:
ctx.status = 404;
return next();
}
};
},
addResponseHandler({ t, term, a, action, o, orient }, handler = noop) {
const trigrams = cleanInput(
normalizeAC({ t, term, a, action, o, orient })
);
const permutations = cartesian(trigrams);
for (let trigram of permutations) {
console.log('@tao.js/koa::addResponseHandler::trigram:', trigram);
transponder.addInlineHandler(trigram, handler);
let ac = new AppCtx(trigram.term, trigram.action, trigram.orient);
if (!responseTrigrams.has(ac.key)) {
responseTrigrams.set(ac.key, { ac, count: 0 });
}
responseTrigrams.get(ac.key).count += 1;
console.log(
'@tao.js/koa::addResponseHandler::responseTrigrams:',
responseTrigrams
);
}
},
removeResponseHandler({ t, term, a, action, o, orient }, handler = noop) {
const trigrams = cleanInput(
normalizeAC({ t, term, a, action, o, orient })
);
const permutations = cartesian(trigrams);
for (let trigram of permutations) {
transponder.removeInlineHandler(trigram, handler);
let ac = new AppCtx(trigram.term, trigram.action, trigram.orient);
let count = !responseTrigrams.has(ac.key)
? 0
: responseTrigrams.get(ac.key).count;
if (count) {
responseTrigrams.get(ac.key).count -= 1;
}
}
}
};
}

0 comments on commit 079cdb5

Please sign in to comment.