flow: default service, gateway, emitters

This commit is contained in:
cha0s 2019-03-05 23:08:28 -06:00
parent dc9ffcbd0e
commit 028518ef1e
13 changed files with 148 additions and 98 deletions

View File

@ -1,3 +1,5 @@
npm_config_registry=https://npm.i12e.cha0s.io npm_config_registry=https://npm.i12e.cha0s.io
NODE_PATH=/var/node/dist/node_modules NODE_PATH=/var/node/dist/node_modules
GATEWAY_EMITTERS=@truss/emitters/http

1
services/default/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,2 @@
module.exports = (serviceMap) => ({
});

View File

@ -0,0 +1,3 @@
module.exports = (config) => {
};

View File

@ -0,0 +1,2 @@
module.exports = () => ({
});

15
services/default/index.js Normal file
View File

@ -0,0 +1,15 @@
const {createDispatcher} = require('@truss/comm');
const dispatcher = createDispatcher();
dispatcher.lookupActions(require('./actions'));
dispatcher.lookupHooks(require('./hooks'));
dispatcher.connect();
if (module.hot) {
module.hot.accept('./actions', () => {
dispatcher.lookupActions(require('./actions'));
});
module.hot.accept('./hooks', () => {
dispatcher.lookupHooks(require('./hooks'));
});
}

View File

@ -0,0 +1,17 @@
{
"name": "default",
"version": "1.0.0",
"description": "Default service. Clone or customize!",
"main": "index.js",
"private": true,
"scripts": {
"build": "node -e '' -r '@truss/webpack/task/build'",
"default": "yarn run dev",
"dev": "node -e '' -r '@truss/webpack/task/scaffold'"
},
"license": "MIT",
"dependencies": {
"@truss/comm": "1.x",
"@truss/webpack": "1.x"
}
}

View File

@ -2,18 +2,18 @@ const {sendActionToService} = require('@truss/comm');
const debug = require('debug')('truss:gateway:actions'); const debug = require('debug')('truss:gateway:actions');
module.exports = (serviceMap) => ({ module.exports = (hooks) => ({
'truss/hook-services': ({payload: {hook, args}}) => { '@truss/hook-services': ({payload: {hook, args}}) => {
if (!(hook in serviceMap.hooks)) { return []; } if (!(hook in hooks)) { return []; }
return serviceMap.hooks[hook]; return hooks[hook];
}, },
'truss/invoke': ({payload: {hook, args}}) => { '@truss/invoke': ({payload: {hook, args}}) => {
if (!(hook in serviceMap.hooks)) { return {}; } if (!(hook in hooks)) { return {}; }
return invokeHook(serviceMap.hooks[hook], hook, args); return invokeHook(hooks[hook], hook, args);
}, },
'truss/invoke-flat': ({payload: {hook, args}}) => { '@truss/invoke-flat': ({payload: {hook, args}}) => {
if (!(hook in serviceMap.hooks)) { return []; } if (!(hook in hooks)) { return []; }
return invokeHookFlat(serviceMap.hooks[hook], hook, args); return invokeHookFlat(hooks[hook], hook, args);
}, },
}); });
@ -23,7 +23,7 @@ function invokeHookFlat(services, hook, args) {
} }
function invokeHookFlatInternal(services, hook, args) { function invokeHookFlatInternal(services, hook, args) {
const action = {type: 'truss/hook', payload: {hook, args}}; const action = {type: '@truss/hook', payload: {hook, args}};
return Promise.all(services.map((service) => { return Promise.all(services.map((service) => {
return sendActionToService(action, service); return sendActionToService(action, service);
})); }));

View File

@ -2,4 +2,3 @@ module.exports = (config) => {
const gatewayPort = process.env.GATEWAY_PORT || 8000; const gatewayPort = process.env.GATEWAY_PORT || 8000;
config.ports = [`${gatewayPort}:8000`]; config.ports = [`${gatewayPort}:8000`];
}; };

View File

@ -1,82 +1,40 @@
// Core.
const http = require('http'); const http = require('http');
// 3rd party.
const debug = require('debug')('truss:gateway'); const debug = require('debug')('truss:gateway');
// 2nd party.
const {createDispatcher, sendActionToService} = require('@truss/comm'); const {createDispatcher} = require('@truss/comm');
// 1st party.
let listener = require('./listener').default; const {loadServiceMap} = require('./service-map');
// Main.
// --- (async () => {
try {
if (!process.env.SERVICES) { debug(`loading service map...`);
console.error('$SERVICES must be defined!'); const serviceMap = await loadServiceMap();
process.exit(1); const {executors, hooks, listeners} = serviceMap;
}
const services = process.env.SERVICES.split(',');
// get all schemas
const schemas$ = services.map((service) => {
return sendActionToService({type: 'truss/schema'}, service);
});
// build service map
const reduceServiceMap$ = Promise.all(schemas$).then((schemas) => {
return schemas.reduce((serviceMap, schema, i) => {
const service = services[i];
for (const type of (schema.executors || [])) {
if (serviceMap.executors[type]) {
throw new RangeError(`
Only one executor may be specified per action type! "${service}"
tried to register an executor but "${serviceMap.executors[type]}"
already registered one for "${type}".
`.replace(/\s+/g, ' ').trim());
}
serviceMap.executors[type] = service;
}
for (const type of (schema.listeners || [])) {
serviceMap.listeners[type] = serviceMap.listeners[type] || [];
serviceMap.listeners[type].push(service);
}
for (const hook of (schema.hooks || [])) {
serviceMap.hooks[hook] = serviceMap.hooks[hook] || [];
serviceMap.hooks[hook].push(service);
}
return serviceMap;
}, {
executors: {},
hooks: {},
listeners: {},
});
});
// hey, listen
const httpServer = http.createServer();
reduceServiceMap$.then((serviceMap) => {
debug(`service map: ${JSON.stringify(serviceMap, null, ' ')}`); debug(`service map: ${JSON.stringify(serviceMap, null, ' ')}`);
const dispatcher = createDispatcher(); const dispatcher = createDispatcher();
dispatcher.setArgs(serviceMap); dispatcher.setArgs(hooks);
dispatcher.lookupActions(require('./actions')); dispatcher.lookupActions(require('./actions'));
if (module.hot) { if (module.hot) {
module.hot.accept('./actions', () => { module.hot.accept('./actions', () => {
dispatcher.lookupActions(require('./actions')); dispatcher.lookupActions(require('./actions'));
}); });
} }
const emitterPaths = process.env.GATEWAY_EMITTERS.split(':');
const emitters = [];
for (const emitterPath of emitterPaths) {
const Class = __non_webpack_require__(emitterPath);
emitters.push(new Class(executors, listeners));
}
const server = dispatcher.connect(); const server = dispatcher.connect();
server.on('listening', () => { server.on('listening', () => {
const port = process.env.GATEWAY_PORT || 8000; for (const emitter of emitters) {
httpServer.listen(port); emitter.listen();
httpServer.on('listening', () => { }
debug(`HTTP listening on port ${port}...`);
})
httpServer.on('request', (req, res) => {
listener(serviceMap, req, res);
}); });
}); }
}).catch(console.error); catch (error) {
console.error(error);
if (module.hot) { }
module.hot.accept('./listener', () => { })();
listener = require('./listener').default;
});
}

