jsgui3-server 0.0.147 → 0.0.149
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/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
- package/README.md +85 -3
- package/admin-ui/client.js +213 -0
- package/admin-ui/server.js +104 -0
- package/client/controls/auto-observable.js +207 -0
- package/dev-status.svg +139 -0
- package/docs/api-reference.md +301 -43
- package/docs/books/admin-ui/01-introduction.md +32 -0
- package/docs/books/admin-ui/02-architecture.md +92 -0
- package/docs/books/admin-ui/03-controls.md +194 -0
- package/docs/books/admin-ui/04-implementation-plan.md +62 -0
- package/docs/books/admin-ui/README.md +26 -0
- package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/client.js +125 -0
- package/examples/controls/19) window, auto observable ui/server.js +73 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +11 -4
- package/package.json +14 -4
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-observable-publisher.js +8 -0
- package/publishers/http-sse-publisher.js +341 -0
- package/publishers/http-webpage-publisher.js +13 -3
- package/publishers/http-webpageorsite-publisher.js +18 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +441 -259
- package/server.js +161 -16
- package/tests/README.md +66 -4
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +13 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
package/serve-factory.js
CHANGED
|
@@ -1,194 +1,366 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} = require('./serve-helpers');
|
|
8
|
-
const lib_path = require('path');
|
|
9
|
-
const Webpage = require('./website/webpage');
|
|
10
|
-
const HTTP_Webpage_Publisher = require('./publishers/http-webpage-publisher');
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
const {
|
|
2
|
+
truthy,
|
|
3
|
+
guess_caller_file,
|
|
4
|
+
find_default_client_path,
|
|
5
|
+
load_default_control_from_client,
|
|
6
|
+
ensure_route_leading_slash
|
|
7
|
+
} = require('./serve-helpers');
|
|
8
|
+
const lib_path = require('path');
|
|
9
|
+
const Webpage = require('./website/webpage');
|
|
10
|
+
const HTTP_Webpage_Publisher = require('./publishers/http-webpage-publisher');
|
|
11
|
+
const HTTP_SSE_Publisher = require('./publishers/http-sse-publisher');
|
|
12
|
+
const Static_Route_HTTP_Responder = require('./http/responders/static/Static_Route_HTTP_Responder');
|
|
13
|
+
const Process_Resource = require('./resources/process-resource');
|
|
14
|
+
const Remote_Process_Resource = require('./resources/remote-process-resource');
|
|
15
|
+
const { get_port_or_free } = require('./port-utils');
|
|
16
|
+
|
|
15
17
|
const prepare_webpage_route = (server, route, page_options = {}, defaults = {}) => {
|
|
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
|
-
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
try {
|
|
20
|
+
const {
|
|
21
|
+
title,
|
|
22
|
+
name
|
|
23
|
+
} = page_options;
|
|
24
|
+
const content_ctrl = page_options.content || page_options.ctrl || page_options.Ctrl;
|
|
25
|
+
if (typeof content_ctrl !== 'function') {
|
|
26
|
+
return reject(new Error(`Page at route "${route}" requires a control constructor as content.`));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const caller_dir = defaults.caller_dir || process.cwd();
|
|
30
|
+
const explicit_client_path = page_options.clientPath || page_options.src_path_client_js || page_options.disk_path_client_js;
|
|
31
|
+
const guessed_client_path = find_default_client_path(explicit_client_path, caller_dir);
|
|
32
|
+
|
|
33
|
+
const webpage = new Webpage({
|
|
34
|
+
name: name || title || `Page ${route}`,
|
|
35
|
+
title,
|
|
36
|
+
content: content_ctrl,
|
|
37
|
+
path: route
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const publisher_options = {
|
|
41
|
+
webpage
|
|
42
|
+
};
|
|
43
|
+
if (guessed_client_path) publisher_options.src_path_client_js = guessed_client_path;
|
|
44
|
+
if (truthy(defaults.debug)) publisher_options.debug = true;
|
|
45
|
+
if (defaults.style !== undefined) publisher_options.style = defaults.style;
|
|
46
|
+
if (defaults.bundler !== undefined) publisher_options.bundler = defaults.bundler;
|
|
47
|
+
|
|
48
|
+
const webpage_publisher = new HTTP_Webpage_Publisher(publisher_options);
|
|
49
|
+
webpage_publisher.on('ready', (bundle) => {
|
|
50
|
+
try {
|
|
51
|
+
if (bundle && bundle._arr) {
|
|
52
|
+
for (const item of bundle._arr) {
|
|
53
|
+
const static_responder = new Static_Route_HTTP_Responder(item);
|
|
54
|
+
server.router.set_route(item.route, static_responder, static_responder.handle_http);
|
|
55
|
+
}
|
|
56
|
+
resolve();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
reject(new Error(`Unexpected bundle format when preparing route ${route}`));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
reject(error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
webpage_publisher.on('error', reject);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
reject(error);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const invoke_resource_method = (resource, method_name) => {
|
|
72
|
+
if (!resource || typeof resource[method_name] !== 'function') {
|
|
73
|
+
return Promise.resolve(true);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (resource[method_name].length >= 1) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
resource[method_name]((error, result) => {
|
|
79
|
+
if (error) {
|
|
80
|
+
reject(error);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolve(result);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const method_result = resource[method_name]();
|
|
89
|
+
if (method_result && typeof method_result.then === 'function') {
|
|
90
|
+
return method_result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Promise.resolve(method_result);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const is_resource_like = (resource_value) => {
|
|
97
|
+
return !!resource_value
|
|
98
|
+
&& typeof resource_value === 'object'
|
|
99
|
+
&& (typeof resource_value.start === 'function'
|
|
100
|
+
|| typeof resource_value.meets_requirements === 'function'
|
|
101
|
+
|| typeof resource_value.get_abstract === 'function');
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const instantiate_custom_resource = (resource_name, resource_spec, resource_constructor) => {
|
|
105
|
+
const constructor_spec = resource_spec && typeof resource_spec.spec === 'object'
|
|
106
|
+
? {
|
|
107
|
+
...resource_spec.spec
|
|
108
|
+
}
|
|
109
|
+
: {
|
|
110
|
+
...resource_spec
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (!constructor_spec.name) {
|
|
114
|
+
constructor_spec.name = resource_name;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
delete constructor_spec.type;
|
|
118
|
+
delete constructor_spec.class;
|
|
119
|
+
delete constructor_spec.Ctor;
|
|
120
|
+
delete constructor_spec.constructor_fn;
|
|
121
|
+
delete constructor_spec.instance;
|
|
122
|
+
delete constructor_spec.resource;
|
|
123
|
+
delete constructor_spec.spec;
|
|
124
|
+
|
|
125
|
+
return new resource_constructor(constructor_spec);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const create_configured_resource = (resource_name, resource_spec = {}) => {
|
|
129
|
+
if (is_resource_like(resource_spec)) {
|
|
130
|
+
if (!resource_spec.name) {
|
|
131
|
+
resource_spec.name = resource_name;
|
|
132
|
+
}
|
|
133
|
+
return resource_spec;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof resource_spec === 'function') {
|
|
137
|
+
return instantiate_custom_resource(resource_name, {}, resource_spec);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const normalized_spec = {
|
|
141
|
+
...resource_spec,
|
|
142
|
+
name: resource_spec.name || resource_name
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const explicit_instance = normalized_spec.instance || normalized_spec.resource;
|
|
146
|
+
if (is_resource_like(explicit_instance)) {
|
|
147
|
+
if (!explicit_instance.name) {
|
|
148
|
+
explicit_instance.name = normalized_spec.name;
|
|
149
|
+
}
|
|
150
|
+
return explicit_instance;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const resource_constructor = normalized_spec.class || normalized_spec.Ctor || normalized_spec.constructor_fn;
|
|
154
|
+
if (typeof resource_constructor === 'function') {
|
|
155
|
+
return instantiate_custom_resource(resource_name, normalized_spec, resource_constructor);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const resource_type = String(normalized_spec.type || 'process').toLowerCase();
|
|
159
|
+
if (resource_type === 'process' || resource_type === 'local') {
|
|
160
|
+
return new Process_Resource(normalized_spec);
|
|
161
|
+
}
|
|
162
|
+
if (resource_type === 'remote' || resource_type === 'http') {
|
|
163
|
+
return new Remote_Process_Resource(normalized_spec);
|
|
164
|
+
}
|
|
165
|
+
if (resource_type === 'resource' || resource_type === 'in_process' || resource_type === 'in-process') {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`resources.${resource_name} requires one of: { instance }, { resource }, { class }, { constructor_fn }, or a resource object directly.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (normalized_spec.command || normalized_spec.processManager) {
|
|
172
|
+
return new Process_Resource(normalized_spec);
|
|
173
|
+
}
|
|
174
|
+
if (normalized_spec.host || normalized_spec.endpoints) {
|
|
175
|
+
return new Remote_Process_Resource(normalized_spec);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
throw new Error(`Unsupported resources.${resource_name}.type: ${normalized_spec.type}`);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const normalize_resource_entries = (resources_option) => {
|
|
182
|
+
if (!resources_option) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (Array.isArray(resources_option)) {
|
|
187
|
+
return resources_option.map((resource_spec, index) => {
|
|
188
|
+
const inferred_name = (resource_spec && resource_spec.name) || `resource_${index + 1}`;
|
|
189
|
+
return [inferred_name, resource_spec || {}];
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (typeof resources_option === 'object') {
|
|
194
|
+
return Object.entries(resources_option);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
throw new Error('`resources` must be an object map or an array.');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
module.exports = (Server) => {
|
|
201
|
+
const serve = function(input, maybe_options, maybe_callback) {
|
|
202
|
+
let callback = null;
|
|
203
|
+
if (typeof maybe_options === 'function') {
|
|
204
|
+
callback = maybe_options;
|
|
205
|
+
maybe_options = undefined;
|
|
206
|
+
} else if (typeof maybe_callback === 'function') {
|
|
207
|
+
callback = maybe_callback;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let serve_options = {};
|
|
211
|
+
if (typeof input === 'function') {
|
|
212
|
+
serve_options.ctrl = input;
|
|
213
|
+
} else if (input && typeof input === 'object') {
|
|
214
|
+
serve_options = {
|
|
215
|
+
...input
|
|
216
|
+
};
|
|
217
|
+
} else if (input !== undefined) {
|
|
218
|
+
throw new Error('First argument to Server.serve must be a control constructor or options object.');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (maybe_options && typeof maybe_options === 'object') {
|
|
222
|
+
serve_options = {
|
|
223
|
+
...serve_options,
|
|
224
|
+
...maybe_options
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const caller_file = serve_options.caller_file || serve_options.callerFile || guess_caller_file();
|
|
229
|
+
const caller_dir = serve_options.root
|
|
230
|
+
? lib_path.resolve(process.cwd(), serve_options.root)
|
|
231
|
+
: (caller_file ? lib_path.dirname(caller_file) : process.cwd());
|
|
232
|
+
|
|
233
|
+
if (!serve_options.ctrl && serve_options.Ctrl) serve_options.ctrl = serve_options.Ctrl;
|
|
234
|
+
if (!serve_options.ctrl && serve_options.page) {
|
|
235
|
+
const page_config = serve_options.page;
|
|
236
|
+
serve_options.ctrl = page_config.content || page_config.ctrl || page_config.Ctrl;
|
|
237
|
+
serve_options.page_route = ensure_route_leading_slash(page_config.route || '/');
|
|
238
|
+
serve_options.page_config = page_config;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let additional_pages = [];
|
|
242
|
+
if (!serve_options.ctrl && serve_options.pages && typeof serve_options.pages === 'object') {
|
|
243
|
+
const page_entries = Object.entries(serve_options.pages);
|
|
244
|
+
if (!page_entries.length) {
|
|
245
|
+
throw new Error('`pages` option requires at least one entry.');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const normalized_pages = page_entries.map(([route, cfg]) => [ensure_route_leading_slash(route), cfg || {}]);
|
|
249
|
+
const root_entry = normalized_pages.find(([route]) => route === '/') || normalized_pages[0];
|
|
250
|
+
serve_options.ctrl = (root_entry[1].content || root_entry[1].ctrl || root_entry[1].Ctrl);
|
|
251
|
+
serve_options.page_route = root_entry[0];
|
|
252
|
+
serve_options.page_config = root_entry[1];
|
|
253
|
+
additional_pages = normalized_pages.filter(([route]) => route !== serve_options.page_route);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const explicit_client_path = serve_options.clientPath || serve_options.client_path || serve_options.src_path_client_js || serve_options.disk_path_client_js;
|
|
257
|
+
const root_client_path = find_default_client_path(explicit_client_path, caller_dir);
|
|
258
|
+
|
|
259
|
+
if (typeof serve_options.ctrl !== 'function' && root_client_path) {
|
|
260
|
+
const auto_ctrl = load_default_control_from_client(root_client_path);
|
|
261
|
+
if (typeof auto_ctrl === 'function') {
|
|
262
|
+
serve_options.ctrl = auto_ctrl;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (serve_options.page_config && typeof serve_options.ctrl !== 'function') {
|
|
267
|
+
throw new Error('`page` option requires a control constructor.');
|
|
268
|
+
}
|
|
269
|
+
if (additional_pages.length && typeof serve_options.ctrl !== 'function') {
|
|
270
|
+
throw new Error('`pages` option requires at least one control constructor.');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const port = Number.isFinite(serve_options.port)
|
|
274
|
+
? Number(serve_options.port)
|
|
275
|
+
: (serve_options.port === 'auto' ? 0 : (process.env.PORT ? Number(process.env.PORT) : 8080));
|
|
276
|
+
const auto_port = serve_options.autoPort !== false && (port === 0 || serve_options.port === 'auto' || serve_options.autoPort === true);
|
|
277
|
+
if (!Number.isFinite(port) && serve_options.port !== 'auto') {
|
|
278
|
+
throw new Error('Invalid port specified for Server.serve');
|
|
279
|
+
}
|
|
280
|
+
|
|
143
281
|
const host = serve_options.host || process.env.HOST || null;
|
|
144
282
|
const debug_enabled = serve_options.debug !== undefined ? truthy(serve_options.debug) : truthy(process.env.JSGUI_DEBUG);
|
|
145
283
|
const style_config = serve_options.style;
|
|
146
284
|
const bundler_config = serve_options.bundler;
|
|
147
|
-
|
|
285
|
+
|
|
148
286
|
const server_spec = {
|
|
149
287
|
name: serve_options.name || 'jsgui3 server',
|
|
150
288
|
debug: debug_enabled
|
|
151
289
|
};
|
|
152
|
-
if (style_config !== undefined)
|
|
153
|
-
|
|
290
|
+
if (style_config !== undefined) server_spec.style = style_config;
|
|
291
|
+
if (bundler_config !== undefined) server_spec.bundler = bundler_config;
|
|
292
|
+
|
|
293
|
+
if (typeof serve_options.ctrl === 'function') {
|
|
294
|
+
server_spec.Ctrl = serve_options.ctrl;
|
|
295
|
+
} else if (serve_options.api && typeof serve_options.api === 'object') {
|
|
296
|
+
server_spec.website = false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (root_client_path) {
|
|
300
|
+
server_spec.src_path_client_js = root_client_path;
|
|
154
301
|
}
|
|
155
|
-
|
|
156
|
-
|
|
302
|
+
|
|
303
|
+
const server_instance = new Server(server_spec);
|
|
304
|
+
if (host) {
|
|
305
|
+
server_instance.allowed_addresses = Array.isArray(host) ? host : [host];
|
|
157
306
|
}
|
|
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
|
-
|
|
307
|
+
|
|
308
|
+
const configured_resources = [];
|
|
309
|
+
const resource_entries = normalize_resource_entries(serve_options.resources);
|
|
310
|
+
if (resource_entries.length) {
|
|
311
|
+
for (const [resource_name, resource_spec] of resource_entries) {
|
|
312
|
+
configured_resources.push(create_configured_resource(resource_name, resource_spec || {}));
|
|
313
|
+
}
|
|
314
|
+
server_instance.configured_resources = configured_resources;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (serve_options.events) {
|
|
318
|
+
const event_options = typeof serve_options.events === 'object' ? serve_options.events : {};
|
|
319
|
+
const events_route = ensure_route_leading_slash(event_options.route || '/events');
|
|
320
|
+
const sse_publisher = new HTTP_SSE_Publisher({
|
|
321
|
+
name: event_options.name || 'events',
|
|
322
|
+
...event_options
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
server_instance.sse_publisher = sse_publisher;
|
|
326
|
+
server_instance.server_router.set_route(events_route, sse_publisher, sse_publisher.handle_http);
|
|
327
|
+
|
|
328
|
+
const pool_event_names = ['resource_state_change', 'crashed', 'unhealthy', 'unreachable', 'recovered'];
|
|
329
|
+
pool_event_names.forEach((event_name) => {
|
|
330
|
+
server_instance.resource_pool.on(event_name, (event_data) => {
|
|
331
|
+
sse_publisher.broadcast(event_name, event_data);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const attach_configured_resources = () => {
|
|
337
|
+
for (const resource of configured_resources) {
|
|
338
|
+
if (!server_instance.resource_pool.has_resource(resource.name)) {
|
|
339
|
+
server_instance.resource_pool.add(resource);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const start_configured_resources = async () => {
|
|
345
|
+
if (!configured_resources.length) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
attach_configured_resources();
|
|
350
|
+
await Promise.all(configured_resources.map((resource) => invoke_resource_method(resource, 'start')));
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const startup_ready_timeout_ms = Number.isFinite(serve_options.readyTimeoutMs) && Number(serve_options.readyTimeoutMs) > 0
|
|
354
|
+
? Number(serve_options.readyTimeoutMs)
|
|
355
|
+
: 120000;
|
|
356
|
+
|
|
186
357
|
const extra_page_promises = additional_pages.map(([route, cfg]) => prepare_webpage_route(server_instance, route, cfg, {
|
|
187
358
|
caller_dir,
|
|
188
359
|
debug: debug_enabled,
|
|
189
360
|
style: style_config,
|
|
190
361
|
bundler: bundler_config
|
|
191
362
|
}));
|
|
363
|
+
|
|
192
364
|
if (serve_options.page_config && serve_options.page_route && serve_options.page_route !== '/') {
|
|
193
365
|
extra_page_promises.unshift(prepare_webpage_route(server_instance, serve_options.page_route, serve_options.page_config, {
|
|
194
366
|
caller_dir,
|
|
@@ -197,88 +369,98 @@ module.exports = (Server) => {
|
|
|
197
369
|
bundler: bundler_config
|
|
198
370
|
}));
|
|
199
371
|
}
|
|
200
|
-
|
|
201
|
-
if (serve_options.api && typeof serve_options.api === 'object') {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
372
|
+
|
|
373
|
+
if (serve_options.api && typeof serve_options.api === 'object') {
|
|
374
|
+
for (const [name, handler] of Object.entries(serve_options.api)) {
|
|
375
|
+
if (typeof handler === 'function') {
|
|
376
|
+
server_instance.publish(name, handler);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return new Promise((resolve, reject) => {
|
|
382
|
+
let has_started = false;
|
|
383
|
+
let has_settled = false;
|
|
384
|
+
let ready_timeout_handle = null;
|
|
385
|
+
|
|
386
|
+
const settle = (resolver, value) => {
|
|
387
|
+
if (has_settled) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
has_settled = true;
|
|
391
|
+
if (ready_timeout_handle) {
|
|
392
|
+
clearTimeout(ready_timeout_handle);
|
|
393
|
+
ready_timeout_handle = null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (callback) {
|
|
397
|
+
try {
|
|
398
|
+
callback(resolver === reject ? value : null, resolver === resolve ? value : undefined);
|
|
399
|
+
} catch {
|
|
400
|
+
// Ignore callback errors after settlement.
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
resolver(value);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const start_server = async () => {
|
|
407
|
+
if (has_started || has_settled) return;
|
|
408
|
+
has_started = true;
|
|
409
|
+
|
|
410
|
+
let actual_port = port;
|
|
411
|
+
if (auto_port) {
|
|
412
|
+
try {
|
|
413
|
+
const check_host = host || '127.0.0.1';
|
|
414
|
+
actual_port = await get_port_or_free(port, Array.isArray(check_host) ? check_host[0] : check_host);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
return settle(reject, error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
server_instance.port = actual_port;
|
|
421
|
+
|
|
422
|
+
server_instance.start(actual_port, (error) => {
|
|
423
|
+
if (error) {
|
|
424
|
+
return settle(reject, error);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
start_configured_resources().then(() => {
|
|
428
|
+
settle(resolve, server_instance);
|
|
429
|
+
}).catch((resource_error) => {
|
|
430
|
+
settle(reject, resource_error);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
server_instance.on('ready', () => {
|
|
436
|
+
if (has_settled) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
Promise.allSettled(extra_page_promises).then((results) => {
|
|
440
|
+
const rejected_entry = results.find((result) => result.status === 'rejected');
|
|
441
|
+
if (rejected_entry) {
|
|
442
|
+
return settle(reject, rejected_entry.reason);
|
|
443
|
+
}
|
|
444
|
+
start_server();
|
|
445
|
+
}).catch((error) => {
|
|
446
|
+
settle(reject, error);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
if (serve_options.api && typeof serve_options.api === 'object' && !serve_options.ctrl) {
|
|
451
|
+
server_instance.raise('ready');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
ready_timeout_handle = setTimeout(() => {
|
|
455
|
+
if (!has_settled && !has_started) {
|
|
456
|
+
settle(reject, new Error(
|
|
457
|
+
`Server.serve timed out waiting for "ready" before start (${startup_ready_timeout_ms}ms).`
|
|
458
|
+
));
|
|
459
|
+
}
|
|
460
|
+
}, startup_ready_timeout_ms);
|
|
461
|
+
ready_timeout_handle.unref?.();
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
return serve;
|
|
466
|
+
};
|