UNPKG

3.45 kBJavaScriptView Raw
1const debug = require('debug')('upward-js:Context');
2const { pick } = require('lodash');
3const { URL } = require('url');
4
5const ContextPath = require('./ContextPath');
6
7const statusCodes = Array.from({ length: 600 }, (_, i) => i + 100);
8const constants = new Set([
9 true,
10 false,
11 'GET',
12 'POST',
13 'mustache',
14 'text/html',
15 'text/plain',
16 'application/json',
17 'utf-8',
18 'utf8',
19 'latin-1',
20 'base64',
21 'binary',
22 'hex',
23 ...statusCodes,
24 ...statusCodes.map(code => code.toString())
25]);
26
27class Context {
28 static fromRequest(env, request) {
29 debug('generating from request: %s', request.url);
30 const hostedUrl = new URL(request.url, `http://${request.get('host')}`);
31 debug('url derived from host is %O', hostedUrl);
32 const url = pick(hostedUrl, [
33 'host',
34 'hostname',
35 'port',
36 'pathname',
37 'path',
38 'search',
39 'searchParams'
40 ]);
41 url.query = request.query;
42 return new Context({
43 env,
44 request: {
45 url,
46 headers: request.headers,
47 headerEntries: Object.entries(request.headers).map(
48 ([name, value]) => ({ name, value })
49 ),
50 queryEntries: Object.entries(url.query).map(
51 ([name, value]) => ({ name, value })
52 )
53 }
54 });
55 }
56
57 constructor(data) {
58 this._data = data;
59 this._promises = new Map();
60 }
61
62 setVisitor(visitor) {
63 this.visitor = visitor;
64 }
65
66 async get(lookup) {
67 if (constants.has(lookup) || constants.has(lookup.toString())) {
68 debug('%s is a constant', lookup);
69 return lookup;
70 }
71 const path = ContextPath.from(lookup);
72 debug('lookup %s at path %s', lookup, path);
73 const base = path.base();
74 debug('%s is from context base %s', lookup, base);
75 if (!this._data.hasOwnProperty(base)) {
76 debug('%s not yet assigned, acquiring promise handle', base);
77 let promise = this._promises.get(base);
78 if (!promise) {
79 debug('%s has never been requested, visiting from root', base);
80 promise = this.visitor.downward([base]).then(value => {
81 if (typeof value === 'function') {
82 debug(
83 '%s assigned to context as function: %o',
84 base,
85 value
86 );
87 this.set(base, value);
88 } else {
89 debug('%s assigned: %o', base, value[base]);
90 this.set(base, value[base]);
91 }
92 });
93 this._promises.set(base, promise);
94 }
95 await promise;
96 }
97 return path.getFrom(this._data);
98 }
99
100 set(base, value, override) {
101 const isSet = constants.has(base) || this._data.hasOwnProperty(base);
102 if (isSet && !override) {
103 throw new Error(
104 `Attempted to reassign context property '${base}' to '${value}'. Context properties cannot be reassigned.`
105 );
106 }
107 this._data[base] = value;
108 }
109
110 forget(base) {
111 delete this._data[base];
112 }
113}
114
115module.exports = Context;