View File

@ -7,25 +7,32 @@ const debug = require('debug')('truss:gateway:listener');
const parser = bodyParser.json(); const parser = bodyParser.json();
export default function(serviceMap, req, res) { parser(req, res, () => { export default function(serviceMap, req, res) { parser(req, res, () => {
debug(`HTTP ${req.method} ${req.url}`); const method = req.method.toLowerCase();
debug(`HTTP ${method} ${req.url}`);
// map to action // map to action
const action = {type: undefined, payload: {url: req.url}}; const action = {
switch (req.method) { type: undefined,
case 'POST': payload: {
headers: req.headers,
method: method,
url: req.url,
}
};
switch (method) {
case 'post':
// truss action // truss action
if (req.url.startsWith('/truss/action/')) { if (req.url.startsWith('/truss/action/')) {
action.type = req.url.substr('/truss/action/'.length); action.type = req.url.substr('/truss/action/'.length);
} }
// truss/http-post // @truss/http-post
else { else {
action.type = 'truss/http-post'; action.type = '@truss/http-post';
} }
action.payload.body = req.body; action.payload.body = req.body;
break; break;
default: default:
// truss/http-(get|delete|...) // @truss/http-(get|delete|...)
action.type = `truss/http-${req.method.toLowerCase()}`; action.type = `@truss/http-${method}`;
action.payload.headers = req.headers;
break; break;
} }
// listeners... // listeners...

View File

@ -12,6 +12,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@truss/comm": "1.x", "@truss/comm": "1.x",
"@truss/emitters": "1.x",
"@truss/webpack": "1.x", "@truss/webpack": "1.x",
"body-parser": "1.18.3", "body-parser": "1.18.3",
"debug": "3.1.0" "debug": "3.1.0"

View File

@ -0,0 +1,43 @@
const {sendActionToService} = require('@truss/comm');
if (!process.env.SERVICES) {
console.error('$SERVICES must be defined!');
process.exit(1);
}
const services = process.env.SERVICES.split(',');
export function loadServiceMap() {
// get all schemas
const schemas$ = services.map((service) => {
return sendActionToService({type: '@truss/schema'}, service);
});
// build service map
return Promise.all(schemas$).then((schemas) => {
return schemas.reduce((serviceMap, schema, i) => {
const service = services[i];
for (const type of (schema.executors || [])) {
if (serviceMap.executors[type]) {
throw new RangeError(`
Only one executor may be specified per action type! "${service}"
tried to register an executor but "${serviceMap.executors[type]}"
already registered one for "${type}".
`.replace(/\s+/g, ' ').trim());
}
serviceMap.executors[type] = service;
}
for (const type of (schema.listeners || [])) {
serviceMap.listeners[type] = serviceMap.listeners[type] || [];
serviceMap.listeners[type].push(service);
}
for (const hook of (schema.hooks || [])) {
serviceMap.hooks[hook] = serviceMap.hooks[hook] || [];
serviceMap.hooks[hook].push(service);
}
return serviceMap;
}, {
executors: {},
hooks: {},
listeners: {},
});
});
}