jsgui3-server 0.0.129 → 0.0.133

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -768,6 +768,33 @@ const server = new Server({
768
768
 
769
769
  Data models can be shared within a single client instance, enabling synchronized UI controls.
770
770
 
771
+ ### Publishing functions with `server.publish`
772
+
773
+ The server can publish plain JavaScript functions as HTTP endpoints. When a function is published, request bodies are parsed and passed to the function, and the function's return value determines the response MIME type. The API base path (for example, `/api`) is prefixed automatically, so only provide the route suffix to `publish()`.
774
+
775
+ - If the function returns a string, the response is sent as `text/plain; charset=UTF-8`.
776
+ - If the function returns an object (including arrays), it is serialized as JSON with `application/json`.
777
+ - If the function returns a Promise, it is awaited and then handled by the two rules above.
778
+
779
+ Input payload handling:
780
+ - `Content-Type: application/json` bodies are parsed to an object and passed as the single argument.
781
+ - `Content-Type: text/plain` bodies are passed as a UTF-8 string.
782
+
783
+ Minimal example:
784
+
785
+ ```javascript
786
+ // Inside your server bootstrap (after constructing Server)
787
+ server.on('ready', () => {
788
+ // Returns text/plain at GET/POST /api/hello
789
+ server.publish('hello', name => `Hello ${name || 'world'}`);
790
+
791
+ // Returns application/json at GET/POST /api/sum
792
+ server.publish('sum', ({ a, b }) => ({ sum: a + b }));
793
+ });
794
+ ```
795
+
796
+ This behavior is implemented by the function publisher, which inspects the function result type to choose the correct headers and serialization.
797
+
771
798
  ## CSS Architecture and Styling
772
799
 
773
800
  ### CSS Definition Patterns
@@ -7,7 +7,7 @@ if (require.main === module) {
7
7
 
8
8
  const server = new Server({
9
9
  Ctrl: Demo_UI,
10
- debug: true,
10
+ //debug: true,
11
11
 
12
12
 
13
13
  //'js_mode': 'debug',
@@ -1,8 +1,6 @@
1
1
  const jsgui = require('./client');
2
-
3
- const {Demo_UI} = jsgui.controls;
4
2
  const Server = require('../../../server');
5
-
3
+ const {Demo_UI} = jsgui.controls;
6
4
 
7
5
  // what would be the (best?) way to include the whole thing in one JS file?
8
6
  // Maybe don't try that right now.
@@ -0,0 +1,37 @@
1
+
2
+ const Server = require('../../server');
3
+
4
+
5
+
6
+ if (require.main === module) {
7
+
8
+ const server = new Server({
9
+ 'port': 8088
10
+
11
+ });
12
+ console.log('server.resource_names', server.resource_names);
13
+
14
+ /*
15
+ server.website.api.publish('time', () => {
16
+ return new Date().toISOString();
17
+ });
18
+ */
19
+
20
+ server.publish('time', () => {
21
+ return new Date().toISOString();
22
+ });
23
+
24
+ server.publish('user', () => {
25
+ return { id: 1, name: 'John Doe' };
26
+ });
27
+
28
+ server.publish('users', () => {
29
+ return [
30
+ { id: 1, name: 'John Doe' },
31
+ { id: 2, name: 'Jane Doe' }
32
+ ];
33
+ });
34
+
35
+ server.start(8088);
36
+
37
+ }
package/package.json CHANGED
@@ -3,15 +3,15 @@
3
3
  "main": "module.js",
4
4
  "license": "MIT",
5
5
  "dependencies": {
6
- "@babel/core": "^7.27.7",
7
- "@babel/generator": "^7.27.5",
8
- "@babel/parser": "7.27.7",
6
+ "@babel/core": "^7.28.3",
7
+ "@babel/generator": "^7.28.3",
8
+ "@babel/parser": "^7.28.3",
9
9
  "cookies": "^0.9.1",
10
- "esbuild": "^0.25.5",
10
+ "esbuild": "^0.25.9",
11
11
  "fnl": "^0.0.36",
12
12
  "fnlfs": "^0.0.33",
13
- "jsgui3-client": "^0.0.118",
14
- "jsgui3-html": "^0.0.164",
13
+ "jsgui3-client": "^0.0.119",
14
+ "jsgui3-html": "^0.0.165",
15
15
  "jsgui3-webpage": "^0.0.8",
16
16
  "jsgui3-website": "^0.0.8",
17
17
  "lang-tools": "^0.0.36",
@@ -38,5 +38,5 @@
38
38
  "type": "git",
39
39
  "url": "https://github.com/metabench/jsgui3-server.git"
40
40
  },
41
- "version": "0.0.129"
41
+ "version": "0.0.133"
42
42
  }
@@ -0,0 +1,16 @@
1
+
2
+
3
+ const Publishers = {
4
+ 'css': require('./http-css-publisher'),
5
+ 'function': require('./http-function-publisher'),
6
+ 'html_page': require('./http-html-page-publisher'),
7
+ 'html': require('./http-html-publisher'),
8
+ 'jpeg': require('./http-jpeg-publisher'),
9
+ 'js': require('./http-js-publisher'),
10
+ 'observable': require('./http-observable-publisher'),
11
+ 'png': require('./http-png-publisher'),
12
+ 'resource': require('./http-resource-publisher'),
13
+ 'svg': require('./http-svg-publisher')
14
+ }
15
+
16
+ module.exports = Publishers;
@@ -101,9 +101,6 @@ class Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner
101
101
  } else {
102
102
 
103
103
  }
104
-
105
-
106
-
107
104
  //console.trace();
108
105
  //throw 'stop';
109
106
  }
@@ -112,13 +109,7 @@ class Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner
112
109
  console.trace();
113
110
  throw 'stop';
114
111
  }
115
-
116
-
117
-
118
-
119
112
  }
