jsgui3-server 0.0.144 → 0.0.145

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.
Files changed (47) hide show
  1. package/docs/jsgui3-html-improvement-ideas.md +162 -0
  2. package/docs/jsgui3-html-improvement-ideas.svg +151 -0
  3. package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +19 -14
  4. package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +5 -5
  5. package/examples/jsgui3-html/01) mvvm-counter/client.js +648 -0
  6. package/examples/jsgui3-html/01) mvvm-counter/server.js +21 -0
  7. package/examples/jsgui3-html/02) date-transform/client.js +764 -0
  8. package/examples/jsgui3-html/02) date-transform/server.js +21 -0
  9. package/examples/jsgui3-html/03) form-validation/client.js +1045 -0
  10. package/examples/jsgui3-html/03) form-validation/server.js +21 -0
  11. package/examples/jsgui3-html/04) data-grid/client.js +738 -0
  12. package/examples/jsgui3-html/04) data-grid/server.js +21 -0
  13. package/examples/jsgui3-html/05) master-detail/client.js +649 -0
  14. package/examples/jsgui3-html/05) master-detail/server.js +21 -0
  15. package/examples/jsgui3-html/06) theming/client.js +514 -0
  16. package/examples/jsgui3-html/06) theming/server.js +21 -0
  17. package/examples/jsgui3-html/07) mixins/client.js +465 -0
  18. package/examples/jsgui3-html/07) mixins/server.js +21 -0
  19. package/examples/jsgui3-html/08) router/client.js +372 -0
  20. package/examples/jsgui3-html/08) router/server.js +21 -0
  21. package/examples/jsgui3-html/09) resource-transform/client.js +692 -0
  22. package/examples/jsgui3-html/09) resource-transform/server.js +21 -0
  23. package/examples/jsgui3-html/10) binding-debugger/client.js +810 -0
  24. package/examples/jsgui3-html/10) binding-debugger/server.js +21 -0
  25. package/examples/jsgui3-html/README.md +48 -0
  26. package/http/responders/static/Static_Route_HTTP_Responder.js +25 -20
  27. package/package.json +3 -3
  28. package/publishers/http-webpageorsite-publisher.js +3 -1
  29. package/serve-factory.js +12 -5
  30. package/server.js +103 -85
  31. package/tests/README.md +7 -0
  32. package/tests/end-to-end.test.js +336 -365
  33. package/tests/examples-controls.e2e.test.js +13 -1
  34. package/tests/fixtures/end-to-end-client.js +54 -0
  35. package/tests/fixtures/jsgui3-html/binding_debugger_expectations.json +15 -0
  36. package/tests/fixtures/jsgui3-html/counter_expectations.json +31 -0
  37. package/tests/fixtures/jsgui3-html/data_grid_expectations.json +26 -0
  38. package/tests/fixtures/jsgui3-html/date_transform_expectations.json +26 -0
  39. package/tests/fixtures/jsgui3-html/form_validation_expectations.json +27 -0
  40. package/tests/fixtures/jsgui3-html/master_detail_expectations.json +15 -0
  41. package/tests/fixtures/jsgui3-html/mixins_expectations.json +10 -0
  42. package/tests/fixtures/jsgui3-html/resource_transform_expectations.json +11 -0
  43. package/tests/fixtures/jsgui3-html/router_expectations.json +10 -0
  44. package/tests/fixtures/jsgui3-html/theming_expectations.json +10 -0
  45. package/tests/jsgui3-html-examples.puppeteer.test.js +537 -0
  46. package/tests/test-runner.js +1 -0
  47. package/tests/window-examples.puppeteer.test.js +217 -1
