noxt-server 0.1.13 → 0.1.14
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/noxt-server.js +2 -1
- package/package.json +3 -3
- package/units/config.js +12 -7
- package/units/env.js +3 -3
- package/units/express.js +3 -1
- package/units/fetch-node.js +0 -13
- package/units/fetch.js +1 -1
- package/units/plugin.js +1 -1
- package/units/utils.js +8 -8
- package/units.txt +848 -0
package/noxt-server.js
CHANGED
|
@@ -28,8 +28,9 @@ export async function startServer({ config, recipe }) {
|
|
|
28
28
|
const mlm = mlmInstance.context;
|
|
29
29
|
await mlm.services.config.merge(config);
|
|
30
30
|
console.log(mlm.config);
|
|
31
|
-
|
|
31
|
+
mlmInstance.start();
|
|
32
32
|
|
|
33
|
+
mlmInstance.repl();
|
|
33
34
|
} catch (e) {
|
|
34
35
|
console.log(await mlmInstance.analyze(recipe ?? 'noxt-dev'));
|
|
35
36
|
console.error(e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noxt-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Server for noxt-js-middleware with CLI and config support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "noxt-server.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"author": "Zoran Obradović [https://github.com/zocky]",
|
|
24
24
|
"license": "GPL-3.0-or-later",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"noxt-js-middleware": "^1.0.
|
|
27
|
-
"mlm-core": "^1.0.
|
|
26
|
+
"noxt-js-middleware": "^1.0.6",
|
|
27
|
+
"mlm-core": "^1.0.6",
|
|
28
28
|
"express": "^5.0.0",
|
|
29
29
|
"cookie-parser": "^1.4.6",
|
|
30
30
|
"js-yaml": "^4.1.0",
|
package/units/config.js
CHANGED
|
@@ -5,6 +5,7 @@ export const info = {
|
|
|
5
5
|
requires: ['utils','services'],
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
const config = {};
|
|
8
9
|
const config_is = {};
|
|
9
10
|
const config_defaults = {};
|
|
10
11
|
const config_defs = {}
|
|
@@ -54,7 +55,11 @@ function isPlainObject(value) {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
export default mlm => ({
|
|
57
|
-
'define.config':
|
|
58
|
+
'define.config': {
|
|
59
|
+
get: () => config,
|
|
60
|
+
enumerable: true,
|
|
61
|
+
configurable: false
|
|
62
|
+
},
|
|
58
63
|
'register.config': (defs, unit) => {
|
|
59
64
|
for (const key in defs) {
|
|
60
65
|
const def = defs[key];
|
|
@@ -78,8 +83,8 @@ export default mlm => ({
|
|
|
78
83
|
},
|
|
79
84
|
'utils.merge_deep': merge_deep,
|
|
80
85
|
'services.config': () => new class ConfigService {
|
|
81
|
-
process(
|
|
82
|
-
const merged = merge_deep({}, config_defaults,
|
|
86
|
+
process(userConfig) {
|
|
87
|
+
const merged = merge_deep({}, config_defaults, userConfig);
|
|
83
88
|
const ret = {};
|
|
84
89
|
for (const key in config_defs) {
|
|
85
90
|
const { is: type, normalize } = config_defs[key];
|
|
@@ -94,10 +99,10 @@ export default mlm => ({
|
|
|
94
99
|
}
|
|
95
100
|
return ret;
|
|
96
101
|
}
|
|
97
|
-
merge(
|
|
98
|
-
|
|
99
|
-
Object.assign(
|
|
100
|
-
|
|
102
|
+
merge(userConfig) {
|
|
103
|
+
console.log('initial',config_defaults)
|
|
104
|
+
Object.assign(config, this.process(userConfig));
|
|
105
|
+
console.log(userConfig,'final',config)
|
|
101
106
|
}
|
|
102
107
|
get_defs() {
|
|
103
108
|
return {
|
package/units/env.js
CHANGED
|
@@ -3,12 +3,12 @@ export const info = {
|
|
|
3
3
|
version: '1.0.0',
|
|
4
4
|
description: 'Environment',
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
export default mlm => ({
|
|
8
8
|
'define.DEV': () => process.env.NODE_ENV !== 'production',
|
|
9
9
|
'define.PROD': () => process.env.NODE_ENV === 'production',
|
|
10
10
|
'onBeforeLoad': () => {
|
|
11
|
-
|
|
11
|
+
process.env.NODE_ENV ||= 'development'
|
|
12
12
|
}
|
|
13
13
|
})
|
|
14
|
-
|
|
14
|
+
|
package/units/express.js
CHANGED
package/units/fetch-node.js
CHANGED
|
@@ -11,19 +11,6 @@ export default mlm => ({
|
|
|
11
11
|
requires: ['noxt-plugin'],
|
|
12
12
|
provides: ['#fetch'],
|
|
13
13
|
description: 'Native fetch',
|
|
14
|
-
'config.fetch': {
|
|
15
|
-
is: {
|
|
16
|
-
ttl: 'integer|none',
|
|
17
|
-
retry: 'positiveInteger|none',
|
|
18
|
-
timeout: 'positiveInteger|none'
|
|
19
|
-
},
|
|
20
|
-
default: {
|
|
21
|
-
ttl: 60 * 60 * 1000,
|
|
22
|
-
retry: 2,
|
|
23
|
-
timeout: 5000
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
|
|
27
14
|
'serverContext.fetch': () => fetchOrThrow,
|
|
28
15
|
});
|
|
29
16
|
|
package/units/fetch.js
CHANGED
|
@@ -8,7 +8,7 @@ export default mlm => ({
|
|
|
8
8
|
'componentExports.fetch': async ({exported, props, ctx}) => {
|
|
9
9
|
for (const id in exported) {
|
|
10
10
|
let url = await mlm.utils.eval(exported[id], props, ctx);
|
|
11
|
-
|
|
11
|
+
// mlm.log('fetching', id, url);
|
|
12
12
|
try {
|
|
13
13
|
const now = performance.now();
|
|
14
14
|
const res = await ctx.fetch(url);
|
package/units/plugin.js
CHANGED
package/units/utils.js
CHANGED
|
@@ -7,16 +7,16 @@ export const info = {
|
|
|
7
7
|
export default mlm => {
|
|
8
8
|
|
|
9
9
|
const utils = {};
|
|
10
|
-
utils.eval = (fn
|
|
10
|
+
utils.eval = (fn, ...args) => typeof fn === 'function' ? fn(...args) : fn;
|
|
11
11
|
utils.readOnly = (obj, { label = 'object' } = {}) => new Proxy(obj, {
|
|
12
12
|
get: (t, k) => t[k],
|
|
13
13
|
set: (t, k, v) => { mlm.throw(label + ' is read-only'); }
|
|
14
14
|
});
|
|
15
|
-
utils.collector = (target, {
|
|
16
|
-
map, is, filter,
|
|
17
|
-
label = 'object',
|
|
18
|
-
mode = 'object',
|
|
19
|
-
override = false
|
|
15
|
+
utils.collector = (target, {
|
|
16
|
+
map, is, filter,
|
|
17
|
+
label = 'object',
|
|
18
|
+
mode = 'object',
|
|
19
|
+
override = false
|
|
20
20
|
} = {}) => {
|
|
21
21
|
const modes = {
|
|
22
22
|
array: () => source => {
|
|
@@ -34,7 +34,7 @@ export default mlm => {
|
|
|
34
34
|
}
|
|
35
35
|
let value = source[key];
|
|
36
36
|
if (filter && !filter(value, key)) continue;
|
|
37
|
-
if (is) mlm.assert.is(
|
|
37
|
+
if (is) mlm.assert.is(is, value, label + '.' + key);
|
|
38
38
|
if (map) value = map(value, key);
|
|
39
39
|
target[key] = value;
|
|
40
40
|
}
|
|
@@ -73,6 +73,6 @@ export default mlm => {
|
|
|
73
73
|
}
|
|
74
74
|
return ({
|
|
75
75
|
'define.utils': () => utils.readOnly(utils, 'utils'),
|
|
76
|
-
'register.utils': utils.collector(utils, { is: 'function', mode: 'object' }),
|
|
76
|
+
'register.utils': utils.collector(utils, { is: 'function', mode: 'object', map: (fn, key) => { mlm.log('utils', key); return fn; } }),
|
|
77
77
|
})
|
|
78
78
|
}
|
package/units.txt
ADDED
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
------
|
|
2
|
+
fetch.js
|
|
3
|
+
-----
|
|
4
|
+
export const info = {
|
|
5
|
+
version: '1.0.0',
|
|
6
|
+
description: 'Preload props from urls',
|
|
7
|
+
requires: ['noxt-plugin','#fetch'],
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default mlm => ({
|
|
11
|
+
'componentExports.fetch': async ({exported, props, ctx}) => {
|
|
12
|
+
for (const id in exported) {
|
|
13
|
+
let url = await mlm.utils.eval(exported[id], props, ctx);
|
|
14
|
+
// mlm.log('fetching', id, url);
|
|
15
|
+
try {
|
|
16
|
+
const now = performance.now();
|
|
17
|
+
const res = await ctx.fetch(url);
|
|
18
|
+
mlm.log('fetched', id, url, (performance.now() - now).toFixed(3) +'ms');
|
|
19
|
+
props[id] = await res.json();
|
|
20
|
+
//mlm.log('fetched', url, JSON.stringify(props[id]).length);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
23
|
+
mlm.throw(new Error('error fetching ' + url + ': ' + e));
|
|
24
|
+
}
|
|
25
|
+
mlm.error('fetch', url, e);
|
|
26
|
+
props[id] = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
------
|
|
32
|
+
config.js
|
|
33
|
+
-----
|
|
34
|
+
export const info = {
|
|
35
|
+
name: 'config',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
description: 'Config ',
|
|
38
|
+
requires: ['utils','services'],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const config_is = {};
|
|
42
|
+
const config_defaults = {};
|
|
43
|
+
const config_defs = {}
|
|
44
|
+
|
|
45
|
+
function merge_deep(target, ...sources) {
|
|
46
|
+
// If no sources, return target
|
|
47
|
+
if (sources.length === 0) return target;
|
|
48
|
+
|
|
49
|
+
// If target is not a plain object, return the first source (overwrite)
|
|
50
|
+
if (!isPlainObject(target)) {
|
|
51
|
+
return sources[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = Object.assign({}, target);
|
|
55
|
+
|
|
56
|
+
for (const source of sources) {
|
|
57
|
+
// If source is not a plain object, skip (treat as scalar)
|
|
58
|
+
if (!isPlainObject(source)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
63
|
+
const targetValue = result[key];
|
|
64
|
+
|
|
65
|
+
// If both values are plain objects, merge recursively
|
|
66
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
67
|
+
result[key] = merge_deep(targetValue, sourceValue);
|
|
68
|
+
}
|
|
69
|
+
// Otherwise, overwrite target with source value
|
|
70
|
+
else {
|
|
71
|
+
result[key] = sourceValue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Helper function to check if value is a plain object
|
|
80
|
+
function isPlainObject(value) {
|
|
81
|
+
if (value === null || typeof value !== 'object') {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const proto = Object.getPrototypeOf(value);
|
|
86
|
+
return proto === null || proto === Object.prototype;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default mlm => ({
|
|
90
|
+
'$global.config': () => ({}),
|
|
91
|
+
'$define.config': (defs, unit) => {
|
|
92
|
+
for (const key in defs) {
|
|
93
|
+
const def = defs[key];
|
|
94
|
+
mlm.assert.not(key in config_defs, 'Duplicate config key ' + key);
|
|
95
|
+
mlm.assert.is({
|
|
96
|
+
is: 'any|none',
|
|
97
|
+
default: 'any|none',
|
|
98
|
+
normalize: 'function|none'
|
|
99
|
+
}, def, `config.${key}`);
|
|
100
|
+
|
|
101
|
+
if ('default' in def) {
|
|
102
|
+
config_defaults[key] = mlm.utils.eval(def.default);
|
|
103
|
+
}
|
|
104
|
+
config_defs[key] = {
|
|
105
|
+
normalize: def.normalize,
|
|
106
|
+
is: def.is,
|
|
107
|
+
default: def.default,
|
|
108
|
+
unit: unit
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
'utils.merge_deep': merge_deep,
|
|
113
|
+
'services.config': () => new class ConfigService {
|
|
114
|
+
process(config) {
|
|
115
|
+
const merged = merge_deep({}, config_defaults, config);
|
|
116
|
+
const ret = {};
|
|
117
|
+
for (const key in config_defs) {
|
|
118
|
+
const { is: type, normalize } = config_defs[key];
|
|
119
|
+
if (key in merged) {
|
|
120
|
+
if (normalize) {
|
|
121
|
+
merged[key] = normalize(merged[key]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mlm.assert.is(type, merged[key], `config.${key}`);
|
|
125
|
+
ret[key] = merged[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return ret;
|
|
129
|
+
}
|
|
130
|
+
merge(config) {
|
|
131
|
+
mlm.log('config', config,mlm.config);
|
|
132
|
+
Object.assign(mlm.config, this.process(config));
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
get_defs() {
|
|
136
|
+
return {
|
|
137
|
+
config_defs,
|
|
138
|
+
config_is,
|
|
139
|
+
config_defaults
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
------
|
|
145
|
+
fetch-cache.js
|
|
146
|
+
-----
|
|
147
|
+
export const info = {
|
|
148
|
+
name: 'fetch-cache',
|
|
149
|
+
version: '1.0.0',
|
|
150
|
+
requires: ['noxt-plugin'],
|
|
151
|
+
provides: ['#fetch'],
|
|
152
|
+
description: 'Cached fetch',
|
|
153
|
+
npm: {
|
|
154
|
+
'node-fetch-cache': '^1.0.0',
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let fetchCache = null;
|
|
159
|
+
|
|
160
|
+
export default mlm => ({
|
|
161
|
+
'config.fetch': {
|
|
162
|
+
is: {
|
|
163
|
+
ttl: 'integer|none',
|
|
164
|
+
retry: 'positiveInteger|none',
|
|
165
|
+
timeout: 'positiveInteger|none'
|
|
166
|
+
},
|
|
167
|
+
default: {
|
|
168
|
+
ttl: 60 * 60 * 1000,
|
|
169
|
+
retry: 2,
|
|
170
|
+
timeout: 5000
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
'pageContext.fetch': (props, ctx) => globalFetch.bind(null, ctx),
|
|
175
|
+
'serverContext.fetch': () => globalFetch,
|
|
176
|
+
|
|
177
|
+
async onStart() {
|
|
178
|
+
const { default: nodeFetchCache } = await mlm.import('node-fetch-cache');
|
|
179
|
+
const cfg = mlm.config.fetch;
|
|
180
|
+
fetchCache = await nodeFetchCache.create({
|
|
181
|
+
cache: undefined,
|
|
182
|
+
ttl: cfg.ttl,
|
|
183
|
+
retry: cfg.retry,
|
|
184
|
+
timeout: cfg.timeout
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
async function globalFetch(ctx, url, options = {}) {
|
|
190
|
+
if (ctx.req.headers.pragma === 'no-cache') options.cache = 'no-cache';
|
|
191
|
+
const res = await fetchCache(url, options);
|
|
192
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
193
|
+
return res;
|
|
194
|
+
}
|
|
195
|
+
------
|
|
196
|
+
services.js
|
|
197
|
+
-----
|
|
198
|
+
export const info = {
|
|
199
|
+
name: 'services',
|
|
200
|
+
version: '1.0.0',
|
|
201
|
+
description: 'Services',
|
|
202
|
+
requires: ['utils']
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const services = {}
|
|
206
|
+
|
|
207
|
+
export default mlm => ({
|
|
208
|
+
'$global.services': () => mlm.utils.readOnly(services, 'services'),
|
|
209
|
+
'$define.services': mlm.utils.collector(services, {
|
|
210
|
+
is: 'function',
|
|
211
|
+
mode: 'object',
|
|
212
|
+
map: (fn, key) => {
|
|
213
|
+
const service = fn(mlm)
|
|
214
|
+
mlm.assert.is.object(service, 'service');
|
|
215
|
+
return service
|
|
216
|
+
}
|
|
217
|
+
}),
|
|
218
|
+
})
|
|
219
|
+
------
|
|
220
|
+
noxt.js
|
|
221
|
+
-----
|
|
222
|
+
export const info = {
|
|
223
|
+
name: 'noxt',
|
|
224
|
+
version: '1.0.0',
|
|
225
|
+
description: 'Noxt server',
|
|
226
|
+
requires: [
|
|
227
|
+
'express',
|
|
228
|
+
'logger',
|
|
229
|
+
'static',
|
|
230
|
+
'noxt-router',
|
|
231
|
+
'fetch-cache-fs',
|
|
232
|
+
'fetch',
|
|
233
|
+
],
|
|
234
|
+
}
|
|
235
|
+
------
|
|
236
|
+
utils.js
|
|
237
|
+
-----
|
|
238
|
+
export const info = {
|
|
239
|
+
name: 'utils',
|
|
240
|
+
version: '1.0.0',
|
|
241
|
+
description: 'Utils',
|
|
242
|
+
//requires: ['env']
|
|
243
|
+
}
|
|
244
|
+
export default mlm => {
|
|
245
|
+
|
|
246
|
+
const utils = {};
|
|
247
|
+
utils.eval = (fn,...args) => typeof fn === 'function' ? fn(...args) : fn;
|
|
248
|
+
utils.readOnly = (obj, { label = 'object' } = {}) => new Proxy(obj, {
|
|
249
|
+
get: (t, k) => t[k],
|
|
250
|
+
set: (t, k, v) => { mlm.throw(label + ' is read-only'); }
|
|
251
|
+
});
|
|
252
|
+
utils.collector = (target, {
|
|
253
|
+
map, is, filter,
|
|
254
|
+
label = 'object',
|
|
255
|
+
mode = 'object',
|
|
256
|
+
override = false
|
|
257
|
+
} = {}) => {
|
|
258
|
+
const modes = {
|
|
259
|
+
array: () => source => {
|
|
260
|
+
for (let value of source) {
|
|
261
|
+
if (filter && !filter(value)) continue;
|
|
262
|
+
if (is) mlm.assert.is(is, value, label);
|
|
263
|
+
if (map) value = map(value);
|
|
264
|
+
target.push(value);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
object: () => source => {
|
|
268
|
+
for (const key in source) {
|
|
269
|
+
if (!override && key in target) {
|
|
270
|
+
mlm.throw('Duplicate key' + key + ' in ' + label);
|
|
271
|
+
}
|
|
272
|
+
let value = source[key];
|
|
273
|
+
if (filter && !filter(value, key)) continue;
|
|
274
|
+
if (is) mlm.assert.is( is,value, label + '.' + key);
|
|
275
|
+
if (map) value = map(value, key);
|
|
276
|
+
target[key] = value;
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
arrays: () => source => {
|
|
280
|
+
for (const key in source) {
|
|
281
|
+
let values = [source[key]].flat(Infinity);
|
|
282
|
+
target[key] ??= [];
|
|
283
|
+
for (let value of values) {
|
|
284
|
+
if (filter && !filter(value, key)) continue;
|
|
285
|
+
if (is) mlm.assert.is(is, value, label + '.' + key);
|
|
286
|
+
if (map) value = map(value, key);
|
|
287
|
+
target[key].push(value);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
directory: () => source => {
|
|
292
|
+
for (const key in source) {
|
|
293
|
+
let values = source[key];
|
|
294
|
+
target[key] ??= {};
|
|
295
|
+
for (const id in values) {
|
|
296
|
+
if (!override && id in target[key]) {
|
|
297
|
+
mlm.throw('Duplicate key' + id + ' in ' + label + '.' + key);
|
|
298
|
+
}
|
|
299
|
+
let value = values[id];
|
|
300
|
+
if (filter && !filter(value, key)) continue;
|
|
301
|
+
if (is) mlm.assert.is(is, value, label + '.' + key);
|
|
302
|
+
if (map) value = map(value, key);
|
|
303
|
+
target[key][id] = value;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
mlm.assert(mode in modes, 'Unknown mode ' + mode);
|
|
309
|
+
return modes[mode]();
|
|
310
|
+
}
|
|
311
|
+
return ({
|
|
312
|
+
'$global.utils': () => utils.readOnly(utils, 'utils'),
|
|
313
|
+
'$define.utils': utils.collector(utils, { is: 'function', mode: 'object' }),
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
------
|
|
317
|
+
fetch-node.js
|
|
318
|
+
-----
|
|
319
|
+
export const info = {
|
|
320
|
+
name: 'fetch-node',
|
|
321
|
+
version: '1.0.0',
|
|
322
|
+
description: 'Native fetch',
|
|
323
|
+
requires: ['noxt-plugin'],
|
|
324
|
+
provides: ['#fetch'],
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export default mlm => ({
|
|
328
|
+
name: 'fetch-node',
|
|
329
|
+
requires: ['noxt-plugin'],
|
|
330
|
+
provides: ['#fetch'],
|
|
331
|
+
description: 'Native fetch',
|
|
332
|
+
'serverContext.fetch': () => fetchOrThrow,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
async function fetchOrThrow(ctx, url, options = {}) {
|
|
336
|
+
const res = await fetch(url, options);
|
|
337
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
338
|
+
return res;
|
|
339
|
+
}
|
|
340
|
+
------
|
|
341
|
+
plugin.js
|
|
342
|
+
-----
|
|
343
|
+
export const info = {
|
|
344
|
+
name: 'plugin',
|
|
345
|
+
version: '1.0.0',
|
|
346
|
+
description: 'Base',
|
|
347
|
+
requires: ['utils','services','hooks','config']
|
|
348
|
+
}
|
|
349
|
+
------
|
|
350
|
+
reload.js
|
|
351
|
+
-----
|
|
352
|
+
export const info = {
|
|
353
|
+
name: 'reload',
|
|
354
|
+
description: 'Hot reload middleware',
|
|
355
|
+
requires: ['noxt-plugin'],
|
|
356
|
+
npm: {
|
|
357
|
+
'express': '^5.0.0',
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const clients = new Set();
|
|
362
|
+
export default mlm => ({
|
|
363
|
+
'pageContext.reload': ({ ctx }) => ctx.slot('script', 'noxt-reload', '/_reload.js'),
|
|
364
|
+
'middleware.reload': async () => {
|
|
365
|
+
const { default: express } = await mlm.import('express');
|
|
366
|
+
const router = express.Router();
|
|
367
|
+
router.get('/_reload.js', (req, res) => {
|
|
368
|
+
res.set('Content-Type', 'text/javascript');
|
|
369
|
+
res.send(reloadJs);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
router.get('/_events', (req, res) => {
|
|
373
|
+
res.writeHead(200, {
|
|
374
|
+
'Content-Type': 'text/event-stream',
|
|
375
|
+
'Cache-Control': 'no-cache',
|
|
376
|
+
'Connection': 'keep-alive',
|
|
377
|
+
'content-encoding': 'none'
|
|
378
|
+
});
|
|
379
|
+
res.write('\nevent:connected\ndata:\n\n');
|
|
380
|
+
clients.add(res);
|
|
381
|
+
res.on('close', () => {
|
|
382
|
+
clients.delete(res);
|
|
383
|
+
res.end();
|
|
384
|
+
})
|
|
385
|
+
req.on('error', () => clients.delete(res));
|
|
386
|
+
res.on('error', () => clients.delete(res));
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return router
|
|
390
|
+
},
|
|
391
|
+
onStart() {
|
|
392
|
+
process.on('SIGUSR2', handleSignal)
|
|
393
|
+
},
|
|
394
|
+
onStop() {
|
|
395
|
+
process.removeListener('SIGUSR2', handleSignal)
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
async function handleSignal() {
|
|
400
|
+
const promises = Array.from(clients).map(async client => {
|
|
401
|
+
client.write('event: reload\ndata:\n\n');
|
|
402
|
+
return new Promise(resolve => client.end(resolve));
|
|
403
|
+
});
|
|
404
|
+
await Promise.all(promises);
|
|
405
|
+
process.exit(0);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const reloadJs = `
|
|
409
|
+
const eventSource = new EventSource('/_events');
|
|
410
|
+
let reload = false;
|
|
411
|
+
eventSource.addEventListener('connected', () => {
|
|
412
|
+
if (reload) {
|
|
413
|
+
console.log('%c NOXT ','color: #ffd; background-color: #080', 'Reloading...' );
|
|
414
|
+
window.location.reload();
|
|
415
|
+
} else {
|
|
416
|
+
console.log('%c NOXT ', 'color: #ffd; background-color: #080', 'Connected.' );
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
});
|
|
420
|
+
eventSource.addEventListener('reload', () => {
|
|
421
|
+
console.log('%c NOXT ','color: #ffd; background-color: #080', 'Restarting...' );
|
|
422
|
+
reload = true;
|
|
423
|
+
});
|
|
424
|
+
window.addEventListener('beforeunload', () => {
|
|
425
|
+
eventSource.close();
|
|
426
|
+
});
|
|
427
|
+
`
|
|
428
|
+
------
|
|
429
|
+
logger.js
|
|
430
|
+
-----
|
|
431
|
+
export const info = {
|
|
432
|
+
name: 'logger',
|
|
433
|
+
version: '1.0.0',
|
|
434
|
+
description: 'Minimal logger',
|
|
435
|
+
requires: ['express'],
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export default mlm => ({
|
|
439
|
+
'middleware.logger': async (app) => {
|
|
440
|
+
return (req, res, next) => {
|
|
441
|
+
mlm.log(req.method, req.url);
|
|
442
|
+
next();
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
})
|
|
446
|
+
------
|
|
447
|
+
express.js
|
|
448
|
+
-----
|
|
449
|
+
export const info = {
|
|
450
|
+
name: 'express',
|
|
451
|
+
description: 'Sets up an Express server',
|
|
452
|
+
requires: ['plugin'],
|
|
453
|
+
npm: {
|
|
454
|
+
'express': '^5.0.0',
|
|
455
|
+
'cookie-parser': '^1.4.6'
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let app;
|
|
460
|
+
const middlewareNames = new Set();
|
|
461
|
+
const middlewares = [];
|
|
462
|
+
export default mlm => ({
|
|
463
|
+
'$global.express': { get: () => app },
|
|
464
|
+
'$define.middleware': (conf, unit) => {
|
|
465
|
+
for (const key in conf) {
|
|
466
|
+
mlm.assert.not(key in middlewareNames, 'Duplicate middleware ' + key);
|
|
467
|
+
middlewareNames.add(key);
|
|
468
|
+
let c = conf[key];
|
|
469
|
+
if (mlm.is.function(c)) {
|
|
470
|
+
c = { create: c };
|
|
471
|
+
}
|
|
472
|
+
mlm.assert.is({
|
|
473
|
+
path: 'string|none',
|
|
474
|
+
create: 'function'
|
|
475
|
+
}, c, 'middleware');
|
|
476
|
+
mlm.assert.is.function(c.create, 'middleware');
|
|
477
|
+
middlewares.push({ ...c, unit: unit.name });
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
'config.port': {
|
|
481
|
+
is: v => Number.isInteger(v) && v > 0 && v < 65536,
|
|
482
|
+
default: 3000
|
|
483
|
+
},
|
|
484
|
+
'config.host': {
|
|
485
|
+
is: 'string',
|
|
486
|
+
default: 'localhost'
|
|
487
|
+
},
|
|
488
|
+
'middleware.json': async (app) => {
|
|
489
|
+
const { default: express } = await mlm.import('express');
|
|
490
|
+
return express.json();
|
|
491
|
+
},
|
|
492
|
+
'middleware.urlencoded': async (app) => {
|
|
493
|
+
const { default: express } = await mlm.import('express');
|
|
494
|
+
return express.urlencoded({ extended: true });
|
|
495
|
+
},
|
|
496
|
+
async onStart() {
|
|
497
|
+
const { default: express } = await mlm.import('express');
|
|
498
|
+
app = express();
|
|
499
|
+
for (const middleware of middlewares) {
|
|
500
|
+
const mw = await middleware.create(app);
|
|
501
|
+
if (middleware.path) {
|
|
502
|
+
app.use(middleware.path, mw);
|
|
503
|
+
} else {
|
|
504
|
+
app.use(mw);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
app.listen(mlm.config.port, mlm.config.host, () => {
|
|
508
|
+
mlm.log(`Listening on ${mlm.config.host}:${mlm.config.port}`);
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
})
|
|
512
|
+
------
|
|
513
|
+
noxt-dev.js
|
|
514
|
+
-----
|
|
515
|
+
export const info = {
|
|
516
|
+
name: 'noxt-dev',
|
|
517
|
+
description: 'Noxt Dev server',
|
|
518
|
+
requires: [
|
|
519
|
+
'express',
|
|
520
|
+
'logger',
|
|
521
|
+
'static',
|
|
522
|
+
'noxt-router-dev',
|
|
523
|
+
'reload',
|
|
524
|
+
'fetch-cache-fs',
|
|
525
|
+
'fetch',
|
|
526
|
+
],
|
|
527
|
+
}
|
|
528
|
+
------
|
|
529
|
+
fetch-cache-fs.js
|
|
530
|
+
-----
|
|
531
|
+
export const info = {
|
|
532
|
+
name: 'fetch-cache-fs',
|
|
533
|
+
version: '1.0.0',
|
|
534
|
+
description: 'Cached fetch with filesystem cache',
|
|
535
|
+
requires: ['noxt-plugin'],
|
|
536
|
+
provides: ['#fetch'],
|
|
537
|
+
npm: {
|
|
538
|
+
'node-fetch-cache': '^1.0.0',
|
|
539
|
+
},
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
let fetchCache = null;
|
|
543
|
+
|
|
544
|
+
export default mlm => ({
|
|
545
|
+
'config.fetch': {
|
|
546
|
+
is: {
|
|
547
|
+
cacheDir: 'string',
|
|
548
|
+
ttl: 'integer|none',
|
|
549
|
+
enabled: 'boolean',
|
|
550
|
+
retry: 'positiveInteger',
|
|
551
|
+
timeout: 'positiveInteger'
|
|
552
|
+
},
|
|
553
|
+
default: {
|
|
554
|
+
enabled: true,
|
|
555
|
+
cacheDir: '.cache/fetch-cache',
|
|
556
|
+
ttl: 60 * 60 * 1000,
|
|
557
|
+
retry: 2,
|
|
558
|
+
timeout: 5000
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
'pageContext.fetch': ({ ctx }) => globalFetch.bind(null, ctx),
|
|
562
|
+
'serverContext.fetch': () => globalFetch,
|
|
563
|
+
'onStart': async () => {
|
|
564
|
+
const { default: nodeFetchCache, FileSystemCache } = await mlm.import('node-fetch-cache');
|
|
565
|
+
const cfg = mlm.config.fetch;
|
|
566
|
+
fetchCache = nodeFetchCache.create({
|
|
567
|
+
cache: cfg.enabled ? new FileSystemCache({ cacheDirectory: cfg.cacheDir }) : undefined,
|
|
568
|
+
ttl: cfg.ttl,
|
|
569
|
+
retry: cfg.retry,
|
|
570
|
+
timeout: cfg.timeout
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
async function globalFetch(ctx, url, options = {}) {
|
|
576
|
+
let res = await fetchCache(url, options);
|
|
577
|
+
if (ctx.req.headers.pragma === 'no-cache' && !res.isCacheMiss) {
|
|
578
|
+
console.log('reload', url);
|
|
579
|
+
res.ejectFromCache();
|
|
580
|
+
res = await fetch(url, options);
|
|
581
|
+
}
|
|
582
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
583
|
+
return res;
|
|
584
|
+
}
|
|
585
|
+
------
|
|
586
|
+
noxt-plugin.js
|
|
587
|
+
-----
|
|
588
|
+
export const info = {
|
|
589
|
+
name: 'noxt-plugin',
|
|
590
|
+
description: 'Noxt Plugin',
|
|
591
|
+
requires: ['express','#noxt-router'],
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const pageContextHooks = {};
|
|
595
|
+
const serverContextHooks = {};
|
|
596
|
+
const componentExportsHooks = {};
|
|
597
|
+
const registerPageHooks = {};
|
|
598
|
+
const registerComponentHooks = {};
|
|
599
|
+
|
|
600
|
+
export default mlm => {
|
|
601
|
+
|
|
602
|
+
const collector = (coll, desc) => (conf, unit) => {
|
|
603
|
+
for (const key in conf) {
|
|
604
|
+
const fn = conf[key];
|
|
605
|
+
mlm.assert.is.function(fn, desc);
|
|
606
|
+
mlm.assert.not(key in coll, 'Duplicate ' + desc + ' ' + key);
|
|
607
|
+
coll[key] = ({ fn, unit: unit.name });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const runner = (
|
|
612
|
+
coll, each = (_, fn, ...args) => fn(...args)
|
|
613
|
+
) => async (...args) => {
|
|
614
|
+
for (const key in coll) {
|
|
615
|
+
const { fn } = coll[key];
|
|
616
|
+
await each(key, fn, ...args);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const arrayCollector = (coll, desc) => (conf, unit) => {
|
|
621
|
+
for (const key in conf) {
|
|
622
|
+
const fn = conf[key];
|
|
623
|
+
mlm.assert.is.function(fn, desc);
|
|
624
|
+
coll[key] ??= [];
|
|
625
|
+
coll[key].push({ fn, unit: unit.name });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const arrayRunner = (
|
|
630
|
+
coll, each = (_, fn, ...args) => fn(...args)
|
|
631
|
+
) => async (...args) => {
|
|
632
|
+
for (const key in coll) {
|
|
633
|
+
const handlers = coll[key];
|
|
634
|
+
for (const handler of handlers) {
|
|
635
|
+
await each(key, handler.fn, ...args);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const globalServerContext = {};
|
|
640
|
+
return ({
|
|
641
|
+
'$define.pageContext': arrayCollector(pageContextHooks, 'page context handler'),
|
|
642
|
+
'$define.serverContext': arrayCollector(serverContextHooks, 'server context handler'),
|
|
643
|
+
'$define.componentExports': arrayCollector(componentExportsHooks, 'page export handler'),
|
|
644
|
+
'$define.registerPage': collector(registerPageHooks, 'register page handler'),
|
|
645
|
+
'$define.registerComponent': collector(registerComponentHooks, 'register component handler'),
|
|
646
|
+
'$global.noxt_context': {
|
|
647
|
+
get: () => globalServerContext,
|
|
648
|
+
enumerable: true
|
|
649
|
+
},
|
|
650
|
+
'serverContext.utils': () => mlm.utils,
|
|
651
|
+
'serverContext.DEV': () => mlm.DEV,
|
|
652
|
+
'serverContext.PROD': () => mlm.PROD,
|
|
653
|
+
onStart: async () => {
|
|
654
|
+
mlm.services.noxt.noxt_context({ ctx: globalServerContext });
|
|
655
|
+
//mlm.log('ctx', mlm.serverContext);
|
|
656
|
+
},
|
|
657
|
+
'services.noxt': () => new class PluginService {
|
|
658
|
+
noxt_context = arrayRunner(serverContextHooks,
|
|
659
|
+
async (key, fn, { ctx }) => ctx[key] = await fn({ ctx })
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
noxt_hooks = {
|
|
663
|
+
beforeRequest: [
|
|
664
|
+
({ ctx }) => {
|
|
665
|
+
Object.assign(ctx, mlm.noxt_context);
|
|
666
|
+
},
|
|
667
|
+
arrayRunner(pageContextHooks,
|
|
668
|
+
async (key, fn, args) => {
|
|
669
|
+
args.ctx[key] = await fn(args)
|
|
670
|
+
}
|
|
671
|
+
),
|
|
672
|
+
],
|
|
673
|
+
beforeRender: arrayRunner(componentExportsHooks,
|
|
674
|
+
async (key, fn, { module, props, ctx }) => {
|
|
675
|
+
//mlm.log('beforeRender', unit, key, Object.keys(props));
|
|
676
|
+
if (!(key in module)) return;
|
|
677
|
+
const exported = module[key];
|
|
678
|
+
await fn({ exported, props, ctx, module })
|
|
679
|
+
//mlm.log('beforeRender', key, Object.keys(props));
|
|
680
|
+
}
|
|
681
|
+
),
|
|
682
|
+
registerPage: runner(registerPageHooks,
|
|
683
|
+
async (key, fn, { module, component }) => {
|
|
684
|
+
await fn({ module, component })
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
registerComponent: runner(registerComponentHooks,
|
|
688
|
+
async (key, fn, { module, component }) => {
|
|
689
|
+
await fn({ module, component })
|
|
690
|
+
}
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
report() {
|
|
695
|
+
return {
|
|
696
|
+
pageContext: pageContextHooks,
|
|
697
|
+
serverContext: serverContextHooks,
|
|
698
|
+
componentExports: componentExportsHooks,
|
|
699
|
+
registerPage: registerPageHooks,
|
|
700
|
+
registerComponent: registerComponentHooks
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
'registerPage.Link'({ component, module }) {
|
|
705
|
+
const As = module.Link ?? 'a';
|
|
706
|
+
component.Link = ({ text, children, attrs, ...props }, ctx) => {
|
|
707
|
+
const href = component.getRoutePath(props, ctx);
|
|
708
|
+
return { type: As, props: { ...attrs, href, children: text ?? children } };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
------
|
|
714
|
+
noxt-router-dev.js
|
|
715
|
+
-----
|
|
716
|
+
export const info = {
|
|
717
|
+
name: 'noxt-router-dev',
|
|
718
|
+
description: 'Sets up a Noxt Router',
|
|
719
|
+
requires: ['plugin'],
|
|
720
|
+
provides: ['#noxt-router'],
|
|
721
|
+
npm: {
|
|
722
|
+
'noxt-js-middleware': '^1.0.4'
|
|
723
|
+
},
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export default mlm => ({
|
|
727
|
+
'config.views': {
|
|
728
|
+
normalize: p => [p].flat(),
|
|
729
|
+
is: ['string'],
|
|
730
|
+
default: ['views']
|
|
731
|
+
},
|
|
732
|
+
'middleware.noxt': async () => {
|
|
733
|
+
const { default: noxt } = await mlm.import('noxt-js-middleware');
|
|
734
|
+
const noxtRouter = await noxt({
|
|
735
|
+
context: mlm.noxt_context,
|
|
736
|
+
views: mlm.config.views,
|
|
737
|
+
hooks: mlm.services.noxt.noxt_hooks
|
|
738
|
+
})
|
|
739
|
+
const devRouter = await noxt({
|
|
740
|
+
context: mlm.noxt_context,
|
|
741
|
+
views: mlm.config.views,
|
|
742
|
+
hooks: mlm.services.noxt.noxt_hooks,
|
|
743
|
+
noxt: noxtRouter
|
|
744
|
+
})
|
|
745
|
+
noxtRouter.use('/dev', devRouter)
|
|
746
|
+
return noxtRouter
|
|
747
|
+
}
|
|
748
|
+
})
|
|
749
|
+
------
|
|
750
|
+
hooks.js
|
|
751
|
+
-----
|
|
752
|
+
export const info = {
|
|
753
|
+
name: 'hooks',
|
|
754
|
+
version: '1.0.0',
|
|
755
|
+
description: 'Hooks',
|
|
756
|
+
requires: ['utils','services']
|
|
757
|
+
}
|
|
758
|
+
const hooks = {}
|
|
759
|
+
const hook_handlers = {}
|
|
760
|
+
|
|
761
|
+
export default mlm => {
|
|
762
|
+
return ({
|
|
763
|
+
'$global.hooks': () => mlm.utils.readOnly(hooks, 'hooks'),
|
|
764
|
+
'$define.hooks': mlm.utils.collector(hooks, {
|
|
765
|
+
is: 'function',
|
|
766
|
+
mode: 'object',
|
|
767
|
+
map: (fn, key) => (...args) => {
|
|
768
|
+
for (const handler of hook_handlers[key] ?? []) {
|
|
769
|
+
fn(handler, ...args);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}),
|
|
773
|
+
'$define.on': mlm.utils.collector(hook_handlers, {
|
|
774
|
+
is: 'function',
|
|
775
|
+
mode: 'array'
|
|
776
|
+
})
|
|
777
|
+
})
|
|
778
|
+
}
|
|
779
|
+
------
|
|
780
|
+
static.js
|
|
781
|
+
-----
|
|
782
|
+
export const info = {
|
|
783
|
+
name: 'static',
|
|
784
|
+
description: 'Static middleware',
|
|
785
|
+
requires: ['express'],
|
|
786
|
+
npm: {
|
|
787
|
+
'serve-static': '^1.15.0'
|
|
788
|
+
},
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export default mlm => ({
|
|
792
|
+
'config.static': {
|
|
793
|
+
is: 'string',
|
|
794
|
+
default: 'public'
|
|
795
|
+
},
|
|
796
|
+
'middleware.static': async (app) => {
|
|
797
|
+
const { default: serve_static } = await mlm.import('serve-static');
|
|
798
|
+
const { resolve } = await mlm.import('path');
|
|
799
|
+
const dir = mlm.config.static;
|
|
800
|
+
return serve_static(resolve(dir));
|
|
801
|
+
},
|
|
802
|
+
})
|
|
803
|
+
------
|
|
804
|
+
env.js
|
|
805
|
+
-----
|
|
806
|
+
export const info = {
|
|
807
|
+
name: 'env',
|
|
808
|
+
version: '1.0.0',
|
|
809
|
+
description: 'Environment',
|
|
810
|
+
}
|
|
811
|
+
/*
|
|
812
|
+
export default mlm => ({
|
|
813
|
+
'$global.DEV': () => process.env.NODE_ENV !== 'production',
|
|
814
|
+
'$global.PROD': () => process.env.NODE_ENV === 'production',
|
|
815
|
+
'onBeforeLoad': () => {
|
|
816
|
+
//process.env.NODE_ENV ||= 'development'
|
|
817
|
+
}
|
|
818
|
+
})
|
|
819
|
+
*/
|
|
820
|
+
------
|
|
821
|
+
noxt-router.js
|
|
822
|
+
-----
|
|
823
|
+
export const info = {
|
|
824
|
+
name: 'noxt-router',
|
|
825
|
+
description: 'Sets up a Noxt Router',
|
|
826
|
+
requires: ['plugin'],
|
|
827
|
+
provides: ['#noxt-router'],
|
|
828
|
+
npm: {
|
|
829
|
+
'noxt-js-middleware': '^1.0.4'
|
|
830
|
+
},
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export default mlm => ({
|
|
834
|
+
'config.views': {
|
|
835
|
+
normalize: p => [p].flat(),
|
|
836
|
+
is: ['string'],
|
|
837
|
+
default: ['views']
|
|
838
|
+
},
|
|
839
|
+
'middleware.noxt': async (app) => {
|
|
840
|
+
const { default: noxt } = await mlm.import('noxt-js-middleware');
|
|
841
|
+
const noxtRouter = await noxt({
|
|
842
|
+
context: mlm.noxt_context,
|
|
843
|
+
views: mlm.config.views,
|
|
844
|
+
hooks: mlm.services.noxt.noxt_hooks
|
|
845
|
+
})
|
|
846
|
+
return noxtRouter
|
|
847
|
+
},
|
|
848
|
+
})
|