120
-
121
-
122
113
  }
123
114
 
124
115
 
@@ -41,35 +41,9 @@ class Single_Control_Webpage_Server_Static_Uncompressed_Response_Buffers_Assigne
41
41
  // assign to (bundle) items in array.
42
42
 
43
43
  async assign(arr_bundled_items) {
44
- // go through them....
45
-
46
- // Maybe check that the correct items are in the bundle.
47
-
48
- // Perhaps check for 1 of each js, css, html
49
- // And could use a specific other class to assign these.
50
-
51
- // Should be OK to make classes for really specific things.
52
- // At this part of the system / API, it's not necessary / important to limit complexity in that way.
53
-
54
- // The goal is to provide a very simple high level interface. Powerful too.
55
-
56
- // Could assign a static_route property to the items in the bundles.
57
-
58
-
59
-
60
-
61
-
62
44
  if (is_array(arr_bundled_items)) {
63
-
64
45
  for (const item of arr_bundled_items) {
65
- //console.log('item', item);
66
-
67
46
  const {type} = item;
68
-
69
- // And need to create the uncompressed response buffer.
70
-
71
- // // response_buffers.identity I think....
72
-
73
47
  if (item.text) {
74
48
  const buf_identity_response = Buffer.from(item.text, 'utf-8');
75
49
 
@@ -78,11 +52,6 @@ class Single_Control_Webpage_Server_Static_Uncompressed_Response_Buffers_Assigne
78
52
  } else {
79
53
 
80
54
  }
81
-
82
-
83
-
84
- //console.trace();
85
- //throw 'stop';
86
55
  }
87
56
 
88
57
  } else {
@@ -40,6 +40,7 @@ class Function_Publisher extends HTTP_Publisher {
40
40
  fn = spec;
41
41
  } else {
42
42
  fn = spec.fn;
43
+ this.name = spec.name;
43
44
  if (spec.schema) {
44
45
  this.schema = spec.schema;
45
46
  } else {
@@ -50,6 +51,7 @@ class Function_Publisher extends HTTP_Publisher {
50
51
  //let fn = spec;
51
52
  //console.log('Function_Publisher constructor fn', fn);
52
53
  //console.log('Function_Publisher constructor fn', fn.toString());
54
+ // But will need to route to the function publisher.
53
55
 
54
56
  this.handle_http = (req, res) => {
55
57
  // need to handle observable http request.
@@ -64,8 +66,8 @@ class Function_Publisher extends HTTP_Publisher {
64
66
 
65
67
  const {method, headers} = req;
66
68
 
67
- console.log('Function Publisher handle_http method', method);
68
- console.log('headers', headers);
69
+ //console.log('Function Publisher handle_http method', method);
70
+ //console.log('headers', headers);
69
71
 
70
72
  // Need to get the incoming parameters.
71
73
  // Need to use formidable or whatever else...?
@@ -88,12 +90,21 @@ class Function_Publisher extends HTTP_Publisher {
88
90
 
89
91
  req.on('end', () => {
90
92
  const buf_input = Buffer.concat(chunks);
91
- console.log('buf_input', buf_input);
93
+ //console.log('buf_input', buf_input);
92
94
 
93
95
  // then interpret it according to the content_type
94
96
  let obj_input;
97
+ //console.log('content_type', content_type);
98
+ if (!content_type) {
99
+ console.log('buf_input.length', buf_input.length);
100
+ if (buf_input.length === 0) {
95
101
 
96
- if (content_type.startsWith('text/plain')) {
102
+ } else {
103
+ console.trace();
104
+ throw 'NYI';
105
+ }
106
+ } else {
107
+ if (content_type.startsWith('text/plain')) {
97
108
  obj_input = buf_input.toString();
98
109
 
99
110
  } else {
@@ -106,6 +117,9 @@ class Function_Publisher extends HTTP_Publisher {
106
117
  }
107
118
  // decode / parse JSON.
108
119
  }
120
+ }
121
+
122
+
109
123
 
110
124
 
111
125
  const output_all = (call_res) => {
@@ -129,8 +143,9 @@ class Function_Publisher extends HTTP_Publisher {
129
143
 
130
144
  const fn_res = fn(obj_input);
131
145
  const tfr = tf(fn_res);
146
+ //console.log('fn_res', fn_res);
132
147
 
133
- //console.log('tfr', tfr);
148
+ //
134
149
 
135
150
  if (tfr === 'p') {
136
151
  // promise
@@ -145,8 +160,36 @@ class Function_Publisher extends HTTP_Publisher {
145
160
  });
146
161
 
147
162
 
148
- } else {
163
+ } else if (tfr === 's') {
164
+ // Just write it as a string for the moment I think?
165
+ // Or always encode as JSON?
166
+
167
+ // text/plain;charset=UTF-8
168
+
169
+ res.writeHead(200, {
170
+ 'Content-Type': 'text/plain;charset=UTF-8'//,
171
+ //'Transfer-Encoding': 'chunked',
172
+ //'Trailer': 'Content-MD5'
173
+ });
174
+ res.end(fn_res);
149
175
 
176
+
177
+ } else if (tfr === 'o' || tfr === 'a') {
178
+ // Just write it as a string for the moment I think?
179
+ // Or always encode as JSON?
180
+
181
+ // text/plain;charset=UTF-8
182
+
183
+ res.writeHead(200, {
184
+ 'Content-Type': 'application/json'//,
185
+ //'Transfer-Encoding': 'chunked',
186
+ //'Trailer': 'Content-MD5'
187
+ });
188
+ res.end(JSON.stringify(fn_res));
189
+
190
+
191
+ } else {
192
+ console.log('tfr', tfr);
150
193
  console.trace();
151
194
  throw 'NYI';
152
195
  }
@@ -173,10 +216,6 @@ class Function_Publisher extends HTTP_Publisher {
173
216
 
174
217
 
175
218
 
176
-
177
-
178
-
179
-
180
219
  });
181
220
 
182
221
 
@@ -0,0 +1,46 @@
1
+ const fs = require('fs');
2
+ const HTTP_Publisher = require('./http-publisher');
3
+
4
+ class PNG_Publisher extends HTTP_Publisher {
5
+ constructor(spec) {
6
+ super(spec);
7
+ this.filePath = spec.filePath;
8
+ this.resource = spec.resource;
9
+ }
10
+
11
+ handle_http(req, res) {
12
+ if (req.method !== 'GET') {
13
+ res.writeHead(405, { 'Allow': 'GET' });
14
+ return res.end('Method Not Allowed');
15
+ }
16
+
17
+ const sendBuffer = buf => {
18
+ res.setHeader('Content-Type', 'image/png');
19
+ res.setHeader('Content-Length', buf.length);
20
+ res.setHeader('Cache-Control', 'public, max-age=31536000');
21
+ res.end(buf);
22
+ };
23
+
24
+ if (this.resource && typeof this.resource.getPNG === 'function') {
25
+ Promise.resolve(this.resource.getPNG())
26
+ .then(buf => sendBuffer(buf))
27
+ .catch(() => {
28
+ res.writeHead(500);
29
+ res.end('Internal Server Error');
30
+ });
31
+ } else if (this.filePath) {
32
+ fs.readFile(this.filePath, (err, buf) => {
33
+ if (err) {
34
+ res.writeHead(404);
35
+ return res.end('Not Found');
36
+ }
37
+ sendBuffer(buf);
38
+ });
39
+ } else {
40
+ res.writeHead(500);
41
+ res.end('No PNG resource configured');
42
+ }
43
+ }
44
+ }
45
+
46
+ module.exports = PNG_Publisher;
@@ -207,9 +207,11 @@ class HTTP_Website_Publisher extends HTTP_Webpageorsite_Publisher {
207
207
 
208
208
  // Will probably insert new bundling code here - but refer to advanced classes that handle the details.
209
209
 
210
+ /*
210
211
  console.log('\n\nskipping __old__setup_website_publishing');
211
212
  console.trace();
212
213
  console.log('\n\n');
214
+ */
213
215
 
214
216
 
215
217
  const __old__setup_website_publishing = (website) => {
@@ -491,10 +493,11 @@ class HTTP_Website_Publisher extends HTTP_Webpageorsite_Publisher {
491
493
  if (website) {
492
494
 
493
495
 
494
- console.trace();
496
+ //console.trace();
495
497
 
496
- throw 'NYI - HTTP_Website_Publisher needs to publish specified website';
498
+ //throw 'NYI - HTTP_Website_Publisher needs to publish specified website';
497
499
 
500
+ console.log('Possibly missing website publishing code.')
498
501
 
499
502
  // Website_Bundle class would help.
500
503
 
package/server.js CHANGED
@@ -22,9 +22,11 @@ const Website = require('./website/website');
22
22
  const HTTP_Website_Publisher = require('./publishers/http-website-publisher');
23
23
  const Webpage = require('./website/webpage');
24
24
  const HTTP_Webpage_Publisher = require('./publishers/http-webpage-publisher');
25
+ const HTTP_Function_Publisher = require('./publishers/http-function-publisher');
25
26
 
26
27
  const Static_Route_HTTP_Responder = require('./http/responders/static/Static_Route_HTTP_Responder');
27
28
 
29
+ const Publishers = require('./publishers/Publishers');
28
30
 
29
31
  class JSGUI_Single_Process_Server extends Evented_Class {
30
32
  constructor(spec = {
@@ -146,7 +148,7 @@ class JSGUI_Single_Process_Server extends Evented_Class {
146
148
  // See about making use of relevant shared abstractions.
147
149
 
148
150
 
149
- const ws_app = this.app = new Website(opts_website);
151
+ const ws_app = this.app = this.website = new Website(opts_website);
150
152
  // Be able to treat Webpage as an app?
151
153
 
152
154
  const opts_ws_publisher = {
@@ -156,6 +158,7 @@ class JSGUI_Single_Process_Server extends Evented_Class {
156
158
  opts_ws_publisher.disk_path_client_js = disk_path_client_js;
157
159
  }
158
160
  const ws_publisher = new HTTP_Website_Publisher(opts_ws_publisher);
161
+ this._ws_publisher = ws_publisher;
159
162
  ws_publisher.on('ready', () => {
160
163
  console.log('ws publisher is ready');
161
164
  const ws_resource = new Website_Resource({
@@ -172,11 +175,27 @@ class JSGUI_Single_Process_Server extends Evented_Class {
172
175
 
173
176
  Object.defineProperty(this, 'router', { get: () => server_router })
174
177
  }
178
+
179
+ publish(name, fn) {
180
+ // Get the function publisher.
181
+ // Possibly ensure it exists.
182
+ //const fn_publisher = this.function_publisher;
183
+ //fn_publisher.add(name, fn);
184
+ const fpub = new HTTP_Function_Publisher({name, fn});
185
+
186
+ this.function_publishers = this.function_publishers || [];
187
+ this.function_publishers.push(fpub);
188
+
189
+ this.server_router.set_route('/api/' + name, fpub, fpub.handle_http);
190
+ }
191
+
192
+
175
193
  get resource_names() {
176
194
  return this.resource_pool.resource_names;
177
195
  }
178
196
  'start' (port, callback, fnProcessRequest) {
179
197
  if (tof(port) !== 'number') {
198
+ console.log('Invalid port:', port);
180
199
  console.trace();
181
200
  throw 'stop';
182
201
  }
@@ -279,6 +298,7 @@ JSGUI_Single_Process_Server.Resource = Resource;
279
298
  JSGUI_Single_Process_Server.Page_Context = Server_Page_Context;
280
299
  JSGUI_Single_Process_Server.Server_Page_Context = Server_Page_Context;
281
300
  JSGUI_Single_Process_Server.Website_Resource = Website_Resource;
301
+ JSGUI_Single_Process_Server.Publishers = Publishers;
282
302
  module.exports = JSGUI_Single_Process_Server;
283
303
 
284
304