@@ -0,0 +1,21 @@
1
+ const jsgui = require('./client');
2
+ const Server = require('../../../server');
3
+ const { Demo_UI } = jsgui.controls;
4
+
5
+ if (require.main === module) {
6
+ const server_instance = new Server({
7
+ Ctrl: Demo_UI,
8
+ src_path_client_js: require.resolve('./client.js')
9
+ });
10
+
11
+ server_instance.allowed_addresses = ['127.0.0.1'];
12
+
13
+ server_instance.on('ready', () => {
14
+ server_instance.start(52000, (err) => {
15
+ if (err) {
16
+ throw err;
17
+ }
18
+ console.log('server started on port 52000');
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,48 @@
1
+ # JSGUI3-HTML Example Plans
2
+
3
+ This folder collects new jsgui3-html focused examples for the server repo.
4
+
5
+ ## Outline plan (10 examples)
6
+
7
+ 1. **01) mvvm-counter**
8
+ - Goal: MVVM binding, computed properties, watchers, transformations, validators.
9
+ - UI: counter display, step input, increment/decrement/reset buttons.
10
+ - Tests: Puppeteer click flows, class toggles, validation states.
11
+
12
+ 2. **02) date-transform**
13
+ - Goal: date transformations, parsing, range validation, locale formatting.
14
+ - UI: ISO input + locale display, min/max validation, error state.
15
+
16
+ 3. **03) form-validation**
17
+ - Goal: multi-field validation (required/email/url/length/pattern).
18
+ - UI: registration form with per-field errors and submit state.
19
+
20
+ 4. **04) data-grid**
21
+ - Goal: collection binding, sorting, filtering, pagination.
22
+ - UI: simple data grid with search and page controls.
23
+
24
+ 5. **05) master-detail**
25
+ - Goal: selection syncing, computed detail view, navigation.
26
+ - UI: list + detail panel with prev/next controls.
27
+
28
+ 6. **06) theming**
29
+ - Goal: theme tokens, theme overrides, CSS variable application.
30
+ - UI: theme toggle with token changes shown in a small layout.
31
+
32
+ 7. **07) mixins**
33
+ - Goal: dragable/resizable/selectable mixins.
34
+ - UI: cards with drag/resize handles and selection states.
35
+
36
+ 8. **08) router**
37
+ - Goal: router contract + route switching.
38
+ - UI: simple nav that swaps controls per route.
39
+
40
+ 9. **09) resource-transform**
41
+ - Goal: Resource + Data_Transform pipeline.
42
+ - UI: load data, transform it, render output with status.
43
+
44
+ 10. **10) binding-debugger**
45
+ - Goal: BindingDebugger usage to inspect bindings and changes.
46
+ - UI: inspector panel showing tracked changes over time.
47
+
48
+ Examples 01-10 are fully implemented right now.
@@ -54,22 +54,27 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
54
54
  if (typeof accept_encoding === 'string' && accept_encoding.includes('gzip')) supported_encodings.gzip = true;
55
55
 
56
56
  if (typeof accept_encoding === 'string' && accept_encoding.includes('br')) supported_encodings.br = true;
57
+
58
+ const has_br = response_buffers && response_buffers.br;
59
+ const has_gzip = response_buffers && response_buffers.gzip;
60
+ const use_br = supported_encodings.br === true && has_br;
61
+ const use_gzip = supported_encodings.gzip === true && has_gzip;
57
62
 
58
63
  //console.log('supported_encodings', supported_encodings);
59
64
 
