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 +27 -0
- package/examples/box/4) twenty composition selectable square boxes/css flex wrap/server.js +1 -1
- package/examples/controls/1) window/server.js +1 -3
- package/examples/json-api/json_server_1.js +37 -0
- package/package.json +7 -7
- package/publishers/Publishers.js +16 -0
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +0 -9
- package/publishers/helpers/assigners/static-uncompressed-response-buffers/Single_Control_Webpage_Server_Static_Uncompressed_Response_Buffers_Assigner.js +0 -31
- package/publishers/http-function-publisher.js +49 -10
- package/publishers/http-png-publisher.js +46 -0
- package/publishers/http-website-publisher.js +5 -2
- package/server.js +21 -1
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
|
|
@@ -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.
|
|
7
|
-
"@babel/generator": "^7.
|
|
8
|
-
"@babel/parser": "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.
|
|
10
|
+
"esbuild": "^0.25.9",
|
|
11
11
|
"fnl": "^0.0.36",
|
|
12
12
|
"fnlfs": "^0.0.33",
|
|
13
|
-
"jsgui3-client": "^0.0.
|
|
14
|
-
"jsgui3-html": "^0.0.
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|