From a75ad58d2f8295ed21cca527c7039f0a16973a85 Mon Sep 17 00:00:00 2001 From: OKNoah Date: Sun, 31 Dec 2017 05:55:00 -0800 Subject: [PATCH] Publish --- README.md | 54 ++++++++++++++++++++++++++++++++--------- examples/game-server.js | 1 + package.json | 28 +++++++++++++++++---- src/Component.js | 12 +++++++++ src/Component.test.js | 6 ++--- src/reduxConnect.js | 9 +++---- src/updater.js | 11 ++++----- 7 files changed, 88 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fc74736..74cbd30 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,35 @@ +> ⚠️ NOTE: Future releases will be pre-release and have `alpha` and `beta` tags. NPM/yarn will probably not install these unless you specify. + # Final -This is a very experimental proof of concept for a sort of MC framework. It's meant to work a little like React, but for handling web requests. One of the posibilities would be very precise handling of how to respond to requests, possibly allowing redux store-like functionality. +This is a very experimental proof of concept for a server framework. It takes ideas from ES6+ and React to see if there's a more enjoyable, versatile way to create APIs. Key components are: + +#### Classes + +Each endpoint has a class, which extends the component class. The main method of the class is `respond` which decides what is returned upon each request. + +#### Lifecycle + +Each component has a lifecycle, with names similar to the lifecycle components of React components. + +#### Decorators + +Decorators are used to add actions and in-memory state to components. The example decorator that's included is for ArangoDB. A `reduxConnect` decorator is also currently part of the library. + +#### WebSockets + +WebSocket-functionality is built-in (at present). Depending on how you wish to use the library, you can have components that repeat their lifecycle whenever state is changed. This is most useful for WebSocket servers becuase there may be multiple responses per connection. An `http` request will fire `response` onece, but a WebSocket connection might do it many times. ## Usage +See the [`/examples`](examples) and files matching the patter [`**/*.test.js`](src) for more usage and explanation. Be warned the API will change a lot. + ```js -import Final from './src/index' +import Final, { reduxConnect } from './src/index' +import { bindActionCreators } from 'redux' import { findDecorator } from './test/ArangoDecorator' import { middleware, store } from './example/middleware' +import { moveUp } from './redux/modules/player' /* The `findDecorator` adds a few funtions to the class, like `this.findOne`. @@ -16,29 +38,37 @@ import { middleware, store } from './example/middleware' // this decorator will verify collection or create new one collection: 'Post' }) +@reduxConnect( + (state) => ({ + players: state + }), + (dispatch) => ({ + moveUp + }) +) class Post extends Final.Component { /* The path decides what requests will match this component and the params. */ path = '/post/:post?' - constructor () { - super() - } - /* - The respond function returns whatever the response will be. Notice the params and `this.findOne` are available. - */ async respond () { console.log('this.props.params', this.props.params) - const output = await this.findOne({"body": "Updated!"}) - return output + console.log('this.actions.moveUp', this.action.moveUp) + const output = await this.actions.findOne({"body": "Updated!"}) + return { + data: { + players: this.props.players, + output + } + } } } Final.createServer({ components: [Post], port: 3001, - middleware, // optional, see `examples/middleware-usage.js` - store // optional, see `examples/middleware-usage.js` + store, // optional, see `examples/game-server.js` + // middleware, /* Removed. Purpose needs to be decided. */ }) ``` diff --git a/examples/game-server.js b/examples/game-server.js index ff4362e..3f0ab5e 100644 --- a/examples/game-server.js +++ b/examples/game-server.js @@ -104,6 +104,7 @@ class User extends Component { async messageReceived (msg) { if (['moveUp', 'moveDown', 'moveLeft', 'moveRight', 'bug'].includes(msg)) { + console.log('this.actions', this.actions) this.actions[msg](this.props.params.player) } else { throw "That's not a function" diff --git a/package.json b/package.json index 63bbb71..12339fd 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,34 @@ { - "name": "final", - "version": "1.0.0-alpha.1", - "main": "index.js", - "author": "Noah", + "name": "final-server", + "description": "A component-based server framework for ES6+, with a React-like lifecycle. Experimental.", + "version": "0.9.1", + "main": "dist/index.js", + "author": "Noah Gray (https://github.com/oknoah)", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/OKNoah/final.git" + }, "scripts": { - "test:path": "./node_modules/.bin/tape -r @babel/register", + "prepublish": "./node_modules/.bin/babel ./src -d ./dist --ignore node_modules/ --ignore dist/", "test": "./node_modules/.bin/tape -r @babel/register './src/**/*.test.js' | ./node_modules/.bin/tap-notify | ./node_modules/.bin/faucet", + "test:path": "./node_modules/.bin/tape -r @babel/register", "test:watch": "./node_modules/.bin/onchange '**/*.js' -- yarn run test", "test:pathwatch": "./node_modules/.bin/onchange '**/*.js' -- yarn run test:path", "test:game-server": "curl -s -X GET --header 'Accept: application/json' 'http://localhost:3001/map/1/player/1234' | node -r @babel/register -e \"process.stdin.setEncoding('utf8'); var chunks = ''; process.stdin.on('readable', () => { const chunk = process.stdin.read(); if (chunk !== null) { chunks += chunk }}); process.stdin.on('end', () => { process.stdout.write(JSON.stringify(JSON.parse(chunks), null, 2)) ;});\"" }, + "keywords": [ + "rest", + "restful", + "api", + "websockets", + "sockets", + "framework", + "server", + "arango", + "orm", + "web" + ], "devDependencies": { "@babel/cli": "^7.0.0-beta.35", "@babel/core": "^7.0.0-beta.35", diff --git a/src/Component.js b/src/Component.js index 23dad71..0d00aeb 100644 --- a/src/Component.js +++ b/src/Component.js @@ -35,6 +35,18 @@ export default class Final { return } + static async setProps (instance, state) { + instance.props = state + + return + } + + async setProps (state) { + await this.constructor.setProps(this, state) + + return + } + async shouldComponentUpdate (newProps) { if (newProps !== this.props) { return true diff --git a/src/Component.test.js b/src/Component.test.js index 37fd05f..aca3c15 100644 --- a/src/Component.test.js +++ b/src/Component.test.js @@ -1,15 +1,13 @@ import test from 'tape' import client from 'superagent' import Final, { reduxConnect } from './index' -import { middleware, store } from '../examples/middleware' +import { store } from '../examples/middleware' import { findDecorator } from '../test/ArangoDecorator' -import { createStore, bind, bindActionCreators } from 'redux' +import { bindActionCreators } from 'redux' import WS from 'ws' const HOST = 'localhost:3001' -function testo () { return { type: 'TEST' } } - @reduxConnect( null, (dispatch) => bindActionCreators({ diff --git a/src/reduxConnect.js b/src/reduxConnect.js index b71f772..49439f1 100644 --- a/src/reduxConnect.js +++ b/src/reduxConnect.js @@ -17,18 +17,15 @@ const reduxConnect = (mapStateToProps, mapActionsToDispatch) => (Component) => { }) } - Component.prototype.actions = { - ...Component.prototype.actions, - ...actions - } + Object.assign(Component.prototype, { actions }) Component.prototype.store = store const instance = new Component() - instance.props = { + instance.setProps({ ...instance.props, ...props - } + }) function socketHandler (data) { instance.lifecycleIncrement = 1 diff --git a/src/updater.js b/src/updater.js index e742ba7..2e2b603 100644 --- a/src/updater.js +++ b/src/updater.js @@ -21,11 +21,10 @@ async function init (instance, req, res) { const pathname = parse(req.url).pathname const match = route(instance.path) const params = match(pathname) - instance.props = instance.props || {} - instance.props = { + instance.setProps({ ...instance.props, params - } + }) if (!instance.props.params) { // logger('No match pathname', pathname) throw 'No match' @@ -33,11 +32,11 @@ async function init (instance, req, res) { } if (name === 'shouldComponentUpdate') { - instance.props = { + instance.setProps({ ...instance.props, request: req, response: res - } + }) if (instance.props.response instanceof WebSocket) { logger('✅ is WebSocket') } @@ -61,7 +60,7 @@ async function updater (instance, data) { throw "Should not update" } - instance.props = nextProps + instance.setProps(nextProps) const response = await instance.respond() await instance.setState(response)