orator 3.0.10 → 4.0.0
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/debug/Harness.js +4 -8
- package/dist/orator.compatible.js +4889 -0
- package/dist/orator.compatible.min.js +17 -0
- package/dist/orator.compatible.min.js.map +1 -0
- package/dist/orator.js +979 -1727
- package/dist/orator.min.js +3 -24
- package/dist/orator.min.js.map +1 -1
- package/package.json +58 -66
- package/source/Orator-Default-ServiceServer-Web.js +4 -0
- package/source/Orator-Default-ServiceServer.js +4 -0
- package/source/Orator-ServiceServer-Base.js +8 -6
- package/source/Orator-ServiceServer-IPC-SynthesizedResponse.js +18 -1
- package/source/Orator-ServiceServer-IPC.js +111 -83
- package/source/Orator.js +139 -32
- package/test/Orator_basic_tests.js +43 -18
- package/test_legacy/Orator_basic_tests.js +1 -1
- package/test_legacy/Orator_cluster_test.js.deferred +1 -1
- package/test_legacy/Orator_logging_tests.js +1 -1
- package/source/Orator-Default-ServiceServers-Node.js +0 -21
- package/source/Orator-Default-ServiceServers-Web.js +0 -21
- /package/{test → debug/site}/Test.css +0 -0
- /package/{test → debug/site}/Test.html +0 -0
package/package.json
CHANGED
|
@@ -1,69 +1,61 @@
|
|
|
1
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
|
-
"bugs": {
|
|
29
|
-
"url": "https://github.com/stevenvelozo/orator/issues"
|
|
30
|
-
},
|
|
31
|
-
"mocha": {
|
|
32
|
-
"diff": true,
|
|
33
|
-
"extension": [
|
|
34
|
-
"js"
|
|
2
|
+
"name": "orator",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Unopinionated API behavior container - REST or IPC",
|
|
5
|
+
"main": "source/Orator.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node source/Orator.js",
|
|
8
|
+
"coverage": "npx nyc --reporter=lcov --reporter=text-lcov ./node_modules/mocha/bin/_mocha -- -u tdd -R spec",
|
|
9
|
+
"test": "npx mocha -u tdd -R spec",
|
|
10
|
+
"build": "npx quack build",
|
|
11
|
+
"docker-dev-build-image": "docker build ./ -f Dockerfile_LUXURYCode -t retold/orator:local",
|
|
12
|
+
"docker-dev-run": "docker run -it -d --name orator-dev -p 15277:8080 -p 34566:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/orator\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" orator-image:local",
|
|
13
|
+
"tests": "npx mocha -u tdd --exit -R spec --grep",
|
|
14
|
+
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t orator-image:local",
|
|
15
|
+
"docker-dev-shell": "docker exec -it orator-dev /bin/bash"
|
|
16
|
+
},
|
|
17
|
+
"browser": {
|
|
18
|
+
"./source/Orator-Default-ServiceServer.js": "./source/Orator-Default-ServiceServer-Web.js"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/stevenvelozo/orator.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"rest",
|
|
26
|
+
"api",
|
|
27
|
+
"logging"
|
|
35
28
|
],
|
|
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
|
-
}
|
|
29
|
+
"author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/stevenvelozo/orator/issues"
|
|
33
|
+
},
|
|
34
|
+
"mocha": {
|
|
35
|
+
"diff": true,
|
|
36
|
+
"extension": [
|
|
37
|
+
"js"
|
|
38
|
+
],
|
|
39
|
+
"package": "./package.json",
|
|
40
|
+
"reporter": "spec",
|
|
41
|
+
"slow": "75",
|
|
42
|
+
"timeout": "5000",
|
|
43
|
+
"ui": "tdd",
|
|
44
|
+
"watch-files": [
|
|
45
|
+
"source/**/*.js",
|
|
46
|
+
"test/**/*.js"
|
|
47
|
+
],
|
|
48
|
+
"watch-ignore": [
|
|
49
|
+
"lib/vendor"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/stevenvelozo/orator",
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"fable": "^3.0.73",
|
|
55
|
+
"quackage": "^1.0.17"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"fable-serviceproviderbase": "^3.0.10",
|
|
59
|
+
"find-my-way": "^7.5.0"
|
|
60
|
+
}
|
|
69
61
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
|
+
|
|
3
|
+
class OratorServiceServerBase extends libFableServiceProviderBase
|
|
2
4
|
{
|
|
3
|
-
constructor(
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
4
6
|
{
|
|
5
|
-
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
this.serviceType = 'OratorServiceServer';
|
|
8
10
|
|
|
9
11
|
this.ServiceServerType = 'Base';
|
|
10
12
|
|
|
11
|
-
this.Name = this.
|
|
13
|
+
this.Name = this.fable.settings.Product;
|
|
12
14
|
this.URL = 'BASE_SERVICE_SERVER';
|
|
13
|
-
this.Port = this.
|
|
15
|
+
this.Port = this.options.ServicePort;
|
|
14
16
|
|
|
15
17
|
this.Active = false;
|
|
16
18
|
}
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
class OratorServiceServerIPCSynthesizedResponse
|
|
2
2
|
{
|
|
3
|
-
constructor(pLog, pRequestGUID)
|
|
3
|
+
constructor(pHandler, pLog, pRequestGUID)
|
|
4
4
|
{
|
|
5
5
|
this.log = pLog;
|
|
6
6
|
|
|
7
|
+
if (pHandler.hasOwnProperty('params'))
|
|
8
|
+
{
|
|
9
|
+
this.params = pHandler.params;
|
|
10
|
+
}
|
|
11
|
+
else
|
|
12
|
+
{
|
|
13
|
+
this.params = {};
|
|
14
|
+
}
|
|
15
|
+
if (pHandler.hasOwnProperty('searchParams'))
|
|
16
|
+
{
|
|
17
|
+
this.searchParams = pHandler.searchParams;
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
{
|
|
21
|
+
this.searchParams = {};
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
this.requestGUID = pRequestGUID;
|
|
8
25
|
|
|
9
26
|
this.responseData = null;
|
|
@@ -3,27 +3,24 @@ const libOratorServiceServerBase = require('./Orator-ServiceServer-Base.js');
|
|
|
3
3
|
// A synthesized response object, for simple IPC.
|
|
4
4
|
const libOratorServiceServerIPCSynthesizedResponse = require('./Orator-ServiceServer-IPC-SynthesizedResponse.js');
|
|
5
5
|
// A simple constrainer for the find-my-way router since we aren't using any kind of headers to pass version or host
|
|
6
|
-
const libOratorServiceServerIPCCustomConstrainer = require('./Orator-ServiceServer-IPC-RouterConstrainer.js');
|
|
6
|
+
//const libOratorServiceServerIPCCustomConstrainer = require('./Orator-ServiceServer-IPC-RouterConstrainer.js');
|
|
7
7
|
|
|
8
8
|
// This library is the default router for our services
|
|
9
9
|
const libFindMyWay = require('find-my-way');
|
|
10
|
-
//const libAsync = require('async');
|
|
11
|
-
const libAsyncWaterfall = require("async/waterfall");
|
|
12
|
-
const libAsyncEachOfSeries = require('async/eachOfSeries')
|
|
13
10
|
|
|
14
11
|
class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
15
12
|
{
|
|
16
|
-
constructor(
|
|
13
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
17
14
|
{
|
|
18
|
-
|
|
15
|
+
super(pFable, pOptions, pServiceHash);
|
|
19
16
|
|
|
20
|
-
this.
|
|
21
|
-
this.router
|
|
22
|
-
this.router.addConstraintStrategy(libOratorServiceServerIPCCustomConstrainer);
|
|
17
|
+
this.router = libFindMyWay(this.options);
|
|
18
|
+
//this.router.addConstraintStrategy(libOratorServiceServerIPCCustomConstrainer);
|
|
23
19
|
|
|
24
20
|
this.ServiceServerType = 'IPC';
|
|
25
21
|
|
|
26
22
|
this.URL = 'IPC';
|
|
23
|
+
this.Port = 0;
|
|
27
24
|
|
|
28
25
|
this.preBehaviorFunctions = [];
|
|
29
26
|
this.behaviorMap = {};
|
|
@@ -32,16 +29,10 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
32
29
|
|
|
33
30
|
use(fHandlerFunction)
|
|
34
31
|
{
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
this.log.error(`IPC provider failed to map USE handler function!`);
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
this.preBehaviorFunctions.push(fHandlerFunction);
|
|
32
|
+
return this.addPreBehaviorFunction(fHandlerFunction);
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
|
|
35
|
+
addPreBehaviorFunction(fHandlerFunction)
|
|
45
36
|
{
|
|
46
37
|
if (!super.use(fHandlerFunction))
|
|
47
38
|
{
|
|
@@ -50,15 +41,24 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
50
41
|
}
|
|
51
42
|
|
|
52
43
|
this.preBehaviorFunctions.push(fHandlerFunction);
|
|
44
|
+
return true;
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
executePreBehaviorFunctions(pRequest, pResponse, fNext)
|
|
56
48
|
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
let tmpAnticipate = this.fable.serviceManager.instantiateServiceProviderWithoutRegistration('Anticipate');
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < this.preBehaviorFunctions.length; i++)
|
|
52
|
+
{
|
|
53
|
+
let tmpPreBehaviorFunction = this.preBehaviorFunctions[i];
|
|
54
|
+
tmpAnticipate.anticipate(
|
|
55
|
+
(fStageComplete) =>
|
|
56
|
+
{
|
|
57
|
+
return tmpPreBehaviorFunction(pRequest, pResponse, fStageComplete);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
tmpAnticipate.wait(
|
|
62
62
|
(pError) =>
|
|
63
63
|
{
|
|
64
64
|
if (pError)
|
|
@@ -77,16 +77,25 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
77
77
|
return false;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
this.
|
|
80
|
+
this.postBehaviorFunctions.push(fHandlerFunction);
|
|
81
|
+
return true;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
executePostBehaviorFunctions(pRequest, pResponse, fNext)
|
|
84
85
|
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
let tmpAnticipate = this.fable.serviceManager.instantiateServiceProviderWithoutRegistration('Anticipate');
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < this.postBehaviorFunctions.length; i++)
|
|
89
|
+
{
|
|
90
|
+
let tmpPostBehaviorFunction = this.postBehaviorFunctions[i];
|
|
91
|
+
tmpAnticipate.anticipate(
|
|
92
|
+
(fStageComplete) =>
|
|
93
|
+
{
|
|
94
|
+
return tmpPostBehaviorFunction(pRequest, pResponse, fStageComplete);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
tmpAnticipate.wait(
|
|
90
99
|
(pError) =>
|
|
91
100
|
{
|
|
92
101
|
if (pError)
|
|
@@ -117,52 +126,55 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
117
126
|
{
|
|
118
127
|
// We have a constrainer on IPC so we can control channels eventually, if we like.
|
|
119
128
|
// For now it just makes sure it was added with an IPC service server.
|
|
120
|
-
this.router.on(pMethod, pRoute,
|
|
121
|
-
|
|
129
|
+
this.router.on(pMethod, pRoute, this.buildFindMyWayHandler(pRouteFunctionArray));
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
buildFindMyWayHandler(pRouteFunctionArray)
|
|
134
|
+
{
|
|
135
|
+
let tmpRouteFunctionArray = pRouteFunctionArray;
|
|
136
|
+
return (
|
|
137
|
+
(pRequest, pResponse, pData) =>
|
|
122
138
|
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Added to make this mimic what we saw with route parsing in the old restify
|
|
128
|
-
pRequest.params = pParameters;
|
|
129
|
-
return fStageComplete();
|
|
130
|
-
},
|
|
131
|
-
(fStageComplete)=>
|
|
132
|
-
{
|
|
133
|
-
return this.executePreBehaviorFunctions(pRequest, pResponse, fStageComplete);
|
|
134
|
-
},
|
|
135
|
-
(fStageComplete)=>
|
|
136
|
-
{
|
|
137
|
-
libAsyncEachOfSeries(pRouteFunctionArray,
|
|
138
|
-
(fBehaviorFunction, pFunctionIndex, fCallback) =>
|
|
139
|
-
{
|
|
140
|
-
return fBehaviorFunction(pRequest, pResponse, fCallback);
|
|
141
|
-
},
|
|
142
|
-
(pBehaviorFunctionError) =>
|
|
143
|
-
{
|
|
144
|
-
if (pBehaviorFunctionError)
|
|
145
|
-
{
|
|
146
|
-
this.log.error(`IPC Provider behavior function ${pFunctionIndex} failed with error: ${pBehaviorFunctionError}`, pBehaviorFunctionError);
|
|
147
|
-
return fNext(pError);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
(fStageComplete)=>
|
|
152
|
-
{
|
|
153
|
-
return this.executePostBehaviorFunctions(pRequest, pResponse, fStageComplete);
|
|
154
|
-
}
|
|
155
|
-
],
|
|
156
|
-
(pRequestError)=>
|
|
139
|
+
let tmpAnticipate = this.fable.serviceManager.instantiateServiceProviderWithoutRegistration('Anticipate');
|
|
140
|
+
|
|
141
|
+
tmpAnticipate.anticipate(
|
|
142
|
+
(fNext)=>
|
|
157
143
|
{
|
|
158
|
-
|
|
144
|
+
return this.executePreBehaviorFunctions(pRequest, pResponse, fNext);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < tmpRouteFunctionArray.length; i++)
|
|
148
|
+
{
|
|
149
|
+
let tmpRouteFunction = tmpRouteFunctionArray[i];
|
|
150
|
+
tmpAnticipate.anticipate(
|
|
151
|
+
(fNext) =>
|
|
159
152
|
{
|
|
160
|
-
|
|
161
|
-
}
|
|
153
|
+
return tmpRouteFunction(pRequest, pResponse, fNext);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
tmpAnticipate.anticipate(
|
|
158
|
+
(fStageComplete)=>
|
|
159
|
+
{
|
|
160
|
+
return this.executePostBehaviorFunctions(pRequest, pResponse, fStageComplete);
|
|
162
161
|
});
|
|
163
|
-
});
|
|
164
162
|
|
|
165
|
-
|
|
163
|
+
return new Promise(
|
|
164
|
+
(fResolve, fReject) =>
|
|
165
|
+
{
|
|
166
|
+
tmpAnticipate.wait(
|
|
167
|
+
(pBehaviorFunctionError) =>
|
|
168
|
+
{
|
|
169
|
+
if (pBehaviorFunctionError)
|
|
170
|
+
{
|
|
171
|
+
this.log.error(`IPC Provider behavior function ${pFunctionIndex} failed with error: ${pBehaviorFunctionError}`, pBehaviorFunctionError);
|
|
172
|
+
return fReject(pBehaviorFunctionError);
|
|
173
|
+
}
|
|
174
|
+
return fResolve();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
// This is the virtualized "body parser"
|
|
@@ -191,7 +203,7 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
191
203
|
|
|
192
204
|
doDel(pRoute, ...fRouteProcessingFunctions)
|
|
193
205
|
{
|
|
194
|
-
return this.addRouteProcessor('
|
|
206
|
+
return this.addRouteProcessor('DELETE', pRoute, Array.from(fRouteProcessingFunctions));
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
doPatch(pRoute, ...fRouteProcessingFunctions)
|
|
@@ -201,7 +213,7 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
201
213
|
|
|
202
214
|
doOpts(pRoute, ...fRouteProcessingFunctions)
|
|
203
215
|
{
|
|
204
|
-
return this.addRouteProcessor('
|
|
216
|
+
return this.addRouteProcessor('OPTIONS', pRoute, Array.from(fRouteProcessingFunctions));
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
doHead(pRoute, ...fRouteProcessingFunctions)
|
|
@@ -218,33 +230,49 @@ class OratorServiceServerIPC extends libOratorServiceServerBase
|
|
|
218
230
|
// If the data is skipped and a callback is parameter 3, do the right thing
|
|
219
231
|
let tmpCallback = (typeof(fCallback) == 'function') ? fCallback :
|
|
220
232
|
(typeof(pData) == 'function') ? pData :
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
false;
|
|
234
|
+
|
|
235
|
+
if (!tmpCallback)
|
|
236
|
+
{
|
|
237
|
+
throw new Error(`IPC Provider invoke() called without a callback function.`);
|
|
238
|
+
}
|
|
223
239
|
|
|
224
240
|
// Create a bare minimum request object for IPC to pass to our router
|
|
225
241
|
let tmpRequest = (
|
|
226
242
|
{
|
|
227
243
|
method: pMethod,
|
|
228
244
|
url: pRoute,
|
|
229
|
-
guid: this.
|
|
245
|
+
guid: this.fable.getUUID()
|
|
230
246
|
});
|
|
231
247
|
|
|
248
|
+
// For now, dealing with no handler constraints.
|
|
249
|
+
let tmpHandler = this.router.find( tmpRequest.method, tmpRequest.url);
|
|
250
|
+
|
|
232
251
|
// Create a container for the IPC response data to be aggregated to from send() methodds
|
|
233
|
-
let tmpSynthesizedResponseData = new libOratorServiceServerIPCSynthesizedResponse(this.log, tmpRequest.guid);
|
|
252
|
+
let tmpSynthesizedResponseData = new libOratorServiceServerIPCSynthesizedResponse(tmpHandler, this.log, tmpRequest.guid);
|
|
253
|
+
|
|
254
|
+
// Map parsed params back to the request object
|
|
255
|
+
tmpRequest.params = tmpSynthesizedResponseData.params;
|
|
256
|
+
tmpRequest.searchParams = tmpSynthesizedResponseData.searchParams;
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
258
|
+
//params: handle._createParamsObject(params)//,
|
|
259
|
+
//searchParams: this.querystringParser(querystring)
|
|
260
|
+
|
|
261
|
+
tmpHandler.handler(tmpRequest, tmpSynthesizedResponseData, pData).then(
|
|
262
|
+
(pResults)=>
|
|
239
263
|
{
|
|
264
|
+
return tmpCallback(null, tmpSynthesizedResponseData.responseData, tmpSynthesizedResponseData, pResults);
|
|
265
|
+
},
|
|
266
|
+
(pError)=>
|
|
267
|
+
{
|
|
268
|
+
this.log.trace('IPC Response Received', {Error: pError});
|
|
240
269
|
if (pError)
|
|
241
270
|
{
|
|
242
271
|
this.log.error(`IPC Request Error Request GUID [${tmpRequest.guid}] handling route [${pRoute}]: ${pError}`, {Error: pError, Route: pRoute, Data: pData});
|
|
243
272
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
273
|
+
return tmpCallback(pError, tmpSynthesizedResponseData.responseData, tmpSynthesizedResponseData);
|
|
274
|
+
}
|
|
275
|
+
);
|
|
248
276
|
}
|
|
249
277
|
}
|
|
250
278
|
|