-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
194 lines (176 loc) · 6.05 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//. # momi
//.
//. **Mo**nadic **Mi**ddleware
//.
//. > `npm install --save monastic fluture momi`
//.
//. Middleware - specifically in the case of Connect, Express and Koa - is a
//. mechanism which encodes several effects:
//.
//. - **A build-up of state** through mutation of the `req` parameter
//. - **An eventual response** through mutation of the `res` parameter
//. - **Inversion of control** over the continuation by means of calling the
//. `next` parameter
//. - **Possible error branch** by means of calling the `next` parameter with
//. a value
//.
//. If we would want to encode all of these effects into a data-structure, we
//. could use a `StateT(Future) -> StateT(Future)` structure:
//.
//. - **A build-up of state** through the `State` monad
//. - **An eventual response** through the right-sided value of `Future`
//. - **Inversion of control** by passing the whole structure into a function
//. - **Possible error branch** through the left-sided value of `Future`
//.
//. In other words, the `StateT(Future)`-structure might be considered the
//. Middleware monad. This packages exposes the Middleware monad, comprised of
//. `State` from [monastic][] and `Future` from [Fluture][]. Besides the
//. monad itself, it also exposes some utility functions and structures for
//. practically applying Middleware. One such utility is the `App` class,
//. which allows composition of functions over Middleware to be written more
//. like what you are used to from middleware as it comes with Express or Koa.
//.
//. ## Usage
//.
//. ### Node
//.
//. ```console
//. $ npm install --save momi
//. ```
//.
//. On Node 12 and up, this module can be loaded directly with `import` or
//. `require`. On Node versions below 12, `require` or the [esm][]-loader can
//. be used.
//.
//. ### Deno and Modern Browsers
//.
//. You can load the EcmaScript module from various content delivery networks:
//.
//. - [Skypack](https://cdn.skypack.dev/[email protected])
//. - [JSPM](https://jspm.dev/[email protected])
//. - [jsDelivr](https://cdn.jsdelivr.net/npm/[email protected]/+esm)
//.
//. ### Old Browsers and Code Pens
//.
//. There's a [UMD][] file included in the NPM package, also available via
//. jsDelivr: https://cdn.jsdelivr.net/npm/[email protected]/dist/umd.js
//.
//. This file adds `momi` to the global scope, or use CommonJS/AMD
//. when available.
//.
//. ## Usage Example
//.
//. ```js
//. import Z from 'sanctuary-type-classes';
//. import qs from 'querystring';
//. import http from 'http';
//.
//. import {compose, constant} from 'monastic';
//. import {go, mount, get, put} from 'momi';
//.
//. const queryParseMiddleware = go (function* (next) {
//. const req = yield get;
//. const query = qs.parse (req.url.split ('?')[1]);
//. yield put (Object.assign ({query}, req));
//. return yield next;
//. });
//.
//. const echoMiddleware = Z.map (req => ({
//. status: 200,
//. headers: {'X-Powered-By': 'momi'},
//. body: req.query.echo,
//. }), get);
//.
//. const app = compose (
//. queryParseMiddleware,
//. constant (echoMiddleware)
//. );
//.
//. mount (http, app, 3000);
//. ```
//.
//. ## Examples
//.
//. - **[Readme][example-1]** the code from [Usage](#usage), ready to run.
//. - **[Express][example-2]** shows how to embed Momi within Express.
//. - **[Bootstrap][example-3]** an example showing application structure.
//. - **[Real World][example-4]** how momi is being used in real life.
import {Future, fork, reject as rejectF} from 'fluture';
import {StateT} from 'monastic';
import Z from 'sanctuary-type-classes';
export const Middleware = StateT (Future);
// reject :: b -> Middleware a b c
export function reject(x) {
return Middleware.lift (rejectF (x));
}
// fromComputation :: ((a -> (), b -> ()) -> () -> ()) -> Middleware s a b
export function fromComputation(f) {
return Middleware.lift (Future (f));
}
export const get = Middleware.get;
export const modify = Middleware.modify;
export const put = Middleware.put;
export const evalState = Middleware.evalState;
export const execState = Middleware.execState;
export const lift = Middleware.lift;
export const hoist = Middleware.hoist;
function mapErrToRes(err, res) {
res.writeHead (500, {'Content-Type': 'text/plain'});
console.error(err.stack || String(err)); //eslint-disable-line
res.end ('Internal Server Error\n', 'utf8');
}
function mapValToRes(spec, res) {
res.writeHead (spec.status, spec.headers);
res.end (spec.body, 'utf8');
}
const notFoundMiddleware = Z.of (Middleware, {
status: 404,
body: 'Not Found\n',
headers: {'Content-Type': 'text/plain'},
});
function evaluateApp(app) {
const monad = app (notFoundMiddleware);
if (monad instanceof Middleware) {
return monad;
}
throw new TypeError ('Your app does not return a Middleware');
}
export function go(f) {
return function(m) {
return Z.chain (() => {
const g = f (m);
function next(x) {
const o = g.next (x);
return o.done ? Z.of (Middleware, o.value) : Z.chain (next, o.value);
}
return next ();
}, Z.of (Middleware));
};
}
export function connect(app) {
const monad = evaluateApp (app);
return function(req, res, next) {
return fork (next)
(val => mapValToRes (val, res))
(evalState (req) (monad));
};
}
export function mount(http, app, port) {
const monad = evaluateApp (app);
return http.createServer ((req, res) => {
fork (err => mapErrToRes (err, res))
(val => mapValToRes (val, res))
(evalState (req) (monad));
}).listen (port);
}
export function run(app, initial) {
return evalState (initial) (app (Z.of (Middleware, null)));
}
//. [monastic]: https://github.com/dicearr/monastic
//. [Fluture]: https://github.com/fluture-js/Fluture
//. [example-1]: https://github.com/fluture-js/momi/tree/master/examples/readme
//. [example-2]: https://github.com/fluture-js/momi/tree/master/examples/express
//. [example-3]: https://github.com/fluture-js/momi/tree/master/examples/bootstrap
//. [example-4]: https://github.com/Avaq/node-server-skeleton/tree/master/src/bootstrap
//. [esm]: https://github.com/standard-things/esm
//. [UMD]: https://github.com/umdjs/umd