60
- if (supported_encodings.br === true) {
61
-
62
- for (const key in response_headers.br) {
63
- const value = response_headers.br[key];
64
- //console.log('[key, value]', [key, value]);
65
- res.setHeader(key, value);
66
- }
67
-
68
- } else if (supported_encodings.gzip === true) {
69
- //console.log('should write headers for gzipped buffer...');
70
-
71
- for (const key in response_headers.gzip) {
72
- const value = response_headers.gzip[key];
65
+ if (use_br) {
66
+
67
+ for (const key in response_headers.br) {
68
+ const value = response_headers.br[key];
69
+ //console.log('[key, value]', [key, value]);
70
+ res.setHeader(key, value);
71
+ }
72
+
73
+ } else if (use_gzip) {
74
+ //console.log('should write headers for gzipped buffer...');
75
+
76
+ for (const key in response_headers.gzip) {
77
+ const value = response_headers.gzip[key];
73
78
  //console.log('[key, value]', [key, value]);
74
79
  res.setHeader(key, value);
75
80
  }
@@ -83,13 +88,13 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
83
88
 
84
89
  // Then write the (hopefully compressed) response bodies...
85
90
 
86
- if (supported_encodings.br === true) {
87
-
88
- res.write(response_buffers.br);
89
- } else if (supported_encodings.gzip === true) {
90
- //console.log('should write gzipped buffer...');
91
- res.write(response_buffers.gzip);
92
- } else {
91
+ if (use_br) {
92
+
93
+ res.write(response_buffers.br);
94
+ } else if (use_gzip) {
95
+ //console.log('should write gzipped buffer...');
96
+ res.write(response_buffers.gzip);
97
+ } else {
93
98
  res.write(response_buffers.identity);
94
99
  }
95
100
 
package/package.json CHANGED
@@ -10,8 +10,8 @@
10
10
  "esbuild": "^0.27.1",
11
11
  "fnl": "^0.0.37",
12
12
  "fnlfs": "^0.0.34",
13
- "jsgui3-client": "^0.0.124",
14
- "jsgui3-html": "^0.0.175",
13
+ "jsgui3-client": "^0.0.125",
14
+ "jsgui3-html": "^0.0.176",
15
15
  "jsgui3-webpage": "^0.0.8",
16
16
  "jsgui3-website": "^0.0.8",
17
17
  "lang-tools": "^0.0.44",
@@ -43,7 +43,7 @@
43
43
  "type": "git",
44
44
  "url": "https://github.com/metabench/jsgui3-server.git"
45
45
  },
46
- "version": "0.0.144",
46
+ "version": "0.0.145",
47
47
  "scripts": {
48
48
  "cli": "node cli.js",
49
49
  "serve": "node cli.js serve",
@@ -99,6 +99,7 @@ class HTTP_Webpageorsite_Publisher extends HTTP_Publisher {
99
99
 
100
100
  if (spec.debug !== undefined) this.debug = spec.debug;
101
101
  this.style_config = spec.style || {};
102
+ this.bundler_config = spec.bundler || {};
102
103
 
103
104
  // But then some properties to do with the js client(s?) file path.
104
105
 
@@ -129,7 +130,8 @@ class HTTP_Webpageorsite_Publisher extends HTTP_Publisher {
129
130
 
130
131
  this.js_bundler = new JS_Bundler({
131
132
  'debug': this.debug || false,
132
- 'style': this.style_config
133
+ 'style': this.style_config,
134
+ 'bundler': this.bundler_config
133
135
  });
134
136
 
135
137
 
package/serve-factory.js CHANGED
@@ -12,9 +12,9 @@ const Static_Route_HTTP_Responder = require('./http/responders/static/Static_Rou
12
12
  const { get_port_or_free } = require('./port-utils');
13
13
 
14
14
 
15
- const prepare_webpage_route = (server, route, page_options = {}, defaults = {}) => {
16
- return new Promise((resolve, reject) => {
17
- try {
15
+ const prepare_webpage_route = (server, route, page_options = {}, defaults = {}) => {
16
+ return new Promise((resolve, reject) => {
17
+ try {
18
18
  const {
19
19
  title,
20
20
  name,
@@ -40,6 +40,7 @@ const prepare_webpage_route = (server, route, page_options = {}, defaults = {})
40
40
  if (guessed_client_path) publisher_options.src_path_client_js = guessed_client_path;
41
41
  if (truthy(defaults.debug)) publisher_options.debug = true;
42
42
  if (defaults.style !== undefined) publisher_options.style = defaults.style;
43
+ if (defaults.bundler !== undefined) publisher_options.bundler = defaults.bundler;
43
44
 
44
45
  const webpage_publisher = new HTTP_Webpage_Publisher(publisher_options);
45
46
  webpage_publisher.on('ready', (bundle) => {
@@ -142,6 +143,7 @@ module.exports = (Server) => {
142
143
  const host = serve_options.host || process.env.HOST || null;
143
144
  const debug_enabled = serve_options.debug !== undefined ? truthy(serve_options.debug) : truthy(process.env.JSGUI_DEBUG);
144
145
  const style_config = serve_options.style;
146
+ const bundler_config = serve_options.bundler;
145
147
 
146
148
  const server_spec = {
147
149
  name: serve_options.name || 'jsgui3 server',
@@ -150,6 +152,9 @@ module.exports = (Server) => {
150
152
  if (style_config !== undefined) {
151
153
  server_spec.style = style_config;
152
154
  }
155
+ if (bundler_config !== undefined) {
156
+ server_spec.bundler = bundler_config;
157
+ }
153
158
  if (typeof serve_options.ctrl === 'function') {
154
159
  server_spec.Ctrl = serve_options.ctrl;
155
160
  } else if (serve_options.api && typeof serve_options.api === 'object') {
@@ -181,13 +186,15 @@ module.exports = (Server) => {
181
186
  const extra_page_promises = additional_pages.map(([route, cfg]) => prepare_webpage_route(server_instance, route, cfg, {
182
187
  caller_dir,
183
188
  debug: debug_enabled,
184
- style: style_config
189
+ style: style_config,
190
+ bundler: bundler_config
185
191
  }));
186
192
  if (serve_options.page_config && serve_options.page_route && serve_options.page_route !== '/') {
187
193
  extra_page_promises.unshift(prepare_webpage_route(server_instance, serve_options.page_route, serve_options.page_config, {
188
194
  caller_dir,
189
195
  debug: debug_enabled,
190
- style: style_config
196
+ style: style_config,
197
+ bundler: bundler_config
191
198
  }));
192
199
  }
193
200
 
package/server.js CHANGED
@@ -50,6 +50,7 @@ class JSGUI_Single_Process_Server extends Evented_Class {
50
50
  }
51
51
 
52
52
  const style_config = spec.style;
53
+ const bundler_config = spec.bundler;
53
54
 
54
55
  // or src_path_client_js as well...
55
56
 
@@ -93,14 +94,15 @@ class JSGUI_Single_Process_Server extends Evented_Class {
93
94
 
94
95
  const wp_app = new Webpage({content: Ctrl});
95
96
 
96
- const opts_wp_publisher = {
97
- 'webpage': wp_app
98
-
99
- };
100
-
101
- if (this.debug) {
102
- opts_wp_publisher.debug = this.debug;
103
- }
97
+ const opts_wp_publisher = {
98
+ 'webpage': wp_app
99
+
100
+ };
101
+ if (bundler_config !== undefined) opts_wp_publisher.bundler = bundler_config;
102
+
103
+ if (this.debug) {
104
+ opts_wp_publisher.debug = this.debug;
105
+ }
104
106
 
105
107
  if (disk_path_client_js) opts_wp_publisher.src_path_client_js = disk_path_client_js;
106
108
  if (style_config !== undefined) opts_wp_publisher.style = style_config;
@@ -163,12 +165,15 @@ class JSGUI_Single_Process_Server extends Evented_Class {
163
165
  const ws_app = this.app = this.website = new Website(opts_website);
164
166
  // Be able to treat Webpage as an app?
165
167
 
166
- const opts_ws_publisher = {
167
- 'website': ws_app
168
- };
168
+ const opts_ws_publisher = {
169
+ 'website': ws_app
170
+ };
169
171
  if (disk_path_client_js) {
170
172
  opts_ws_publisher.disk_path_client_js = disk_path_client_js;
171
173
  }
174
+ if (bundler_config !== undefined) {
175
+ opts_ws_publisher.bundler = bundler_config;
176
+ }
172
177
  if (style_config !== undefined) {
173
178
  opts_ws_publisher.style = style_config;
174
179
  }
@@ -240,16 +245,33 @@ class JSGUI_Single_Process_Server extends Evented_Class {
240
245
  if (this.allowed_addresses && this.allowed_addresses.length) {
241
246
  arr_ipv4_addresses = arr_ipv4_addresses.filter(a => this.allowed_addresses.indexOf(a) > -1);
242
247
  }
243
- arr_ipv4_addresses = [...new Set(arr_ipv4_addresses)];
244
- console.log('IPv4 addresses to bind:', arr_ipv4_addresses);
245
- let num_to_start = arr_ipv4_addresses.length;
246
- if (num_to_start === 0) {
247
- callback('No allowed network interfaces found.');
248
- return;
249
- }
250
- const respond_not_found = (res) => {
251
- if (!res.headersSent) {
252
- const body = 'Not Found';
248
+ arr_ipv4_addresses = [...new Set(arr_ipv4_addresses)];
249
+ console.log('IPv4 addresses to bind:', arr_ipv4_addresses);
250
+ let num_to_start = arr_ipv4_addresses.length;
251
+ let started_count = 0;
252
+ let last_error = null;
253
+ let ready_raised = false;
254
+ if (num_to_start === 0) {
255
+ callback('No allowed network interfaces found.');
256
+ return;
257
+ }
258
+ const finalize_start = (err) => {
259
+ if (num_to_start !== 0) return;
260
+ if (started_count > 0) {
261
+ if (!ready_raised) {
262
+ console.log('Server ready');
263
+ this.raise('ready');
264
+ ready_raised = true;
265
+ }
266
+ if (callback) callback(null, true);
267
+ return;
268
+ }
269
+ const final_error = err || last_error || new Error('No servers started.');
270
+ if (callback) callback(final_error);
271
+ };
272
+ const respond_not_found = (res) => {
273
+ if (!res.headersSent) {
274
+ const body = 'Not Found';
253
275
  res.statusCode = 404;
254
276
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
255
277
  res.setHeader('Content-Length', Buffer.byteLength(body));
@@ -308,70 +330,66 @@ class JSGUI_Single_Process_Server extends Evented_Class {
308
330
  process_request(req, res);
309
331
  });
310
332
  this.http_servers.push(https_server);
311
- https_server.on('error', (err) => {
312
- if (err.code === 'EACCES') {
313
- console.error('Permission denied:', err.message);
314
- } else if (err.code === 'EADDRINUSE') {
315
- console.error(`Address ${ipv4_address}:${port} already in use; skipping.`);
316
- } else {
317
- console.error('https_server error:', err);
318
- }
319
- num_to_start--;
320
- if (num_to_start === 0 && callback) callback(null, true);
321
- });
322
- https_server.timeout = 10800000;
323
- https_server.listen(port, ipv4_address, () => {
324
- console.log('* Server running at https://' + ipv4_address + ':' + port + '/');
325
- num_to_start--;
326
- if (num_to_start === 0) {
327
- console.log('Server ready');
328
- this.raise('ready');
329
- if (callback) callback(null, true);
330
- }
331
- });
332
- } catch (err) {
333
- console.log('https_server err', err);
334
- num_to_start--;
335
- if (num_to_start === 0 && callback) callback(null, true);
336
- }
337
- });
338
- } else {
339
- each(arr_ipv4_addresses, (ipv4_address) => {
340
- try {
341
- var http_server = http.createServer(function(req, res) {
342
- process_request(req, res);
343
- });
344
- this.http_servers.push(http_server);
345
- http_server.on('error', (err) => {
346
- if (err.code === 'EACCES') {
347
- console.error('Permission denied:', err.message);
348
- } else if (err.code === 'EADDRINUSE') {
349
- console.error(`Address ${ipv4_address}:${port} already in use; skipping.`);
350
- } else {
351
- console.error('http_server error:', err);
352
- }
353
- num_to_start--;
354
- if (num_to_start === 0 && callback) callback(null, true);
355
- });
356
- http_server.timeout = 10800000;
357
- http_server.listen(port, ipv4_address, () => {
358
- console.log('* Server running at http://' + ipv4_address + ':' + port + '/');
359
- num_to_start--;
360
- if (num_to_start === 0) {
361
- console.log('Server ready');
362
- this.raise('ready');
363
- if (callback) callback(null, true);
364
- }
365
- });
366
- } catch (err) {
367
- console.log('http_server err', err);
368
- num_to_start--;
369
- if (num_to_start === 0 && callback) callback(null, true);
370
- }
371
- });
372
- }
373
- }
374
- });
333
+ https_server.on('error', (err) => {
334
+ last_error = err;
335
+ if (err.code === 'EACCES') {
336
+ console.error('Permission denied:', err.message);
337
+ } else if (err.code === 'EADDRINUSE') {
338
+ console.error(`Address ${ipv4_address}:${port} already in use; skipping.`);
339
+ } else {
340
+ console.error('https_server error:', err);
341
+ }
342
+ num_to_start--;
343
+ finalize_start(err);
344
+ });
345
+ https_server.timeout = 10800000;
346
+ https_server.listen(port, ipv4_address, () => {
347
+ console.log('* Server running at https://' + ipv4_address + ':' + port + '/');
348
+ started_count++;
349
+ num_to_start--;
350
+ finalize_start(null);
351
+ });
352
+ } catch (err) {
353
+ console.log('https_server err', err);
354
+ num_to_start--;
355
+ finalize_start(err);
356
+ }
357
+ });
358
+ } else {
359
+ each(arr_ipv4_addresses, (ipv4_address) => {
360
+ try {
361
+ var http_server = http.createServer(function(req, res) {
362
+ process_request(req, res);
363
+ });
364
+ this.http_servers.push(http_server);
365
+ http_server.on('error', (err) => {
366
+ last_error = err;
367
+ if (err.code === 'EACCES') {
368
+ console.error('Permission denied:', err.message);
369
+ } else if (err.code === 'EADDRINUSE') {
370
+ console.error(`Address ${ipv4_address}:${port} already in use; skipping.`);
371
+ } else {
372
+ console.error('http_server error:', err);
373
+ }
374
+ num_to_start--;
375
+ finalize_start(err);
376
+ });
377
+ http_server.timeout = 10800000;
378
+ http_server.listen(port, ipv4_address, () => {
379
+ console.log('* Server running at http://' + ipv4_address + ':' + port + '/');
380
+ started_count++;
381
+ num_to_start--;
382
+ finalize_start(null);
383
+ });
384
+ } catch (err) {
385
+ console.log('http_server err', err);
386
+ num_to_start--;
387
+ finalize_start(err);
388
+ }
389
+ });
390
+ }
391
+ }
392
+ });
375
393
  }
376
394
  });
377
395
  }
package/tests/README.md CHANGED
@@ -27,6 +27,7 @@ tests/
27
27
  ├── error-handling.test.js # Error handling and edge cases
28
28
  ├── examples-controls.e2e.test.js # Example apps regression (controls)
29
29
  ├── sass-controls.e2e.test.js # Sass/CSS controls E2E coverage
30
+ ├── jsgui3-html-examples.puppeteer.test.js # Puppeteer interaction tests (jsgui3-html examples)
30
31
  ├── window-examples.puppeteer.test.js # Puppeteer interaction tests (window examples)
31
32
  ├── test-runner.js # Custom test runner with reporting
32
33
  └── README.md # This file
@@ -159,6 +160,12 @@ Server-level integration tests for controls that define `.scss` or `.sass` style
159
160
 
160
161
  Note: These tests are skipped if the `sass` dependency is not installed.
161
162
 
163
+ ### 10. JSGUI3-HTML Example Puppeteer Tests (`jsgui3-html-examples.puppeteer.test.js`)
164
+
165
+ Browser-level interaction checks for jsgui3-html examples:
166
+
167
+ - MVVM counter interactions, bindings, and validation state
168
+
162
169
  ## Configuration Examples
163
170
 
164
171
  ### Basic Minification