dx-server 0.0.1-pre-11 → 0.0.1-pre-12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -75
- package/cjs/expressApp.d.ts +2 -2
- package/cjs/expressApp.js +5 -5
- package/esm/expressApp.d.ts +2 -2
- package/esm/expressApp.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,11 +9,14 @@ yarn add dx-server jchain
|
|
|
9
9
|
|
|
10
10
|
Check below sample with comment for more details.
|
|
11
11
|
|
|
12
|
+
Sample additionally requires: `yarn install express morgan`
|
|
13
|
+
|
|
12
14
|
```javascript
|
|
13
15
|
import {Server} from 'http'
|
|
14
16
|
import {promisify} from 'util'
|
|
17
|
+
import chain from 'jchain'
|
|
15
18
|
import {
|
|
16
|
-
requestContext, responseContext,
|
|
19
|
+
makeContext, requestContext, responseContext,
|
|
17
20
|
|
|
18
21
|
expressContext, setHtml, setJson,
|
|
19
22
|
|
|
@@ -26,85 +29,148 @@ import {
|
|
|
26
29
|
|
|
27
30
|
router,
|
|
28
31
|
catchApiError, catchError, notFound, notFoundApi,
|
|
29
|
-
expressApp,
|
|
32
|
+
expressApp, expressRouter,
|
|
30
33
|
} from 'dx-server'
|
|
34
|
+
import express from 'express'
|
|
35
|
+
import morgan from 'morgan'
|
|
31
36
|
|
|
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
|
-
|
|
37
|
+
// it is best practice to create custom error class for non-system error
|
|
38
|
+
class ServerError extends Error {
|
|
39
|
+
name = 'ServerError'
|
|
40
|
+
|
|
41
|
+
constructor(message, status = 400, code = 'unknown') {
|
|
42
|
+
super(message)
|
|
43
|
+
this.status = status
|
|
44
|
+
this.code = code
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// makeContext is a convenient way to create context
|
|
49
|
+
const authContext = makeContext(() => {
|
|
50
|
+
const req = requestContext.value
|
|
51
|
+
// determine if user is authenticated
|
|
52
|
+
// for e.g.
|
|
53
|
+
if (req.headers.authorization) {
|
|
54
|
+
return {id: 1, name: 'joe'}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const requireAuth = () => {
|
|
59
|
+
if (!authContext.value) throw new ServerError('unauthorized', 401, 'unauthorized')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const serverChain = chain(
|
|
63
|
+
expressContext.chain({jsonBeautify: true}), // allows to use setHtml, setJson, setRaw, setBuffer, setFile, setRedirect, etc.
|
|
64
|
+
bufferBodyContext.chain(), // use raw buffer body as Buffer use bufferBodyContext.value. This is required for jsonBodyContext, urlencodedBodyContext, textBodyContext, rawBodyContext
|
|
65
|
+
jsonBodyContext.chain(), // to get body parsed as json use jsonBodyContext.value. Only available if content-type is application/json
|
|
66
|
+
urlencodedBodyContext.chain(), // to get body parsed as urlencoded use urlencodedBodyContext.value. Only available if content-type is application/x-www-form-urlencoded
|
|
67
|
+
textBodyContext.chain(), // to get body parsed as text use textBodyContext.value. Only available if content-type is text/plain
|
|
68
|
+
rawBodyContext.chain(), // to get body as raw use rawBodyContext.value. Only available if content-type is application/octet-stream
|
|
69
|
+
queryContext.chain(), // to get query params use queryContext.value. Query is parsed via 'qs' package. If no query, return empty object {}
|
|
70
|
+
next => {
|
|
71
|
+
// this is the difference between express and dx-server
|
|
72
|
+
// req, res can be accessed from anywhere via context which uses NodeJS's AsyncLocalStorage under the hood
|
|
73
|
+
responseContext.value.setHeader('cache-control', 'no-cache')
|
|
74
|
+
next()
|
|
75
|
+
},
|
|
76
|
+
async next => {
|
|
77
|
+
// global error catching for all following middlewares
|
|
78
|
+
try {
|
|
79
|
+
await next()
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// only app error message should be shown to user
|
|
82
|
+
if (e instanceof ServerError) setHtml(`${e.message} (code: ${e.code})`, {status: e.status})
|
|
83
|
+
else {
|
|
84
|
+
// report system error
|
|
85
|
+
console.error(e)
|
|
86
|
+
setHtml('internal server error (code: internal)', {status: 500})
|
|
82
87
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
await expressApp(app => {
|
|
91
|
+
// any express feature can be used
|
|
92
|
+
// required express installed, with for e.g., `yarn add express`
|
|
93
|
+
app.set('trust proxy', true)
|
|
94
|
+
if (process.env.NODE_ENV !== 'production') app.set('json spaces', 2)
|
|
95
|
+
|
|
96
|
+
app.use(
|
|
97
|
+
morgan('common'), // in future, we will provide native implementation of express middlewares
|
|
98
|
+
// cookies, session, etc.
|
|
99
|
+
// session({
|
|
100
|
+
// secret: '123',
|
|
101
|
+
// resave: false,
|
|
102
|
+
// store: redisStore,
|
|
103
|
+
// saveUninitialized: true,
|
|
104
|
+
// // cookie: { secure: true }
|
|
105
|
+
// }),
|
|
106
|
+
)
|
|
107
|
+
}),
|
|
108
|
+
await expressRouter(router => {
|
|
109
|
+
// setup express router
|
|
110
|
+
router.use('/public', express.static('public'))
|
|
111
|
+
}),
|
|
112
|
+
authContext.chain(),
|
|
113
|
+
// example of catching error for all /api/* routes
|
|
114
|
+
router.post({
|
|
115
|
+
async '/api'({next}) {
|
|
116
|
+
try {
|
|
117
|
+
await next()
|
|
118
|
+
} catch (e) {
|
|
119
|
+
// only app error message should be shown to user
|
|
120
|
+
if (e instanceof ServerError) setJson({
|
|
121
|
+
error: e.message,
|
|
122
|
+
code: e.code,
|
|
123
|
+
}, {status: e.status})
|
|
124
|
+
else {
|
|
125
|
+
// report system error
|
|
126
|
+
console.error(e)
|
|
94
127
|
setJson({
|
|
95
|
-
message: '
|
|
96
|
-
code: '
|
|
97
|
-
}, {status:
|
|
128
|
+
message: 'internal server error',
|
|
129
|
+
code: 'internal'
|
|
130
|
+
}, {status: 500})
|
|
98
131
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, {end: false}), // note: {end: false} is required to match all /api/* routes. This option is passed directly to path-to-regexp
|
|
135
|
+
router.post({
|
|
136
|
+
'/api/sample-public-api'() { // sample POST router
|
|
137
|
+
setJson({name: 'joe'})
|
|
138
|
+
},
|
|
139
|
+
'/api/me'() { // sample private router
|
|
140
|
+
requireAuth()
|
|
141
|
+
setJson({name: authContext.value.name})
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
router.get({ // sample GET router
|
|
145
|
+
'/'() {
|
|
146
|
+
setHtml('ok')
|
|
147
|
+
},
|
|
148
|
+
'/health'() {
|
|
149
|
+
setHtml('ok')
|
|
150
|
+
}
|
|
151
|
+
}),
|
|
152
|
+
router.post({ // api not found router
|
|
153
|
+
'/api'() {
|
|
154
|
+
throw new ServerError('not found', 404, 'not_found')
|
|
155
|
+
}
|
|
156
|
+
}, {end: false}),
|
|
157
|
+
() => { // not found router
|
|
158
|
+
throw new ServerError('not found', 404, 'not_found')
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const tcpServer = new Server()
|
|
163
|
+
.on('request', async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
await chain(
|
|
166
|
+
requestContext.chain(req), // required for most middlewares
|
|
167
|
+
responseContext.chain(res), // required for most middlewares
|
|
168
|
+
serverChain,
|
|
169
|
+
)()
|
|
170
|
+
} catch (e) {
|
|
171
|
+
console.error(e)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
108
174
|
|
|
109
175
|
const port = +(process.env.PORT ?? 3000)
|
|
110
176
|
await promisify(tcpServer.listen.bind(tcpServer))(port)
|
package/cjs/expressApp.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type Express, type Router } from 'express';
|
|
2
|
-
export declare const expressApp: (setup: (app: Express) => any) => (next: any) => Promise<void
|
|
3
|
-
export declare const expressRouter: (setup: (router: Router) => any) => (next: any) => Promise<void
|
|
2
|
+
export declare const expressApp: (setup: (app: Express) => any) => Promise<(next: any) => Promise<void>>;
|
|
3
|
+
export declare const expressRouter: (setup: (router: Router) => any) => Promise<(next: any) => Promise<void>>;
|
package/cjs/expressApp.js
CHANGED
|
@@ -4,10 +4,10 @@ exports.expressRouter = exports.expressApp = void 0;
|
|
|
4
4
|
const express_1 = require("express");
|
|
5
5
|
const context_js_1 = require("./context.js");
|
|
6
6
|
const jdefer_1 = require("jdefer");
|
|
7
|
-
const expressApp = (setup) => {
|
|
7
|
+
const expressApp = async (setup) => {
|
|
8
8
|
const symbol = Symbol('expressApp');
|
|
9
9
|
const app = (0, express_1.default)();
|
|
10
|
-
setup(app);
|
|
10
|
+
await setup(app);
|
|
11
11
|
app.use((req, _res, _next) => req[symbol].resolve());
|
|
12
12
|
app.use((err, req, _res, _next) => req[symbol].reject(err));
|
|
13
13
|
return async (next) => {
|
|
@@ -20,10 +20,10 @@ const expressApp = (setup) => {
|
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
exports.expressApp = expressApp;
|
|
23
|
-
const expressRouter = (setup) => {
|
|
23
|
+
const expressRouter = async (setup) => {
|
|
24
24
|
const symbol = Symbol('expressRouter');
|
|
25
25
|
const router = express_1.default.Router();
|
|
26
|
-
setup(router);
|
|
26
|
+
await setup(router);
|
|
27
27
|
router.use((req, _res, _next) => req[symbol].resolve());
|
|
28
28
|
router.use((err, req, _res, _next) => req[symbol].reject(err));
|
|
29
29
|
return async (next) => {
|
|
@@ -36,4 +36,4 @@ const expressRouter = (setup) => {
|
|
|
36
36
|
};
|
|
37
37
|
};
|
|
38
38
|
exports.expressRouter = expressRouter;
|
|
39
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
39
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwcmVzc0FwcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9leHByZXNzQXBwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFDQUEwRDtBQUMxRCw2Q0FBNEQ7QUFDNUQsbUNBQThCO0FBRXZCLE1BQU0sVUFBVSxHQUFHLEtBQUssRUFBRSxLQUE0QixFQUFFLEVBQUU7SUFDaEUsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFBO0lBRW5DLE1BQU0sR0FBRyxHQUFHLElBQUEsaUJBQU8sR0FBRSxDQUFBO0lBQ3JCLE1BQU0sS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBQ2hCLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7SUFDcEQsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO0lBQzNELE9BQU8sS0FBSyxFQUFDLElBQUksRUFBQyxFQUFFO1FBQ25CLE1BQU0sR0FBRyxHQUFHLDJCQUFjLENBQUMsS0FBSyxDQUFBO1FBQ2hDLE1BQU0sS0FBSyxHQUFHLElBQUEsZ0JBQVMsR0FBRSxDQUFBO1FBQ3pCLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUE7UUFDbkIsR0FBRyxDQUFDLEdBQUcsRUFBRSw0QkFBZSxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQy9CLE1BQU0sS0FBSyxDQUFDLE9BQU8sQ0FBQTtRQUNuQixNQUFNLElBQUksRUFBRSxDQUFBO0lBQ2IsQ0FBQyxDQUFBO0FBQ0YsQ0FBQyxDQUFBO0FBZlksUUFBQSxVQUFVLGNBZXRCO0FBRU0sTUFBTSxhQUFhLEdBQUcsS0FBSyxFQUFFLEtBQThCLEVBQUUsRUFBRTtJQUNyRSxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUE7SUFFdEMsTUFBTSxNQUFNLEdBQUcsaUJBQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQTtJQUMvQixNQUFNLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUNuQixNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO0lBQ3ZELE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUM5RCxPQUFPLEtBQUssRUFBQyxJQUFJLEVBQUMsRUFBRTtRQUNuQixNQUFNLEdBQUcsR0FBRywyQkFBYyxDQUFDLEtBQUssQ0FBQTtRQUNoQyxNQUFNLEtBQUssR0FBRyxJQUFBLGdCQUFTLEdBQUUsQ0FBQTtRQUN6QixHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsS0FBSyxDQUFBO1FBQ25CLE1BQU0sQ0FBQyxHQUFHLEVBQUUsNEJBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUNsQyxNQUFNLEtBQUssQ0FBQyxPQUFPLENBQUE7UUFDbkIsTUFBTSxJQUFJLEVBQUUsQ0FBQTtJQUNiLENBQUMsQ0FBQTtBQUNGLENBQUMsQ0FBQTtBQWZZLFFBQUEsYUFBYSxpQkFlekIifQ==
|
package/esm/expressApp.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type Express, type Router } from 'express';
|
|
2
|
-
export declare const expressApp: (setup: (app: Express) => any) => (next: any) => Promise<void
|
|
3
|
-
export declare const expressRouter: (setup: (router: Router) => any) => (next: any) => Promise<void
|
|
2
|
+
export declare const expressApp: (setup: (app: Express) => any) => Promise<(next: any) => Promise<void>>;
|
|
3
|
+
export declare const expressRouter: (setup: (router: Router) => any) => Promise<(next: any) => Promise<void>>;
|
package/esm/expressApp.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { requestContext, responseContext } from './context.js';
|
|
3
3
|
import makeDefer from 'jdefer';
|
|
4
|
-
export const expressApp = (setup) => {
|
|
4
|
+
export const expressApp = async (setup) => {
|
|
5
5
|
const symbol = Symbol('expressApp');
|
|
6
6
|
const app = express();
|
|
7
|
-
setup(app);
|
|
7
|
+
await setup(app);
|
|
8
8
|
app.use((req, _res, _next) => req[symbol].resolve());
|
|
9
9
|
app.use((err, req, _res, _next) => req[symbol].reject(err));
|
|
10
10
|
return async (next) => {
|
|
@@ -16,10 +16,10 @@ export const expressApp = (setup) => {
|
|
|
16
16
|
await next();
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
-
export const expressRouter = (setup) => {
|
|
19
|
+
export const expressRouter = async (setup) => {
|
|
20
20
|
const symbol = Symbol('expressRouter');
|
|
21
21
|
const router = express.Router();
|
|
22
|
-
setup(router);
|
|
22
|
+
await setup(router);
|
|
23
23
|
router.use((req, _res, _next) => req[symbol].resolve());
|
|
24
24
|
router.use((err, req, _res, _next) => req[symbol].reject(err));
|
|
25
25
|
return async (next) => {
|
|
@@ -31,4 +31,4 @@ export const expressRouter = (setup) => {
|
|
|
31
31
|
await next();
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
34
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwcmVzc0FwcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9leHByZXNzQXBwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sT0FBb0MsTUFBTSxTQUFTLENBQUE7QUFDMUQsT0FBTyxFQUFDLGNBQWMsRUFBRSxlQUFlLEVBQUMsTUFBTSxjQUFjLENBQUE7QUFDNUQsT0FBTyxTQUFTLE1BQU0sUUFBUSxDQUFBO0FBRTlCLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxLQUFLLEVBQUUsS0FBNEIsRUFBRSxFQUFFO0lBQ2hFLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQTtJQUVuQyxNQUFNLEdBQUcsR0FBRyxPQUFPLEVBQUUsQ0FBQTtJQUNyQixNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUNoQixHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO0lBQ3BELEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUMzRCxPQUFPLEtBQUssRUFBQyxJQUFJLEVBQUMsRUFBRTtRQUNuQixNQUFNLEdBQUcsR0FBRyxjQUFjLENBQUMsS0FBSyxDQUFBO1FBQ2hDLE1BQU0sS0FBSyxHQUFHLFNBQVMsRUFBRSxDQUFBO1FBQ3pCLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUE7UUFDbkIsR0FBRyxDQUFDLEdBQUcsRUFBRSxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDL0IsTUFBTSxLQUFLLENBQUMsT0FBTyxDQUFBO1FBQ25CLE1BQU0sSUFBSSxFQUFFLENBQUE7SUFDYixDQUFDLENBQUE7QUFDRixDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsS0FBSyxFQUFFLEtBQThCLEVBQUUsRUFBRTtJQUNyRSxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUE7SUFFdEMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFBO0lBQy9CLE1BQU0sS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBQ25CLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7SUFDdkQsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO0lBQzlELE9BQU8sS0FBSyxFQUFDLElBQUksRUFBQyxFQUFFO1FBQ25CLE1BQU0sR0FBRyxHQUFHLGNBQWMsQ0FBQyxLQUFLLENBQUE7UUFDaEMsTUFBTSxLQUFLLEdBQUcsU0FBUyxFQUFFLENBQUE7UUFDekIsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLEtBQUssQ0FBQTtRQUNuQixNQUFNLENBQUMsR0FBRyxFQUFFLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUNsQyxNQUFNLEtBQUssQ0FBQyxPQUFPLENBQUE7UUFDbkIsTUFBTSxJQUFJLEVBQUUsQ0FBQTtJQUNiLENBQUMsQ0FBQTtBQUNGLENBQUMsQ0FBQSJ9
|