Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fxapp

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fxapp - npm Package Compare versions

Comparing version 0.3.0 to 0.4.0

src/fx/makeRouter.js

4

LICENSE.md

@@ -0,1 +1,3 @@

Copyright © 2018-present Wolfgang
Permission is hereby granted, free of charge, to any person obtaining a copy

@@ -17,2 +19,2 @@ of this software and associated documentation files (the "Software"), to deal

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
{
"name": "fxapp",
"description": "Build JavaScript apps using effects as data",
"version": "0.3.0",
"main": "dist/fxapp.js",
"module": "src/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/fxapp/fxapp.git"
},
"description": "Build JavaScript server apps using effects as data",
"version": "0.4.0",
"main": "./src/index.js",
"files": [
"src",
"dist"
"src"
],
"keywords": [
"effects",
"data",
"vdom"
],
"devDependencies": {
"babel-preset-env": "=1.6.1",
"eslint": "=4.19.1",
"eslint-plugin-compat": "=2.2.0",
"jest": "=22.4.3",
"rollup": "=0.57.1",
"uglify-js": "=3.3.16"
"eslint": "=6.7.2",
"jest": "=24.9.0",
"nodemon": "=2.0.2",
"prettier": "=1.19.1"
},
"scripts": {
"clean": "npx rimraf coverage dist node_modules",
"format": "npx prettier --write {src,test}/**/*.js",
"format:check": "npx prettier --list-different {src,test}/**/*.js",
"lint": "eslint {src,test}/**/*.js",
"test": "jest --coverage --no-cache",
"bundle": "rollup -i src/index.js -o dist/fxapp.js -m -f umd -n fx",
"minify": "uglifyjs dist/fxapp.js -o dist/fxapp.js -mc --source-map includeSources,url=fxapp.js.map",
"format": "prettier --ignore-path .gitignore --write \"**/*.js\"",
"format:check": "prettier --ignore-path .gitignore --list-different \"**/*.js\"",
"lint": "eslint .",
"test": "jest --coverage",
"start": "nodemon --ext js example",
"check": "npm run format:check && npm run lint && npm t",
"build": "npm run check && npm run bundle && npm run minify",
"prepare": "npm run build",
"prepare": "npm run check",
"release": "./pre-flight-tests && npm run clean && git pull && npm i && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"babel": {
"presets": "env"
},
"eslintConfig": {
"extends": "eslint:recommended",
"plugins": [
"compat"
],
"parserOptions": {
"sourceType": "module"
},
"env": {
"browser": true
},
"rules": {
"no-use-before-define": "error",
"compat/compat": "error"
"node": true,
"es6": true
}
},
"browserslist": [
"IE 10"
],
"jest": {
"collectCoverageFrom": [
"src/**/*.js"
]
},
"author": "Wolfgang Wedemeyer <wolf@okwolf.com>",
"license": "MIT",
"repository": "fxapp/fxapp",
"bugs": {

@@ -66,0 +42,0 @@ "url": "https://github.com/fxapp/fxapp/issues"

@@ -7,24 +7,245 @@ # FX App

Here is an example counter that can be incremented or decremented. Go ahead and [try it online](https://codepen.io/okwolf/pen/WMWBjR?editors=0010).
Build JavaScript server apps using [_effects as data_](https://youtu.be/6EdXaWfoslc). Requests and responses are represented as data and FX use this data to interact with the imperative [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) APIs provided by Node.
## Getting Started
```console
$ npm i fxapp
```
```js
fx.app({
state: {
count: 0
},
actions: {
down: ({ state, fx }) => fx.merge({ count: state.count - 1 }),
up: ({ state, fx }) => fx.merge({ count: state.count + 1 })
},
view: ({ state, fx }) => [
"main",
["h1", state.count],
["button", { onclick: fx.action("down") }, "-"],
["button", { onclick: fx.action("up") }, "+"]
]
const { app } = require("fxapp");
app({
routes: () => ({
response: {
text: "Hello World"
}
})
});
```
[http://localhost:8080](http://localhost:8080)
## `app` Options
### `port`
Default: `8080`
The port on which the server will listen.
### `initFx`
Optional initial [`dispatch`able(s)](#dispatchable-types) that are run on server start before accepting any requests. Use this to set initial global state or for side effectful initialization like opening required resources or network connections.
### `requestFx`
Optional [`dispatch`able(s)](#dispatchable-types) that are run on every request before the router. Use this for parsing custom request data, custom routing, sending [custom responses](#responsecustom), or side FX like logging.
### `routes`
Default: `{}`
Routes are defined as a nested object structure with some properties having special meanings. The first matching route value will be dispatched.
Example:
```js
app({
routes: {
// GET /unknown/path
_: fallbackAction,
path: {
some: {
// GET /path/some
GET: someReadAction,
// POST /path/some
POST: someAddAction
},
other: {
// GET /path/other/123
$id: otherAction
}
}
}
});
```
#### Default Routes
The special wildcard `_` route is reserved for routes that match in the absence of a more specific route. Useful for 404 and related behaviors. Sending a `GET` request to `/unknown/path` will respond with the results of `fallbackAction`.
#### HTTP Method Routes
Routes with the name of an [HTTP request method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods) will match any requests with that method. Sending a `GET` request to `/path/some` with the example route will respond with the results of `someReadAction`. A `POST` request to `/path/some` will respond with the results of `someAddAction`.
#### Path Params
Routes beginning with `$` are reserved and define a path parameter for matching at that position. In the example above sending a `GET` request to `/path/other/123` will respond with the results of passing `{id: "123"}` as the `request.params` to `otherAction`.
## State Shape
```js
{
request: {
method: "GET",
url: "/path/other/123?param=value&multiple=1&multiple=2",
path: "/path/other/123",
query: { param: "value", multiple: ["1", "2"] },
params: { id: "123" },
headers: {
Host: "localhost:8080"
}
},
response: {
statusCode: 200,
headers: { Server: "fxapp" },
text: "Hello World"
}
}
```
### `request`
Normalized data parsed from the [HTTP request](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message) that is currently being processed.
#### `request.method`
Examples: `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, `TRACE`, `PATCH`
The [HTTP request method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods) used to make the request.
#### `request.url`
The full request URL, including query parameters.
#### `request.path`
The request path, omitting query parameters.
#### `request.query`
An object containing the request query parameters. Multiple instances of the same parameter are stored as an array.
#### `request.params`
An object containing path parameters from the router.
#### `request.headers`
An object containing all [HTTP request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Standard_request_fields).
#### `request.body`
The contents of the request body. Respects the [`Content-Length`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length) header.
#### `request.jsonBody`
This will be set if the `Content-Type` of the request is `application/json` and the body content is valid JSON.
### `response`
Data representing the [HTTP response](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Response_message) that will be sent to the client once all FX are done running.
#### `response.statusCode`
Default: `200`
The [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) to send in the response.
#### `response.headers`
The [HTTP response headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) to send.
#### `response.custom`
Skip the default logic for sending the response body. Make sure you provide [`requestFx`](#requestfx) to handle this response or the request will hang for the client.
#### `response.json`
The HTTP response body that will be sent as `application/json`. Value will be formatted using `JSON.stringify`.
#### `response.html`
The HTTP response body that will be sent as `text/html`.
#### `response.filePath`
The HTTP response will pipe the contents of the file at the given path. Never pass user-provided data for this as that would introduce a vulnerability for arbitrary disk access. You may need to add `response.contentType` in order for the client to interpret the response correctly.
#### `response.text`
The HTTP response body that will be sent as `text/plain` unless a value is passed for `response.contentType`.
## Dispatchable Types
```js
StateUpdate = function(state: Object) => newState: Object
ReservedProps = {
concurrent: boolean? = false,
after: boolean? = false,
cancel: boolean? = false
}
FX = {
run: ({
dispatch: function(Dispatchable),
serverRequest: IncomingMessage,
serverResponse: ServerResponse,
...ReservedProps,
// Additional props
}) => Promise? | undefined,
...ReservedProps,
// Additional props
}
Dispatchable = StateUpdate | FX | [Dispatchable]
```
### State Mapping Function `state => newState`
Perform an immutable state update by receiving the current state as a parameter and returning the new state. Automatically shallow merges root properties in addition to one level under `request` and `response`.
### FX
_FX as data_ are represented with an object containing a `run` function and additional properties that will be passed to that function.
The `run` function returns a `Promise` if the effect is async. Async FX are considered still running until resolved or rejected. Otherwise FX are considered sync and done once the `run` function returns.
Some `props` are reserved and have special meaning:
#### `concurrent`
Default: `false`
Used for running multiple FX in parallel where the results are unrelated. These FX will take priority and must all complete before running any nonconcurrent FX.
#### `after`
Default: `false`
Run FX after all others are complete. Use this for logging, cleanup, or providing custom response-sending logic.
#### `cancel`
Default: `false`
Cancel all other FX immediately. Cancelled FX are no longer able to dispatch. FX already dispatched with `after` will still be run to allow for the response to be sent. Use this to enforce response timeouts or for handling errors.
#### `serverRequest`
The internal [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) used by the Node HTTP server implementation. Allows for FX to interact with the request object to get additional data.
#### `serverResponse`
The internal [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) used by the Node HTTP server implementation. Allows for FX to send other types of responses.
### Arrays
A batch of state mapping functions and/or FX may be dispatched by wrapping them in an array.
## License
FX App is MIT licensed. See [LICENSE](LICENSE.md).

@@ -1,52 +0,42 @@

import { isFn, assign, get } from "./utils";
import { makeFx } from "./fxUtils";
import { patch } from "./patch";
const { createServer } = require("http");
const { assign } = require("./utils");
const makeServer = require("./makeServer");
const makeServerRuntime = require("./makeServerRuntime");
const parseRequest = require("./fx/parseRequest");
const parseBody = require("./fx/parseBody");
const sendResponse = require("./fx/sendResponse");
const makeRouter = require("./fx/makeRouter");
export function app(props) {
var store = {
state: assign(props.state),
actions: assign(props.actions)
};
function wireFx(namespace, state, actions) {
var sliceFx = makeFx(namespace, store, props.fx);
for (var key in actions) {
isFn(actions[key])
? (function(key, action) {
actions[key] = function(data) {
var actionResult = action({
state: get(namespace, store.state),
data: data,
fx: sliceFx.creators
});
sliceFx.run(actionResult);
return actionResult;
};
})(key, actions[key])
: wireFx(
namespace.concat(key),
(state[key] = assign(state[key])),
(actions[key] = assign(actions[key]))
);
}
}
wireFx([], store.state, store.actions);
var rootFx = makeFx([], store, props.fx);
var container = props.container || document.body;
function render() {
var nextNode = props.view({
state: store.state,
fx: rootFx.creators
});
patch(nextNode, container, rootFx);
}
if (isFn(props.view)) {
store.onchange = render;
render();
}
return store.actions;
}
module.exports = options => {
const mergedOptions = assign(
{
port: 8080,
httpApi: createServer,
makeServer,
makeServerRuntime,
parseRequest,
parseBody,
sendResponse,
makeRouter
},
options
);
return mergedOptions
.makeServerRuntime({
initFx: options.initFx,
requestFx: [
mergedOptions.parseRequest,
mergedOptions.parseBody,
options.requestFx,
mergedOptions.makeRouter(options.routes),
mergedOptions.sendResponse
]
})
.then(serverRuntime =>
mergedOptions.makeServer({
port: mergedOptions.port,
httpApi: mergedOptions.httpApi,
serverRuntime
})
);
};

@@ -1,2 +0,3 @@

export { h } from "./h";
export { app } from "./app";
const app = require("./app");
module.exports = { app };

@@ -1,45 +0,14 @@

export var isArray = Array.isArray;
const isArray = Array.isArray;
const isFn = value => typeof value === "function";
const isObj = value => value && typeof value === "object" && !isArray(value);
const isFx = value => isObj(value) && isFn(value.run);
export function isFn(value) {
return typeof value === "function";
}
const assign = (...args) => Object.assign({}, ...args);
export function isObj(value) {
return typeof value === "object" && !isArray(value);
}
export function assign(from, assignments) {
var i,
obj = {};
for (i in from) obj[i] = from[i];
for (i in assignments) obj[i] = assignments[i];
return obj;
}
export function set(prefixes, value, from) {
var target = {};
if (prefixes.length) {
target[prefixes[0]] =
prefixes.length > 1
? set(prefixes.slice(1), value, from[prefixes[0]])
: value;
return assign(from, target);
}
return value;
}
export function get(prefixes, from) {
for (var i = 0; i < prefixes.length; i++) {
from = from && from[prefixes[i]];
}
return from;
}
export function reduceByNameAndProp(items, prop) {
return items.reduce(function(others, next) {
others[next.name] = next[prop];
return others;
}, {});
}
module.exports = {
isArray,
isFn,
isObj,
isFx,
assign
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc