jsgui3-server 0.0.138 → 0.0.140
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/AGENTS.md +87 -0
- package/README.md +12 -0
- package/docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md +19 -0
- package/docs/advanced-usage-examples.md +1360 -0
- package/docs/agent-development-guide.md +386 -0
- package/docs/api-reference.md +916 -0
- package/docs/broken-functionality-tracker.md +285 -0
- package/docs/bundling-system-deep-dive.md +525 -0
- package/docs/cli-reference.md +393 -0
- package/docs/comprehensive-documentation.md +1403 -0
- package/docs/configuration-reference.md +808 -0
- package/docs/controls-development.md +859 -0
- package/docs/documentation-review/CURRENT_REVIEW.md +95 -0
- package/docs/function-publishers-json-apis.md +847 -0
- package/docs/getting-started-with-json.md +518 -0
- package/docs/minification-compression-sourcemaps-status.md +482 -0
- package/docs/minification-compression-sourcemaps-test-results.md +205 -0
- package/docs/publishers-guide.md +313 -0
- package/docs/resources-guide.md +615 -0
- package/docs/serve-helpers.md +406 -0
- package/docs/simple-server-api-design.md +13 -0
- package/docs/system-architecture.md +275 -0
- package/docs/troubleshooting.md +698 -0
- package/examples/json/README.md +115 -0
- package/examples/json/basic-api/README.md +345 -0
- package/examples/json/basic-api/server.js +199 -0
- package/examples/json/simple-api/README.md +125 -0
- package/examples/json/simple-api/diagnostic-report.json +73 -0
- package/examples/json/simple-api/diagnostic-test.js +433 -0
- package/examples/json/simple-api/server-debug.md +58 -0
- package/examples/json/simple-api/server.js +91 -0
- package/examples/json/simple-api/test.js +215 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +1 -2
- package/package.json +19 -8
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +65 -12
- package/publishers/helpers/preparers/static/bundle/Static_Routes_Responses_Webpage_Bundle_Preparer.js +6 -1
- package/publishers/http-function-publisher.js +59 -38
- package/publishers/http-webpage-publisher.js +48 -1
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +38 -146
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +54 -5
- package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +36 -4
- package/serve-factory.js +36 -9
- package/server.js +10 -4
- package/test-report.json +0 -0
- package/tests/README.md +250 -0
- package/tests/assigners.test.js +316 -0
- package/tests/bundlers.test.js +329 -0
- package/tests/configuration-validation.test.js +530 -0
- package/tests/content-analysis.test.js +641 -0
- package/tests/end-to-end.test.js +496 -0
- package/tests/error-handling.test.js +746 -0
- package/tests/performance.test.js +653 -0
- package/tests/publishers.test.js +395 -0
- package/tests/temp_invalid.js +7 -0
- package/tests/temp_invalid_utf8.js +1 -0
- package/tests/temp_malformed.js +10 -0
- package/tests/test-runner.js +261 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Simple JSON API Example
|
|
2
|
+
// Demonstrates basic JSON API functionality with minimal code
|
|
3
|
+
|
|
4
|
+
const Server = require('../../../server');
|
|
5
|
+
|
|
6
|
+
// Simple in-memory data store
|
|
7
|
+
let messages = [
|
|
8
|
+
{ id: 1, text: 'Hello, World!', author: 'System', timestamp: new Date() },
|
|
9
|
+
{ id: 2, text: 'Welcome to JSGUI3 Server JSON API', author: 'API', timestamp: new Date() }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
let nextId = 3;
|
|
13
|
+
|
|
14
|
+
Server.serve({
|
|
15
|
+
// No UI control - pure JSON API server
|
|
16
|
+
// ctrl: undefined,
|
|
17
|
+
|
|
18
|
+
api: {
|
|
19
|
+
// GET /api/messages - Get all messages
|
|
20
|
+
'messages': () => ({
|
|
21
|
+
messages: messages,
|
|
22
|
+
count: messages.length
|
|
23
|
+
}),
|
|
24
|
+
|
|
25
|
+
// GET /api/status - Server status
|
|
26
|
+
'status': () => ({
|
|
27
|
+
status: 'running',
|
|
28
|
+
timestamp: new Date(),
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
uptime: process.uptime()
|
|
31
|
+
}),
|
|
32
|
+
|
|
33
|
+
// POST /api/add-message - Add a new message
|
|
34
|
+
'add-message': (data) => {
|
|
35
|
+
if (!data.text || typeof data.text !== 'string') {
|
|
36
|
+
throw new Error('Message text is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (data.text.length > 500) {
|
|
40
|
+
throw new Error('Message text must be 500 characters or less');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const newMessage = {
|
|
44
|
+
id: nextId++,
|
|
45
|
+
text: data.text.trim(),
|
|
46
|
+
author: data.author || 'Anonymous',
|
|
47
|
+
timestamp: new Date()
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
messages.push(newMessage);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
message: newMessage
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// POST /api/clear-messages - Clear all messages
|
|
59
|
+
'clear-messages': () => {
|
|
60
|
+
const count = messages.length;
|
|
61
|
+
messages = [];
|
|
62
|
+
nextId = 1;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
clearedCount: count,
|
|
67
|
+
message: `Cleared ${count} messages`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
port: 3002
|
|
73
|
+
}).then(server => {
|
|
74
|
+
console.log('🚀 Simple JSON API Server started!');
|
|
75
|
+
console.log(`📡 Running at: http://localhost:${server.port}`);
|
|
76
|
+
console.log('\n📋 Available endpoints:');
|
|
77
|
+
console.log(' GET /api/messages - Get all messages');
|
|
78
|
+
console.log(' GET /api/status - Server status');
|
|
79
|
+
console.log(' POST /api/add-message - Add new message');
|
|
80
|
+
console.log(' POST /api/clear-messages - Clear all messages');
|
|
81
|
+
console.log('\n🧪 Test with:');
|
|
82
|
+
console.log(' curl http://localhost:3002/api/status');
|
|
83
|
+
console.log(' curl -X POST http://localhost:3002/api/add-message \\');
|
|
84
|
+
console.log(' -H "Content-Type: application/json" \\');
|
|
85
|
+
console.log(' -d \'{"text":"Hello from curl!","author":"Tester"}\'');
|
|
86
|
+
|
|
87
|
+
}).catch(err => {
|
|
88
|
+
console.error('❌ Failed to start server:', err.message);
|
|
89
|
+
console.error('❌ Full error:', err);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Test file for the simple JSON API example
|
|
2
|
+
// Runs the server and tests all endpoints
|
|
3
|
+
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
|
|
7
|
+
async function testSimpleAPI() {
|
|
8
|
+
console.log('🧪 Testing Simple JSON API Example');
|
|
9
|
+
console.log('=====================================\n');
|
|
10
|
+
|
|
11
|
+
let serverProcess;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Start the server
|
|
15
|
+
console.log('🚀 Starting server...');
|
|
16
|
+
serverProcess = spawn('node', ['server.js'], {
|
|
17
|
+
cwd: __dirname,
|
|
18
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Wait for server to start
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
let output = '';
|
|
24
|
+
const timeout = setTimeout(() => {
|
|
25
|
+
reject(new Error('Server startup timeout'));
|
|
26
|
+
}, 10000);
|
|
27
|
+
|
|
28
|
+
serverProcess.stdout.on('data', (data) => {
|
|
29
|
+
output += data.toString();
|
|
30
|
+
console.log('Server output:', data.toString().trim());
|
|
31
|
+
// Look for server started message indicating server is listening
|
|
32
|
+
if (output.includes('🚀 Simple JSON API Server started!')) {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
console.log('✅ Server started successfully and is listening');
|
|
35
|
+
// Wait a moment for server to be fully ready
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
console.log('⏳ Waiting for server to be ready...');
|
|
38
|
+
setTimeout(resolve, 0); // Brief additional wait
|
|
39
|
+
}, 0);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
serverProcess.stderr.on('data', (data) => {
|
|
44
|
+
console.error('Server error:', data.toString());
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
serverProcess.on('error', reject);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const baseUrl = 'http://localhost:3002';
|
|
51
|
+
|
|
52
|
+
// Test 1: GET /api/status
|
|
53
|
+
console.log('\n📋 Test 1: GET /api/status');
|
|
54
|
+
let statusResponse;
|
|
55
|
+
try {
|
|
56
|
+
statusResponse = await makeRequest('GET', '/api/status');
|
|
57
|
+
console.log('Response:', JSON.stringify(statusResponse, null, 2));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Request failed:', error.message);
|
|
60
|
+
console.error('Full error:', error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!statusResponse.status || statusResponse.status !== 'running') {
|
|
65
|
+
throw new Error('Status endpoint failed - expected status: "running"');
|
|
66
|
+
}
|
|
67
|
+
console.log('✅ Status test passed - server is running');
|
|
68
|
+
|
|
69
|
+
// Test 2: GET /api/messages
|
|
70
|
+
console.log('\n📋 Test 2: GET /api/messages');
|
|
71
|
+
const messagesResponse = await makeRequest('GET', '/api/messages');
|
|
72
|
+
console.log('Response:', JSON.stringify(messagesResponse, null, 2));
|
|
73
|
+
|
|
74
|
+
if (!messagesResponse.messages || !Array.isArray(messagesResponse.messages)) {
|
|
75
|
+
throw new Error('Messages endpoint failed - expected messages array');
|
|
76
|
+
}
|
|
77
|
+
console.log(`✅ Messages test passed - found ${messagesResponse.messages.length} initial messages`);
|
|
78
|
+
|
|
79
|
+
// Test 3: POST /api/add-message
|
|
80
|
+
console.log('\n📋 Test 3: POST /api/add-message');
|
|
81
|
+
const addMessageData = {
|
|
82
|
+
text: 'Test message from automated test',
|
|
83
|
+
author: 'Test Suite'
|
|
84
|
+
};
|
|
85
|
+
const addResponse = await makeRequest('POST', '/api/add-message', addMessageData);
|
|
86
|
+
console.log('Response:', JSON.stringify(addResponse, null, 2));
|
|
87
|
+
|
|
88
|
+
if (!addResponse.success || !addResponse.message) {
|
|
89
|
+
throw new Error('Add message endpoint failed - expected success response with message');
|
|
90
|
+
}
|
|
91
|
+
console.log('✅ Add message test passed - message added successfully');
|
|
92
|
+
|
|
93
|
+
// Test 4: Verify message was added
|
|
94
|
+
console.log('\n📋 Test 4: Verify message was added');
|
|
95
|
+
const messagesAfterAdd = await makeRequest('GET', '/api/messages');
|
|
96
|
+
const hasNewMessage = messagesAfterAdd.messages.some(msg =>
|
|
97
|
+
msg.text === 'Test message from automated test'
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (!hasNewMessage) {
|
|
101
|
+
throw new Error('Message was not added correctly - test message not found in messages list');
|
|
102
|
+
}
|
|
103
|
+
console.log('✅ Message verification test passed - test message found in list');
|
|
104
|
+
|
|
105
|
+
// Test 5: POST /api/clear-messages
|
|
106
|
+
console.log('\n📋 Test 5: POST /api/clear-messages');
|
|
107
|
+
const clearResponse = await makeRequest('POST', '/api/clear-messages');
|
|
108
|
+
console.log('Response:', JSON.stringify(clearResponse, null, 2));
|
|
109
|
+
|
|
110
|
+
if (!clearResponse.success) {
|
|
111
|
+
throw new Error('Clear messages endpoint failed - expected success response');
|
|
112
|
+
}
|
|
113
|
+
console.log('✅ Clear messages test passed - messages cleared successfully');
|
|
114
|
+
|
|
115
|
+
// Test 6: Verify messages were cleared
|
|
116
|
+
console.log('\n📋 Test 6: Verify messages were cleared');
|
|
117
|
+
const messagesAfterClear = await makeRequest('GET', '/api/messages');
|
|
118
|
+
|
|
119
|
+
if (messagesAfterClear.count !== 0) {
|
|
120
|
+
throw new Error('Messages were not cleared correctly - expected count to be 0');
|
|
121
|
+
}
|
|
122
|
+
console.log('✅ Message clearing verification test passed - all messages cleared');
|
|
123
|
+
|
|
124
|
+
// Test 7: Error handling
|
|
125
|
+
console.log('\n📋 Test 7: Error handling test');
|
|
126
|
+
try {
|
|
127
|
+
const errorResponse = await makeRequest('POST', '/api/add-message', { text: '' });
|
|
128
|
+
console.log('Error response:', JSON.stringify(errorResponse, null, 2));
|
|
129
|
+
if (!errorResponse.error || !errorResponse.error.includes('Message text is required')) {
|
|
130
|
+
throw new Error('Error handling test failed - expected error response with "Message text is required"');
|
|
131
|
+
}
|
|
132
|
+
console.log('✅ Error handling test passed - validation working correctly');
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new Error('Error handling test failed: ' + error.message);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('\n🎉 All tests passed! Simple JSON API is working correctly.');
|
|
138
|
+
console.log('\n📊 Test Summary:');
|
|
139
|
+
console.log(' ✅ Server startup');
|
|
140
|
+
console.log(' ✅ GET /api/status');
|
|
141
|
+
console.log(' ✅ GET /api/messages');
|
|
142
|
+
console.log(' ✅ POST /api/add-message');
|
|
143
|
+
console.log(' ✅ Message persistence');
|
|
144
|
+
console.log(' ✅ POST /api/clear-messages');
|
|
145
|
+
console.log(' ✅ Message clearing');
|
|
146
|
+
console.log(' ✅ Error handling');
|
|
147
|
+
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('\n❌ Test failed:', error.message);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
} finally {
|
|
152
|
+
// Clean up server process
|
|
153
|
+
if (serverProcess) {
|
|
154
|
+
console.log('\n🧹 Shutting down server...');
|
|
155
|
+
serverProcess.kill('SIGTERM');
|
|
156
|
+
|
|
157
|
+
// Wait a bit for graceful shutdown
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
if (!serverProcess.killed) {
|
|
160
|
+
serverProcess.kill('SIGKILL');
|
|
161
|
+
}
|
|
162
|
+
console.log('✅ Server shut down');
|
|
163
|
+
}, 2000);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Helper function to make HTTP requests
|
|
169
|
+
function makeRequest(method, path, data = null) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const options = {
|
|
172
|
+
hostname: 'localhost',
|
|
173
|
+
port: 3002,
|
|
174
|
+
path: path,
|
|
175
|
+
method: method,
|
|
176
|
+
headers: {
|
|
177
|
+
'Content-Type': 'application/json'
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const req = http.request(options, (res) => {
|
|
182
|
+
let body = '';
|
|
183
|
+
|
|
184
|
+
res.on('data', (chunk) => {
|
|
185
|
+
body += chunk;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
res.on('end', () => {
|
|
189
|
+
try {
|
|
190
|
+
const response = JSON.parse(body);
|
|
191
|
+
resolve(response);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
reject(new Error(`Failed to parse response: ${body}`));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
req.on('error', (error) => {
|
|
199
|
+
reject(error);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (data) {
|
|
203
|
+
req.write(JSON.stringify(data));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
req.end();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Run the tests
|
|
211
|
+
if (require.main === module) {
|
|
212
|
+
testSimpleAPI().catch(console.error);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = { testSimpleAPI };
|
|
@@ -84,8 +84,7 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
|
|
|
84
84
|
// Then write the (hopefully compressed) response bodies...
|
|
85
85
|
|
|
86
86
|
if (supported_encodings.br === true) {
|
|
87
|
-
|
|
88
|
-
res.write(response_buffers.br);
|
|
87
|
+
|
|
89
88
|
res.write(response_buffers.br);
|
|
90
89
|
} else if (supported_encodings.gzip === true) {
|
|
91
90
|
//console.log('should write gzipped buffer...');
|
package/package.json
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
"@babel/generator": "^7.28.5",
|
|
8
8
|
"@babel/parser": "^7.28.5",
|
|
9
9
|
"cookies": "^0.9.1",
|
|
10
|
-
"esbuild": "^0.25.
|
|
11
|
-
"fnl": "^0.0.
|
|
12
|
-
"fnlfs": "^0.0.
|
|
10
|
+
"esbuild": "^0.25.12",
|
|
11
|
+
"fnl": "^0.0.37",
|
|
12
|
+
"fnlfs": "^0.0.34",
|
|
13
13
|
"jsgui3-client": "^0.0.120",
|
|
14
|
-
"jsgui3-html": "^0.0.
|
|
14
|
+
"jsgui3-html": "^0.0.170",
|
|
15
15
|
"jsgui3-webpage": "^0.0.8",
|
|
16
16
|
"jsgui3-website": "^0.0.8",
|
|
17
|
-
"lang-tools": "^0.0.
|
|
17
|
+
"lang-tools": "^0.0.41",
|
|
18
18
|
"mocha": "^11.7.4",
|
|
19
19
|
"multiparty": "^4.2.3",
|
|
20
20
|
"ncp": "^2.0.0",
|
|
21
21
|
"obext": "^0.0.31",
|
|
22
|
-
"rimraf": "^6.0
|
|
22
|
+
"rimraf": "^6.1.0",
|
|
23
23
|
"stream-to-array": "^2.3.0",
|
|
24
24
|
"url-parse": "^1.5.10"
|
|
25
25
|
},
|
|
@@ -39,10 +39,21 @@
|
|
|
39
39
|
"type": "git",
|
|
40
40
|
"url": "https://github.com/metabench/jsgui3-server.git"
|
|
41
41
|
},
|
|
42
|
-
"version": "0.0.
|
|
42
|
+
"version": "0.0.140",
|
|
43
43
|
"scripts": {
|
|
44
44
|
"cli": "node cli.js",
|
|
45
45
|
"serve": "node cli.js serve",
|
|
46
|
-
"test": "
|
|
46
|
+
"test": "node tests/test-runner.js",
|
|
47
|
+
"test:mocha": "mocha tests/**/*.test.js",
|
|
48
|
+
"test:bundlers": "node tests/test-runner.js --test=bundlers.test.js",
|
|
49
|
+
"test:assigners": "node tests/test-runner.js --test=assigners.test.js",
|
|
50
|
+
"test:publishers": "node tests/test-runner.js --test=publishers.test.js",
|
|
51
|
+
"test:config": "node tests/test-runner.js --test=configuration-validation.test.js",
|
|
52
|
+
"test:e2e": "node tests/test-runner.js --test=end-to-end.test.js",
|
|
53
|
+
"test:content": "node tests/test-runner.js --test=content-analysis.test.js",
|
|
54
|
+
"test:performance": "node tests/test-runner.js --test=performance.test.js",
|
|
55
|
+
"test:errors": "node tests/test-runner.js --test=error-handling.test.js",
|
|
56
|
+
"test:debug": "node tests/test-runner.js --debug",
|
|
57
|
+
"test:verbose": "node tests/test-runner.js --verbose"
|
|
47
58
|
}
|
|
48
59
|
}
|
|
@@ -43,6 +43,16 @@ class Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner
|
|
|
43
43
|
|
|
44
44
|
constructor(spec = {}) {
|
|
45
45
|
super(spec);
|
|
46
|
+
|
|
47
|
+
// Store compression configuration
|
|
48
|
+
this.compression_config = spec.compression || {};
|
|
49
|
+
this.compression_stats = {
|
|
50
|
+
total_items: 0,
|
|
51
|
+
gzip_compressed: 0,
|
|
52
|
+
brotli_compressed: 0,
|
|
53
|
+
gzip_savings: 0,
|
|
54
|
+
brotli_savings: 0
|
|
55
|
+
};
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
// assign to bundle....
|
|
@@ -54,6 +64,18 @@ class Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner
|
|
|
54
64
|
// These assign functions should (all probably) be async
|
|
55
65
|
|
|
56
66
|
async assign(arr_bundled_items) {
|
|
67
|
+
// Get compression configuration with defaults
|
|
68
|
+
const enabled = this.compression_config.enabled !== false; // Default: true
|
|
69
|
+
const algorithms = this.compression_config.algorithms || ['gzip', 'br'];
|
|
70
|
+
const gzipLevel = this.compression_config.gzip?.level || 6;
|
|
71
|
+
const brotliQuality = this.compression_config.brotli?.quality || 6;
|
|
72
|
+
const threshold = this.compression_config.threshold || 1024;
|
|
73
|
+
|
|
74
|
+
if (!enabled) {
|
|
75
|
+
console.log('Compression disabled, skipping compression assignment');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
// go through them....
|
|
58
80
|
|
|
59
81
|
// Maybe check that the correct items are in the bundle.
|
|
@@ -84,20 +106,42 @@ class Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner
|
|
|
84
106
|
// // response_buffers.identity I think....
|
|
85
107
|
|
|
86
108
|
if (item.text) {
|
|
109
|
+
const originalSize = item.response_buffers.identity.length;
|
|
110
|
+
this.compression_stats.total_items++;
|
|
111
|
+
|
|
112
|
+
// Skip compression if below threshold
|
|
113
|
+
if (originalSize < threshold) {
|
|
114
|
+
console.log(`Skipping compression for ${item.type} (${originalSize} bytes < ${threshold} threshold)`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
87
117
|
|
|
88
118
|
// Async compression definitely seems much better here.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
if (algorithms.includes('gzip')) {
|
|
120
|
+
const buf_gzipped = await gzip_compress(item.response_buffers.identity, { level: gzipLevel });
|
|
121
|
+
item.response_buffers.gzip = buf_gzipped;
|
|
122
|
+
const compressedSize = buf_gzipped.length;
|
|
123
|
+
const savings = originalSize - compressedSize;
|
|
124
|
+
this.compression_stats.gzip_compressed++;
|
|
125
|
+
this.compression_stats.gzip_savings += savings;
|
|
126
|
+
console.log(`Gzip compressed ${item.type}: ${originalSize} → ${compressedSize} bytes (${Math.round(savings/originalSize*100)}% savings)`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (algorithms.includes('br')) {
|
|
130
|
+
const buf_br = await br_compress(item.response_buffers.identity, {
|
|
131
|
+
params: {
|
|
132
|
+
//[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
|
|
133
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: brotliQuality,
|
|
134
|
+
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: item.response_buffers.identity.length,
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
item.response_buffers.br = buf_br;
|
|
139
|
+
const compressedSize = buf_br.length;
|
|
140
|
+
const savings = originalSize - compressedSize;
|
|
141
|
+
this.compression_stats.brotli_compressed++;
|
|
142
|
+
this.compression_stats.brotli_savings += savings;
|
|
143
|
+
console.log(`Brotli compressed ${item.type}: ${originalSize} → ${compressedSize} bytes (${Math.round(savings/originalSize*100)}% savings)`);
|
|
144
|
+
}
|
|
101
145
|
} else {
|
|
102
146
|
|
|
103
147
|
}
|
|
@@ -105,6 +149,15 @@ class Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner
|
|
|
105
149
|
//throw 'stop';
|
|
106
150
|
}
|
|
107
151
|
|
|
152
|
+
// Log compression statistics
|
|
153
|
+
console.log('Compression Statistics:', {
|
|
154
|
+
total_items: this.compression_stats.total_items,
|
|
155
|
+
gzip_compressed: this.compression_stats.gzip_compressed,
|
|
156
|
+
brotli_compressed: this.compression_stats.brotli_compressed,
|
|
157
|
+
total_gzip_savings: `${Math.round(this.compression_stats.gzip_savings/1024)}KB`,
|
|
158
|
+
total_brotli_savings: `${Math.round(this.compression_stats.brotli_savings/1024)}KB`
|
|
159
|
+
});
|
|
160
|
+
|
|
108
161
|
} else {
|
|
109
162
|
console.trace();
|
|
110
163
|
throw 'stop';
|
|
@@ -17,12 +17,17 @@ class Static_Routes_Responses_Webpage_Bundle_Preparer {
|
|
|
17
17
|
|
|
18
18
|
//if (spec.debug !== undefined) this.debug = spec.debug;
|
|
19
19
|
|
|
20
|
+
// Store bundler configuration to pass to compression assigner
|
|
21
|
+
this.bundler_config = spec.bundler_config || {};
|
|
22
|
+
|
|
20
23
|
this.routes_assigner = new Single_Control_Webpage_Server_Static_Routes_Assigner();
|
|
21
24
|
|
|
22
25
|
// And the uncompressed response buffer(s) assigner....?
|
|
23
26
|
|
|
24
27
|
this.uncompressed_response_buffers_assigner = new Single_Control_Webpage_Server_Static_Uncompressed_Response_Buffers_Assigner();
|
|
25
|
-
this.compressed_response_buffers_assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner(
|
|
28
|
+
this.compressed_response_buffers_assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
29
|
+
compression: this.bundler_config.compression
|
|
30
|
+
});
|
|
26
31
|
this.headers_assigner = new Single_Control_Webpage_Server_Static_Headers_Assigner();
|
|
27
32
|
|
|
28
33
|
|
|
@@ -110,7 +110,12 @@ class Function_Publisher extends HTTP_Publisher {
|
|
|
110
110
|
} else {
|
|
111
111
|
|
|
112
112
|
if (content_type === 'application/json') {
|
|
113
|
-
|
|
113
|
+
const inputStr = buf_input.toString();
|
|
114
|
+
if (inputStr.trim() === '') {
|
|
115
|
+
obj_input = null;
|
|
116
|
+
} else {
|
|
117
|
+
obj_input = JSON.parse(inputStr);
|
|
118
|
+
}
|
|
114
119
|
} else {
|
|
115
120
|
console.trace();
|
|
116
121
|
throw 'NYI';
|
|
@@ -141,57 +146,73 @@ class Function_Publisher extends HTTP_Publisher {
|
|
|
141
146
|
// And the function to call may be async.
|
|
142
147
|
// Can test to see if we get a promise (or observable) back from it.
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
+
try {
|
|
150
|
+
const fn_res = fn(obj_input);
|
|
151
|
+
const tfr = tf(fn_res);
|
|
152
|
+
//console.log('fn_res', fn_res);
|
|
149
153
|
|
|
150
|
-
|
|
151
|
-
// promise
|
|
152
|
-
console.log('need to await promise resolution');
|
|
154
|
+
//
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
if (tfr === 'p') {
|
|
157
|
+
// promise
|
|
158
|
+
console.log('need to await promise resolution');
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
fn_res.then(call_res => {
|
|
161
|
+
console.log('fn_res then happened, call_res', call_res);
|
|
162
|
+
output_all(call_res);
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
}, err => {
|
|
165
|
+
console.error('Function execution error:', err);
|
|
166
|
+
if (!res.headersSent) {
|
|
167
|
+
res.writeHead(500, {
|
|
168
|
+
'Content-Type': 'application/json'
|
|
169
|
+
});
|
|
170
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
171
|
+
}
|
|
172
|
+
});
|
|
161
173
|
|
|
162
174
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
} else if (tfr === 's') {
|
|
176
|
+
// Just write it as a string for the moment I think?
|
|
177
|
+
// Or always encode as JSON?
|
|
166
178
|
|
|
167
|
-
|
|
179
|
+
// text/plain;charset=UTF-8
|
|
168
180
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
res.writeHead(200, {
|
|
182
|
+
'Content-Type': 'text/plain;charset=UTF-8'//,
|
|
183
|
+
//'Transfer-Encoding': 'chunked',
|
|
184
|
+
//'Trailer': 'Content-MD5'
|
|
185
|
+
});
|
|
186
|
+
res.end(fn_res);
|
|
175
187
|
|
|
176
188
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
189
|
+
} else if (tfr === 'o' || tfr === 'a') {
|
|
190
|
+
// Just write it as a string for the moment I think?
|
|
191
|
+
// Or always encode as JSON?
|
|
180
192
|
|
|
181
|
-
|
|
193
|
+
// text/plain;charset=UTF-8
|
|
182
194
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
195
|
+
res.writeHead(200, {
|
|
196
|
+
'Content-Type': 'application/json'//,
|
|
197
|
+
//'Transfer-Encoding': 'chunked',
|
|
198
|
+
//'Trailer': 'Content-MD5'
|
|
199
|
+
});
|
|
200
|
+
res.end(JSON.stringify(fn_res));
|
|
189
201
|
|
|
190
202
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
203
|
+
} else {
|
|
204
|
+
console.log('tfr', tfr);
|
|
205
|
+
console.trace();
|
|
206
|
+
throw 'NYI';
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error('Function execution error:', err);
|
|
210
|
+
if (!res.headersSent) {
|
|
211
|
+
res.writeHead(500, {
|
|
212
|
+
'Content-Type': 'application/json'
|
|
213
|
+
});
|
|
214
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
215
|
+
}
|
|
195
216
|
}
|
|
196
217
|
|
|
197
218
|
|