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.
Files changed (57) hide show
  1. package/AGENTS.md +87 -0
  2. package/README.md +12 -0
  3. package/docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md +19 -0
  4. package/docs/advanced-usage-examples.md +1360 -0
  5. package/docs/agent-development-guide.md +386 -0
  6. package/docs/api-reference.md +916 -0
  7. package/docs/broken-functionality-tracker.md +285 -0
  8. package/docs/bundling-system-deep-dive.md +525 -0
  9. package/docs/cli-reference.md +393 -0
  10. package/docs/comprehensive-documentation.md +1403 -0
  11. package/docs/configuration-reference.md +808 -0
  12. package/docs/controls-development.md +859 -0
  13. package/docs/documentation-review/CURRENT_REVIEW.md +95 -0
  14. package/docs/function-publishers-json-apis.md +847 -0
  15. package/docs/getting-started-with-json.md +518 -0
  16. package/docs/minification-compression-sourcemaps-status.md +482 -0
  17. package/docs/minification-compression-sourcemaps-test-results.md +205 -0
  18. package/docs/publishers-guide.md +313 -0
  19. package/docs/resources-guide.md +615 -0
  20. package/docs/serve-helpers.md +406 -0
  21. package/docs/simple-server-api-design.md +13 -0
  22. package/docs/system-architecture.md +275 -0
  23. package/docs/troubleshooting.md +698 -0
  24. package/examples/json/README.md +115 -0
  25. package/examples/json/basic-api/README.md +345 -0
  26. package/examples/json/basic-api/server.js +199 -0
  27. package/examples/json/simple-api/README.md +125 -0
  28. package/examples/json/simple-api/diagnostic-report.json +73 -0
  29. package/examples/json/simple-api/diagnostic-test.js +433 -0
  30. package/examples/json/simple-api/server-debug.md +58 -0
  31. package/examples/json/simple-api/server.js +91 -0
  32. package/examples/json/simple-api/test.js +215 -0
  33. package/http/responders/static/Static_Route_HTTP_Responder.js +1 -2
  34. package/package.json +19 -8
  35. package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +65 -12
  36. package/publishers/helpers/preparers/static/bundle/Static_Routes_Responses_Webpage_Bundle_Preparer.js +6 -1
  37. package/publishers/http-function-publisher.js +59 -38
  38. package/publishers/http-webpage-publisher.js +48 -1
  39. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +38 -146
  40. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +54 -5
  41. package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +36 -4
  42. package/serve-factory.js +36 -9
  43. package/server.js +10 -4
  44. package/test-report.json +0 -0
  45. package/tests/README.md +250 -0
  46. package/tests/assigners.test.js +316 -0
  47. package/tests/bundlers.test.js +329 -0
  48. package/tests/configuration-validation.test.js +530 -0
  49. package/tests/content-analysis.test.js +641 -0
  50. package/tests/end-to-end.test.js +496 -0
  51. package/tests/error-handling.test.js +746 -0
  52. package/tests/performance.test.js +653 -0
  53. package/tests/publishers.test.js +395 -0
  54. package/tests/temp_invalid.js +7 -0
  55. package/tests/temp_invalid_utf8.js +1 -0
  56. package/tests/temp_malformed.js +10 -0
  57. package/tests/test-runner.js +261 -0
@@ -0,0 +1,1360 @@
1
+ # Advanced Usage Examples
2
+
3
+ ## When to Read
4
+
5
+ This document provides practical examples of advanced JSGUI3 Server features. Read this when:
6
+ - You want to implement complex server configurations
7
+ - You're building multi-page applications
8
+ - You need API integration examples
9
+ - You want to understand advanced control patterns
10
+ - You're implementing production-ready features
11
+
12
+ **Note:** For basic usage, see [README.md](../README.md). For API reference, see [docs/api-reference.md](docs/api-reference.md).
13
+
14
+ ## Multi-Page Application with API
15
+
16
+ ### Complete E-commerce Dashboard
17
+
18
+ ```javascript
19
+ // server.js - Full-featured e-commerce server
20
+ const Server = require('jsgui3-server');
21
+ const { controls } = require('./client');
22
+
23
+ // Simulated database (in real app, use actual DB)
24
+ const db = {
25
+ products: [
26
+ { id: 1, name: 'Widget A', price: 29.99, stock: 50 },
27
+ { id: 2, name: 'Widget B', price: 39.99, stock: 30 }
28
+ ],
29
+ orders: [],
30
+ users: [{ id: 1, name: 'Admin', role: 'admin' }]
31
+ };
32
+
33
+ Server.serve({
34
+ pages: {
35
+ '/': {
36
+ content: controls.Dashboard,
37
+ title: 'E-commerce Dashboard'
38
+ },
39
+ '/products': {
40
+ content: controls.ProductManager,
41
+ title: 'Product Management'
42
+ },
43
+ '/orders': {
44
+ content: controls.OrderManager,
45
+ title: 'Order Management'
46
+ },
47
+ '/analytics': {
48
+ content: controls.Analytics,
49
+ title: 'Sales Analytics'
50
+ }
51
+ },
52
+
53
+ api: {
54
+ // Product management
55
+ 'products': () => db.products,
56
+
57
+ 'product': ({ id }) => {
58
+ const product = db.products.find(p => p.id === parseInt(id));
59
+ if (!product) throw new Error('Product not found');
60
+ return product;
61
+ },
62
+
63
+ 'add-product': ({ name, price, stock }) => {
64
+ const newProduct = {
65
+ id: db.products.length + 1,
66
+ name,
67
+ price: parseFloat(price),
68
+ stock: parseInt(stock)
69
+ };
70
+ db.products.push(newProduct);
71
+ return { success: true, product: newProduct };
72
+ },
73
+
74
+ 'update-product': ({ id, ...updates }) => {
75
+ const product = db.products.find(p => p.id === parseInt(id));
76
+ if (!product) throw new Error('Product not found');
77
+
78
+ Object.assign(product, updates);
79
+ return { success: true, product };
80
+ },
81
+
82
+ // Order management
83
+ 'orders': () => db.orders,
84
+
85
+ 'create-order': ({ productId, quantity, customerEmail }) => {
86
+ const product = db.products.find(p => p.id === parseInt(productId));
87
+ if (!product) throw new Error('Product not found');
88
+ if (product.stock < quantity) throw new Error('Insufficient stock');
89
+
90
+ const order = {
91
+ id: db.orders.length + 1,
92
+ productId: parseInt(productId),
93
+ quantity: parseInt(quantity),
94
+ customerEmail,
95
+ total: product.price * quantity,
96
+ status: 'pending',
97
+ createdAt: new Date()
98
+ };
99
+
100
+ db.orders.push(order);
101
+ product.stock -= quantity;
102
+
103
+ return { success: true, order };
104
+ },
105
+
106
+ // Analytics
107
+ 'analytics/summary': () => {
108
+ const totalRevenue = db.orders
109
+ .filter(o => o.status === 'completed')
110
+ .reduce((sum, o) => sum + o.total, 0);
111
+
112
+ const totalOrders = db.orders.length;
113
+ const pendingOrders = db.orders.filter(o => o.status === 'pending').length;
114
+
115
+ return {
116
+ totalRevenue,
117
+ totalOrders,
118
+ pendingOrders,
119
+ topProducts: db.products
120
+ .map(p => ({
121
+ ...p,
122
+ sold: db.orders
123
+ .filter(o => o.productId === p.id && o.status === 'completed')
124
+ .reduce((sum, o) => sum + o.quantity, 0)
125
+ }))
126
+ .sort((a, b) => b.sold - a.sold)
127
+ .slice(0, 5)
128
+ };
129
+ }
130
+ },
131
+
132
+ port: 3000,
133
+ debug: process.env.NODE_ENV !== 'production'
134
+ });
135
+ ```
136
+
137
+ ```javascript
138
+ // client.js - Client-side controls
139
+ const jsgui = require('jsgui3-client');
140
+ const { controls, Control, Data_Object, field } = jsgui;
141
+ const Active_HTML_Document = require('jsgui3-server/controls/Active_HTML_Document');
142
+
143
+ // Dashboard Control
144
+ class Dashboard extends Active_HTML_Document {
145
+ constructor(spec = {}) {
146
+ spec.__type_name = 'dashboard';
147
+ super(spec);
148
+ const { context } = this;
149
+
150
+ if (typeof this.body.add_class === 'function') {
151
+ this.body.add_class('dashboard');
152
+ }
153
+
154
+ this.compose();
155
+ }
156
+
157
+ compose() {
158
+ // Navigation
159
+ const nav = new controls.Panel({
160
+ context: this.context,
161
+ class: 'nav'
162
+ });
163
+
164
+ const navLinks = [
165
+ { text: 'Dashboard', path: '/' },
166
+ { text: 'Products', path: '/products' },
167
+ { text: 'Orders', path: '/orders' },
168
+ { text: 'Analytics', path: '/analytics' }
169
+ ];
170
+
171
+ navLinks.forEach(link => {
172
+ const button = new controls.Button({
173
+ context: this.context,
174
+ text: link.text
175
+ });
176
+ button.on('click', () => {
177
+ window.location.href = link.path;
178
+ });
179
+ nav.add(button);
180
+ });
181
+
182
+ // Summary cards
183
+ const summaryContainer = new controls.Panel({
184
+ context: this.context,
185
+ class: 'summary-cards'
186
+ });
187
+
188
+ // Load analytics data
189
+ fetch('/api/analytics/summary')
190
+ .then(res => res.json())
191
+ .then(data => {
192
+ const cards = [
193
+ { title: 'Total Revenue', value: `$${data.totalRevenue.toFixed(2)}` },
194
+ { title: 'Total Orders', value: data.totalOrders },
195
+ { title: 'Pending Orders', value: data.pendingOrders }
196
+ ];
197
+
198
+ cards.forEach(card => {
199
+ const cardEl = new controls.Panel({
200
+ context: this.context,
201
+ class: 'summary-card'
202
+ });
203
+
204
+ const titleEl = new controls.Text({
205
+ context: this.context,
206
+ text: card.title,
207
+ class: 'card-title'
208
+ });
209
+
210
+ const valueEl = new controls.Text({
211
+ context: this.context,
212
+ text: card.value,
213
+ class: 'card-value'
214
+ });
215
+
216
+ cardEl.add(titleEl);
217
+ cardEl.add(valueEl);
218
+ summaryContainer.add(cardEl);
219
+ });
220
+ });
221
+
222
+ this.body.add(nav);
223
+ this.body.add(summaryContainer);
224
+ }
225
+
226
+ activate() {
227
+ if (!this.__active) {
228
+ super.activate();
229
+ // Additional activation logic
230
+ }
231
+ }
232
+ }
233
+
234
+ Dashboard.css = `
235
+ .dashboard {
236
+ font-family: Arial, sans-serif;
237
+ max-width: 1200px;
238
+ margin: 0 auto;
239
+ padding: 20px;
240
+ }
241
+
242
+ .nav {
243
+ display: flex;
244
+ gap: 10px;
245
+ margin-bottom: 30px;
246
+ padding: 15px;
247
+ background: #f8f9fa;
248
+ border-radius: 5px;
249
+ }
250
+
251
+ .summary-cards {
252
+ display: grid;
253
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
254
+ gap: 20px;
255
+ }
256
+
257
+ .summary-card {
258
+ padding: 20px;
259
+ background: white;
260
+ border: 1px solid #ddd;
261
+ border-radius: 8px;
262
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
263
+ }
264
+
265
+ .card-title {
266
+ font-size: 14px;
267
+ color: #666;
268
+ margin-bottom: 10px;
269
+ }
270
+
271
+ .card-value {
272
+ font-size: 24px;
273
+ font-weight: bold;
274
+ color: #333;
275
+ }
276
+ `;
277
+
278
+ controls.Dashboard = Dashboard;
279
+ module.exports = jsgui;
280
+ ```
281
+
282
+ ## Real-time Data Synchronization
283
+
284
+ ### Collaborative Document Editor
285
+
286
+ ```javascript
287
+ // server.js - Real-time collaborative editing
288
+ const Server = require('jsgui3-server');
289
+ const WebSocket = require('ws');
290
+
291
+ // In-memory document store (use Redis in production)
292
+ const documents = new Map();
293
+ const clients = new Map(); // docId -> Set of WebSocket clients
294
+
295
+ Server.serve({
296
+ ctrl: require('./client').controls.DocumentEditor,
297
+
298
+ api: {
299
+ 'document': ({ id }) => {
300
+ return documents.get(id) || { id, content: '', version: 0 };
301
+ },
302
+
303
+ 'save-document': ({ id, content, version }) => {
304
+ const doc = documents.get(id) || { id, version: 0 };
305
+ if (version < doc.version) {
306
+ throw new Error('Version conflict');
307
+ }
308
+
309
+ doc.content = content;
310
+ doc.version = version + 1;
311
+ doc.lastModified = new Date();
312
+ documents.set(id, doc);
313
+
314
+ // Broadcast to all clients
315
+ const docClients = clients.get(id);
316
+ if (docClients) {
317
+ const update = { type: 'update', document: doc };
318
+ docClients.forEach(client => {
319
+ if (client.readyState === WebSocket.OPEN) {
320
+ client.send(JSON.stringify(update));
321
+ }
322
+ });
323
+ }
324
+
325
+ return { success: true, document: doc };
326
+ }
327
+ },
328
+
329
+ port: 3000,
330
+
331
+ // Custom WebSocket setup
332
+ setup: (server) => {
333
+ const wss = new WebSocket.Server({ server });
334
+
335
+ wss.on('connection', (ws, req) => {
336
+ const docId = new URL(req.url, 'http://localhost').searchParams.get('doc');
337
+
338
+ if (docId) {
339
+ if (!clients.has(docId)) {
340
+ clients.set(docId, new Set());
341
+ }
342
+ clients.get(docId).add(ws);
343
+
344
+ ws.on('close', () => {
345
+ clients.get(docId)?.delete(ws);
346
+ if (clients.get(docId)?.size === 0) {
347
+ clients.delete(docId);
348
+ }
349
+ });
350
+ }
351
+ });
352
+ }
353
+ });
354
+ ```
355
+
356
+ ```javascript
357
+ // client.js - Real-time document editor
358
+ const jsgui = require('jsgui3-client');
359
+ const { controls, Control, Data_Object, field } = jsgui;
360
+ const Active_HTML_Document = require('jsgui3-server/controls/Active_HTML_Document');
361
+
362
+ class DocumentEditor extends Active_HTML_Document {
363
+ constructor(spec = {}) {
364
+ spec.__type_name = 'document_editor';
365
+ super(spec);
366
+ const { context } = this;
367
+
368
+ // Get document ID from URL
369
+ this.docId = new URL(window.location.href).searchParams.get('doc') || 'default';
370
+
371
+ // Create reactive document model
372
+ this.document = new Data_Object({ context });
373
+ field(this.document, 'content');
374
+ field(this.document, 'version');
375
+ context.register_control(this.document);
376
+
377
+ this.compose();
378
+ this.connectWebSocket();
379
+ }
380
+
381
+ compose() {
382
+ const container = new controls.Panel({
383
+ context: this.context,
384
+ class: 'editor-container'
385
+ });
386
+
387
+ // Document title
388
+ const title = new controls.Text_Input({
389
+ context: this.context,
390
+ placeholder: 'Document Title',
391
+ class: 'doc-title'
392
+ });
393
+
394
+ // Content editor
395
+ this.contentEditor = new controls.Text_Area({
396
+ context: this.context,
397
+ placeholder: 'Start writing...',
398
+ class: 'content-editor'
399
+ });
400
+
401
+ // Bind to reactive model
402
+ this.contentEditor.data = { model: this.document, field_name: 'content' };
403
+
404
+ // Status indicator
405
+ this.statusIndicator = new controls.Text({
406
+ context: this.context,
407
+ text: 'Connecting...',
408
+ class: 'status'
409
+ });
410
+
411
+ // Save button
412
+ const saveButton = new controls.Button({
413
+ context: this.context,
414
+ text: 'Save',
415
+ class: 'save-btn'
416
+ });
417
+
418
+ saveButton.on('click', () => this.saveDocument());
419
+
420
+ container.add(title);
421
+ container.add(this.contentEditor);
422
+ container.add(this.statusIndicator);
423
+ container.add(saveButton);
424
+
425
+ this.body.add(container);
426
+
427
+ // Load existing document
428
+ this.loadDocument();
429
+ }
430
+
431
+ async loadDocument() {
432
+ try {
433
+ const response = await fetch(`/api/document?id=${this.docId}`);
434
+ const doc = await response.json();
435
+
436
+ this.document.content = doc.content;
437
+ this.document.version = doc.version;
438
+ this.updateStatus('Loaded');
439
+ } catch (error) {
440
+ this.updateStatus('Error loading document');
441
+ console.error('Load error:', error);
442
+ }
443
+ }
444
+
445
+ async saveDocument() {
446
+ try {
447
+ this.updateStatus('Saving...');
448
+
449
+ const response = await fetch('/api/save-document', {
450
+ method: 'POST',
451
+ headers: { 'Content-Type': 'application/json' },
452
+ body: JSON.stringify({
453
+ id: this.docId,
454
+ content: this.document.content,
455
+ version: this.document.version
456
+ })
457
+ });
458
+
459
+ const result = await response.json();
460
+ if (result.success) {
461
+ this.document.version = result.document.version;
462
+ this.updateStatus('Saved');
463
+ } else {
464
+ this.updateStatus('Save failed');
465
+ }
466
+ } catch (error) {
467
+ this.updateStatus('Save error');
468
+ console.error('Save error:', error);
469
+ }
470
+ }
471
+
472
+ connectWebSocket() {
473
+ const ws = new WebSocket(`ws://localhost:3000?doc=${this.docId}`);
474
+
475
+ ws.onopen = () => {
476
+ this.updateStatus('Connected');
477
+ };
478
+
479
+ ws.onmessage = (event) => {
480
+ const data = JSON.parse(event.data);
481
+ if (data.type === 'update' && data.document.version > this.document.version) {
482
+ this.document.content = data.document.content;
483
+ this.document.version = data.document.version;
484
+ this.updateStatus('Updated from server');
485
+ }
486
+ };
487
+
488
+ ws.onclose = () => {
489
+ this.updateStatus('Disconnected');
490
+ // Attempt reconnection after delay
491
+ setTimeout(() => this.connectWebSocket(), 5000);
492
+ };
493
+
494
+ ws.onerror = (error) => {
495
+ console.error('WebSocket error:', error);
496
+ this.updateStatus('Connection error');
497
+ };
498
+ }
499
+
500
+ updateStatus(message) {
501
+ if (this.statusIndicator) {
502
+ this.statusIndicator.text = message;
503
+ }
504
+ }
505
+
506
+ activate() {
507
+ if (!this.__active) {
508
+ super.activate();
509
+ const { context } = this;
510
+
511
+ // Auto-save on content changes (debounced)
512
+ let saveTimeout;
513
+ this.document.on('change', (e) => {
514
+ if (e.field_name === 'content') {
515
+ clearTimeout(saveTimeout);
516
+ saveTimeout = setTimeout(() => {
517
+ if (this.document.content) {
518
+ this.saveDocument();
519
+ }
520
+ }, 2000); // Save after 2 seconds of no typing
521
+ }
522
+ });
523
+ }
524
+ }
525
+ }
526
+
527
+ DocumentEditor.css = `
528
+ .editor-container {
529
+ max-width: 800px;
530
+ margin: 0 auto;
531
+ padding: 20px;
532
+ }
533
+
534
+ .doc-title {
535
+ width: 100%;
536
+ padding: 10px;
537
+ font-size: 24px;
538
+ border: 1px solid #ddd;
539
+ border-radius: 4px;
540
+ margin-bottom: 20px;
541
+ }
542
+
543
+ .content-editor {
544
+ width: 100%;
545
+ height: 400px;
546
+ padding: 15px;
547
+ border: 1px solid #ddd;
548
+ border-radius: 4px;
549
+ font-family: monospace;
550
+ font-size: 14px;
551
+ line-height: 1.4;
552
+ resize: vertical;
553
+ }
554
+
555
+ .status {
556
+ margin: 10px 0;
557
+ padding: 5px 10px;
558
+ background: #e9ecef;
559
+ border-radius: 4px;
560
+ font-size: 12px;
561
+ color: #666;
562
+ }
563
+
564
+ .save-btn {
565
+ padding: 10px 20px;
566
+ background: #007bff;
567
+ color: white;
568
+ border: none;
569
+ border-radius: 4px;
570
+ cursor: pointer;
571
+ margin-top: 10px;
572
+ }
573
+
574
+ .save-btn:hover {
575
+ background: #0056b3;
576
+ }
577
+ `;
578
+
579
+ controls.DocumentEditor = DocumentEditor;
580
+ module.exports = jsgui;
581
+ ```
582
+
583
+ ## Advanced Control Patterns
584
+
585
+ ### Custom Data-Bound Controls
586
+
587
+ ```javascript
588
+ // client.js - Advanced data-bound controls
589
+ const jsgui = require('jsgui3-client');
590
+ const { controls, Control, Data_Object, field, mixins } = jsgui;
591
+ const { dragable } = mixins;
592
+ const Active_HTML_Document = require('jsgui3-server/controls/Active_HTML_Document');
593
+
594
+ // Data Table with Sorting and Filtering
595
+ class DataTable extends Control {
596
+ constructor(spec = {}) {
597
+ spec.__type_name = 'data_table';
598
+ super(spec);
599
+ const { context } = this;
600
+
601
+ this.data = spec.data || [];
602
+ this.columns = spec.columns || [];
603
+ this.sortColumn = null;
604
+ this.sortDirection = 'asc';
605
+ this.filterText = '';
606
+
607
+ this.compose();
608
+ }
609
+
610
+ compose() {
611
+ const table = document.createElement('table');
612
+ table.className = 'data-table';
613
+
614
+ // Header
615
+ const thead = document.createElement('thead');
616
+ const headerRow = document.createElement('tr');
617
+
618
+ this.columns.forEach(column => {
619
+ const th = document.createElement('th');
620
+ th.textContent = column.title;
621
+ th.className = 'sortable';
622
+ th.onclick = () => this.sortBy(column.key);
623
+ headerRow.appendChild(th);
624
+ });
625
+
626
+ thead.appendChild(headerRow);
627
+ table.appendChild(thead);
628
+
629
+ // Filter input
630
+ const filterContainer = document.createElement('div');
631
+ filterContainer.className = 'filter-container';
632
+
633
+ const filterInput = document.createElement('input');
634
+ filterInput.type = 'text';
635
+ filterInput.placeholder = 'Filter...';
636
+ filterInput.className = 'filter-input';
637
+ filterInput.oninput = (e) => {
638
+ this.filterText = e.target.value.toLowerCase();
639
+ this.renderBody(table);
640
+ };
641
+
642
+ filterContainer.appendChild(filterInput);
643
+ this.dom.el.appendChild(filterContainer);
644
+ this.dom.el.appendChild(table);
645
+
646
+ this.table = table;
647
+ this.renderBody(table);
648
+ }
649
+
650
+ sortBy(columnKey) {
651
+ if (this.sortColumn === columnKey) {
652
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
653
+ } else {
654
+ this.sortColumn = columnKey;
655
+ this.sortDirection = 'asc';
656
+ }
657
+ this.renderBody(this.table);
658
+ }
659
+
660
+ getFilteredAndSortedData() {
661
+ let filtered = this.data;
662
+
663
+ if (this.filterText) {
664
+ filtered = this.data.filter(item =>
665
+ Object.values(item).some(value =>
666
+ String(value).toLowerCase().includes(this.filterText)
667
+ )
668
+ );
669
+ }
670
+
671
+ if (this.sortColumn) {
672
+ filtered = [...filtered].sort((a, b) => {
673
+ const aVal = a[this.sortColumn];
674
+ const bVal = b[this.sortColumn];
675
+
676
+ let result = 0;
677
+ if (aVal < bVal) result = -1;
678
+ if (aVal > bVal) result = 1;
679
+
680
+ return this.sortDirection === 'asc' ? result : -result;
681
+ });
682
+ }
683
+
684
+ return filtered;
685
+ }
686
+
687
+ renderBody(table) {
688
+ // Remove existing body
689
+ const existingBody = table.querySelector('tbody');
690
+ if (existingBody) {
691
+ existingBody.remove();
692
+ }
693
+
694
+ const tbody = document.createElement('tbody');
695
+ const data = this.getFilteredAndSortedData();
696
+
697
+ data.forEach(item => {
698
+ const row = document.createElement('tr');
699
+
700
+ this.columns.forEach(column => {
701
+ const cell = document.createElement('td');
702
+ const value = item[column.key];
703
+
704
+ if (column.renderer) {
705
+ cell.innerHTML = column.renderer(value, item);
706
+ } else {
707
+ cell.textContent = value;
708
+ }
709
+
710
+ row.appendChild(cell);
711
+ });
712
+
713
+ tbody.appendChild(row);
714
+ });
715
+
716
+ table.appendChild(tbody);
717
+ }
718
+
719
+ setData(data) {
720
+ this.data = data;
721
+ this.renderBody(this.table);
722
+ }
723
+ }
724
+
725
+ DataTable.css = `
726
+ .data-table {
727
+ width: 100%;
728
+ border-collapse: collapse;
729
+ margin-top: 10px;
730
+ }
731
+
732
+ .data-table th,
733
+ .data-table td {
734
+ padding: 8px 12px;
735
+ text-align: left;
736
+ border-bottom: 1px solid #ddd;
737
+ }
738
+
739
+ .data-table th {
740
+ background-color: #f8f9fa;
741
+ font-weight: bold;
742
+ cursor: pointer;
743
+ user-select: none;
744
+ }
745
+
746
+ .data-table th:hover {
747
+ background-color: #e9ecef;
748
+ }
749
+
750
+ .data-table th.sortable::after {
751
+ content: ' ⇅';
752
+ opacity: 0.5;
753
+ }
754
+
755
+ .data-table th.sortable.sorted-asc::after {
756
+ content: ' ↑';
757
+ opacity: 1;
758
+ }
759
+
760
+ .data-table th.sortable.sorted-desc::after {
761
+ content: ' ↓';
762
+ opacity: 1;
763
+ }
764
+
765
+ .filter-container {
766
+ margin-bottom: 10px;
767
+ }
768
+
769
+ .filter-input {
770
+ width: 100%;
771
+ padding: 8px;
772
+ border: 1px solid #ddd;
773
+ border-radius: 4px;
774
+ font-size: 14px;
775
+ }
776
+ `;
777
+
778
+ // Usage example
779
+ class ProductManager extends Active_HTML_Document {
780
+ constructor(spec = {}) {
781
+ spec.__type_name = 'product_manager';
782
+ super(spec);
783
+ const { context } = this;
784
+
785
+ this.compose();
786
+ this.loadProducts();
787
+ }
788
+
789
+ compose() {
790
+ const container = new controls.Panel({
791
+ context: this.context,
792
+ class: 'product-manager'
793
+ });
794
+
795
+ // Add product button
796
+ const addButton = new controls.Button({
797
+ context: this.context,
798
+ text: 'Add Product',
799
+ class: 'add-btn'
800
+ });
801
+
802
+ addButton.on('click', () => this.showAddProductDialog());
803
+
804
+ // Products table
805
+ this.table = new DataTable({
806
+ context: this.context,
807
+ columns: [
808
+ { key: 'id', title: 'ID' },
809
+ { key: 'name', title: 'Name' },
810
+ { key: 'price', title: 'Price', renderer: (value) => `$${value.toFixed(2)}` },
811
+ { key: 'stock', title: 'Stock' },
812
+ {
813
+ key: 'actions',
814
+ title: 'Actions',
815
+ renderer: (value, item) => `
816
+ <button onclick="editProduct(${item.id})">Edit</button>
817
+ <button onclick="deleteProduct(${item.id})" class="delete">Delete</button>
818
+ `
819
+ }
820
+ ]
821
+ });
822
+
823
+ container.add(addButton);
824
+ container.add(this.table);
825
+ this.body.add(container);
826
+ }
827
+
828
+ async loadProducts() {
829
+ try {
830
+ const response = await fetch('/api/products');
831
+ const products = await response.json();
832
+ this.table.setData(products);
833
+ } catch (error) {
834
+ console.error('Failed to load products:', error);
835
+ }
836
+ }
837
+
838
+ showAddProductDialog() {
839
+ // Implementation for add product dialog
840
+ alert('Add product functionality would be implemented here');
841
+ }
842
+ }
843
+
844
+ ProductManager.css = `
845
+ .product-manager {
846
+ padding: 20px;
847
+ }
848
+
849
+ .add-btn {
850
+ padding: 10px 20px;
851
+ background: #28a745;
852
+ color: white;
853
+ border: none;
854
+ border-radius: 4px;
855
+ cursor: pointer;
856
+ margin-bottom: 20px;
857
+ }
858
+
859
+ .add-btn:hover {
860
+ background: #218838;
861
+ }
862
+ `;
863
+
864
+ // Make functions global for inline event handlers
865
+ window.editProduct = (id) => {
866
+ alert(`Edit product ${id}`);
867
+ };
868
+
869
+ window.deleteProduct = (id) => {
870
+ if (confirm('Are you sure you want to delete this product?')) {
871
+ alert(`Delete product ${id}`);
872
+ }
873
+ };
874
+
875
+ controls.DataTable = DataTable;
876
+ controls.ProductManager = ProductManager;
877
+ module.exports = jsgui;
878
+ ```
879
+
880
+ ## Production Configuration Examples
881
+
882
+ ### Load Balancing Setup
883
+
884
+ ```javascript
885
+ // server.js - Production server with clustering
886
+ const cluster = require('cluster');
887
+ const os = require('os');
888
+ const Server = require('jsgui3-server');
889
+
890
+ if (cluster.isMaster) {
891
+ // Master process
892
+ const numCPUs = os.cpus().length;
893
+
894
+ console.log(`Master ${process.pid} is running`);
895
+ console.log(`Starting ${numCPUs} workers...`);
896
+
897
+ // Fork workers
898
+ for (let i = 0; i < numCPUs; i++) {
899
+ cluster.fork();
900
+ }
901
+
902
+ cluster.on('exit', (worker, code, signal) => {
903
+ console.log(`Worker ${worker.process.pid} died`);
904
+ // Restart worker
905
+ cluster.fork();
906
+ });
907
+
908
+ } else {
909
+ // Worker process
910
+ Server.serve({
911
+ ctrl: require('./client').controls.App,
912
+ port: process.env.PORT || 3000,
913
+ debug: false,
914
+
915
+ // Production optimizations
916
+ cache: {
917
+ static: { maxAge: 86400 }, // 24 hours
918
+ api: { maxAge: 300 } // 5 minutes
919
+ },
920
+
921
+ compression: true,
922
+ etag: true,
923
+
924
+ // Health check endpoint
925
+ api: {
926
+ 'health': () => ({
927
+ status: 'ok',
928
+ uptime: process.uptime(),
929
+ memory: process.memoryUsage(),
930
+ pid: process.pid
931
+ })
932
+ }
933
+ }).catch(err => {
934
+ console.error(`Worker ${process.pid} failed:`, err);
935
+ process.exit(1);
936
+ });
937
+ }
938
+ ```
939
+
940
+ ### Docker Deployment
941
+
942
+ ```dockerfile
943
+ # Dockerfile
944
+ FROM node:18-alpine
945
+
946
+ WORKDIR /app
947
+
948
+ # Install dependencies
949
+ COPY package*.json ./
950
+ RUN npm ci --only=production
951
+
952
+ # Copy application
953
+ COPY . .
954
+
955
+ # Create non-root user
956
+ RUN addgroup -g 1001 -S nodejs && \
957
+ adduser -S appuser -u 1001
958
+
959
+ USER appuser
960
+
961
+ EXPOSE 3000
962
+
963
+ # Health check
964
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
965
+ CMD node -e "
966
+ const http = require('http');
967
+ const options = { hostname: 'localhost', port: 3000, path: '/api/health', method: 'GET' };
968
+ const req = http.request(options, (res) => {
969
+ if (res.statusCode === 200) process.exit(0);
970
+ else process.exit(1);
971
+ });
972
+ req.on('error', () => process.exit(1));
973
+ req.end();
974
+ "
975
+
976
+ CMD ["node", "server.js"]
977
+ ```
978
+
979
+ ```yaml
980
+ # docker-compose.yml
981
+ version: '3.8'
982
+ services:
983
+ jsgui3-app:
984
+ build: .
985
+ ports:
986
+ - "3000:3000"
987
+ environment:
988
+ - NODE_ENV=production
989
+ - PORT=3000
990
+ restart: unless-stopped
991
+ healthcheck:
992
+ test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
993
+ interval: 30s
994
+ timeout: 10s
995
+ retries: 3
996
+ start_period: 40s
997
+ networks:
998
+ - app-network
999
+
1000
+ nginx:
1001
+ image: nginx:alpine
1002
+ ports:
1003
+ - "80:80"
1004
+ volumes:
1005
+ - ./nginx.conf:/etc/nginx/nginx.conf:ro
1006
+ depends_on:
1007
+ - jsgui3-app
1008
+ networks:
1009
+ - app-network
1010
+
1011
+ networks:
1012
+ app-network:
1013
+ driver: bridge
1014
+ ```
1015
+
1016
+ ```nginx
1017
+ # nginx.conf
1018
+ events {
1019
+ worker_connections 1024;
1020
+ }
1021
+
1022
+ http {
1023
+ upstream app_backend {
1024
+ server jsgui3-app:3000;
1025
+ }
1026
+
1027
+ server {
1028
+ listen 80;
1029
+ server_name localhost;
1030
+
1031
+ # Gzip compression
1032
+ gzip on;
1033
+ gzip_types text/plain text/css application/json application/javascript;
1034
+
1035
+ # Static file caching
1036
+ location /css/ {
1037
+ proxy_pass http://app_backend;
1038
+ expires 1y;
1039
+ add_header Cache-Control "public, immutable";
1040
+ }
1041
+
1042
+ location /js/ {
1043
+ proxy_pass http://app_backend;
1044
+ expires 1y;
1045
+ add_header Cache-Control "public, immutable";
1046
+ }
1047
+
1048
+ # API with shorter cache
1049
+ location /api/ {
1050
+ proxy_pass http://app_backend;
1051
+ expires 5m;
1052
+ add_header Cache-Control "public, must-revalidate, proxy-revalidate";
1053
+ }
1054
+
1055
+ # Main app
1056
+ location / {
1057
+ proxy_pass http://app_backend;
1058
+ proxy_set_header Host $host;
1059
+ proxy_set_header X-Real-IP $remote_addr;
1060
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
1061
+ proxy_set_header X-Forwarded-Proto $scheme;
1062
+ }
1063
+ }
1064
+ }
1065
+ ```
1066
+
1067
+ ## Authentication and Security
1068
+
1069
+ ### JWT-Based Authentication
1070
+
1071
+ ```javascript
1072
+ // server.js - Authentication system
1073
+ const jwt = require('jsonwebtoken');
1074
+ const bcrypt = require('bcrypt');
1075
+ const Server = require('jsgui3-server');
1076
+
1077
+ // In-memory user store (use database in production)
1078
+ const users = [
1079
+ { id: 1, username: 'admin', password: '$2b$10$...' } // hashed password
1080
+ ];
1081
+
1082
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
1083
+
1084
+ Server.serve({
1085
+ pages: {
1086
+ '/': {
1087
+ content: require('./client').controls.Dashboard,
1088
+ title: 'Dashboard'
1089
+ },
1090
+ '/login': {
1091
+ content: require('./client').controls.Login,
1092
+ title: 'Login'
1093
+ }
1094
+ },
1095
+
1096
+ api: {
1097
+ 'login': async ({ username, password }) => {
1098
+ const user = users.find(u => u.username === username);
1099
+ if (!user) {
1100
+ throw new Error('Invalid credentials');
1101
+ }
1102
+
1103
+ const validPassword = await bcrypt.compare(password, user.password);
1104
+ if (!validPassword) {
1105
+ throw new Error('Invalid credentials');
1106
+ }
1107
+
1108
+ const token = jwt.sign(
1109
+ { userId: user.id, username: user.username },
1110
+ JWT_SECRET,
1111
+ { expiresIn: '24h' }
1112
+ );
1113
+
1114
+ return { token, user: { id: user.id, username: user.username } };
1115
+ },
1116
+
1117
+ 'verify-token': ({ token }) => {
1118
+ try {
1119
+ const decoded = jwt.verify(token, JWT_SECRET);
1120
+ return { valid: true, user: decoded };
1121
+ } catch (error) {
1122
+ return { valid: false, error: error.message };
1123
+ }
1124
+ },
1125
+
1126
+ 'protected-data': ({ token }) => {
1127
+ const verification = jwt.verify(token, JWT_SECRET);
1128
+ if (!verification.valid) {
1129
+ throw new Error('Unauthorized');
1130
+ }
1131
+
1132
+ // Return protected data
1133
+ return { secret: 'This is protected data' };
1134
+ }
1135
+ },
1136
+
1137
+ // Middleware for authentication
1138
+ middleware: [
1139
+ (req, res, next) => {
1140
+ // Add CORS headers
1141
+ res.setHeader('Access-Control-Allow-Origin', '*');
1142
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
1143
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
1144
+
1145
+ if (req.method === 'OPTIONS') {
1146
+ res.statusCode = 200;
1147
+ res.end();
1148
+ return;
1149
+ }
1150
+
1151
+ next();
1152
+ }
1153
+ ],
1154
+
1155
+ port: 3000
1156
+ });
1157
+ ```
1158
+
1159
+ ```javascript
1160
+ // client.js - Client-side authentication
1161
+ const jsgui = require('jsgui3-client');
1162
+ const { controls, Control } = jsgui;
1163
+ const Active_HTML_Document = require('jsgui3-server/controls/Active_HTML_Document');
1164
+
1165
+ class Login extends Active_HTML_Document {
1166
+ constructor(spec = {}) {
1167
+ spec.__type_name = 'login';
1168
+ super(spec);
1169
+ const { context } = this;
1170
+
1171
+ this.token = localStorage.getItem('authToken');
1172
+ if (this.token) {
1173
+ // Verify token and redirect if valid
1174
+ this.verifyToken();
1175
+ } else {
1176
+ this.showLoginForm();
1177
+ }
1178
+ }
1179
+
1180
+ showLoginForm() {
1181
+ const form = new controls.Panel({
1182
+ context: this.context,
1183
+ class: 'login-form'
1184
+ });
1185
+
1186
+ const title = new controls.Text({
1187
+ context: this.context,
1188
+ text: 'Login',
1189
+ class: 'form-title'
1190
+ });
1191
+
1192
+ this.usernameInput = new controls.Text_Input({
1193
+ context: this.context,
1194
+ placeholder: 'Username',
1195
+ class: 'form-input'
1196
+ });
1197
+
1198
+ this.passwordInput = new controls.Text_Input({
1199
+ context: this.context,
1200
+ placeholder: 'Password',
1201
+ type: 'password',
1202
+ class: 'form-input'
1203
+ });
1204
+
1205
+ this.loginButton = new controls.Button({
1206
+ context: this.context,
1207
+ text: 'Login',
1208
+ class: 'login-btn'
1209
+ });
1210
+
1211
+ this.messageDiv = new controls.Text({
1212
+ context: this.context,
1213
+ text: '',
1214
+ class: 'message'
1215
+ });
1216
+
1217
+ this.loginButton.on('click', () => this.attemptLogin());
1218
+
1219
+ form.add(title);
1220
+ form.add(this.usernameInput);
1221
+ form.add(this.passwordInput);
1222
+ form.add(this.loginButton);
1223
+ form.add(this.messageDiv);
1224
+
1225
+ this.body.add(form);
1226
+ }
1227
+
1228
+ async attemptLogin() {
1229
+ try {
1230
+ this.setMessage('Logging in...');
1231
+
1232
+ const response = await fetch('/api/login', {
1233
+ method: 'POST',
1234
+ headers: { 'Content-Type': 'application/json' },
1235
+ body: JSON.stringify({
1236
+ username: this.usernameInput.dom.el.value,
1237
+ password: this.passwordInput.dom.el.value
1238
+ })
1239
+ });
1240
+
1241
+ const result = await response.json();
1242
+
1243
+ if (result.token) {
1244
+ localStorage.setItem('authToken', result.token);
1245
+ this.setMessage('Login successful! Redirecting...');
1246
+ setTimeout(() => {
1247
+ window.location.href = '/';
1248
+ }, 1000);
1249
+ } else {
1250
+ this.setMessage('Login failed');
1251
+ }
1252
+ } catch (error) {
1253
+ this.setMessage('Login error: ' + error.message);
1254
+ }
1255
+ }
1256
+
1257
+ async verifyToken() {
1258
+ try {
1259
+ const response = await fetch('/api/verify-token', {
1260
+ method: 'POST',
1261
+ headers: { 'Content-Type': 'application/json' },
1262
+ body: JSON.stringify({ token: this.token })
1263
+ });
1264
+
1265
+ const result = await response.json();
1266
+
1267
+ if (result.valid) {
1268
+ // Token is valid, redirect to dashboard
1269
+ window.location.href = '/';
1270
+ } else {
1271
+ // Token invalid, show login form
1272
+ localStorage.removeItem('authToken');
1273
+ this.showLoginForm();
1274
+ }
1275
+ } catch (error) {
1276
+ localStorage.removeItem('authToken');
1277
+ this.showLoginForm();
1278
+ }
1279
+ }
1280
+
1281
+ setMessage(text) {
1282
+ if (this.messageDiv) {
1283
+ this.messageDiv.text = text;
1284
+ }
1285
+ }
1286
+ }
1287
+
1288
+ Login.css = `
1289
+ .login-form {
1290
+ max-width: 400px;
1291
+ margin: 50px auto;
1292
+ padding: 30px;
1293
+ border: 1px solid #ddd;
1294
+ border-radius: 8px;
1295
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
1296
+ }
1297
+
1298
+ .form-title {
1299
+ font-size: 24px;
1300
+ text-align: center;
1301
+ margin-bottom: 30px;
1302
+ color: #333;
1303
+ }
1304
+
1305
+ .form-input {
1306
+ width: 100%;
1307
+ padding: 12px;
1308
+ margin-bottom: 15px;
1309
+ border: 1px solid #ddd;
1310
+ border-radius: 4px;
1311
+ font-size: 16px;
1312
+ }
1313
+
1314
+ .login-btn {
1315
+ width: 100%;
1316
+ padding: 12px;
1317
+ background: #007bff;
1318
+ color: white;
1319
+ border: none;
1320
+ border-radius: 4px;
1321
+ font-size: 16px;
1322
+ cursor: pointer;
1323
+ }
1324
+
1325
+ .login-btn:hover {
1326
+ background: #0056b3;
1327
+ }
1328
+
1329
+ .message {
1330
+ margin-top: 15px;
1331
+ text-align: center;
1332
+ min-height: 20px;
1333
+ }
1334
+ `;
1335
+
1336
+ // Global auth helper
1337
+ window.Auth = {
1338
+ getToken: () => localStorage.getItem('authToken'),
1339
+
1340
+ logout: () => {
1341
+ localStorage.removeItem('authToken');
1342
+ window.location.href = '/login';
1343
+ },
1344
+
1345
+ // Add auth headers to fetch requests
1346
+ authenticatedFetch: (url, options = {}) => {
1347
+ const token = Auth.getToken();
1348
+ if (token) {
1349
+ options.headers = options.headers || {};
1350
+ options.headers['Authorization'] = `Bearer ${token}`;
1351
+ }
1352
+ return fetch(url, options);
1353
+ }
1354
+ };
1355
+
1356
+ controls.Login = Login;
1357
+ module.exports = jsgui;
1358
+ ```
1359
+
1360
+ These examples demonstrate advanced patterns for building complex applications with JSGUI3 Server, including real-time collaboration, custom controls, production deployment, and authentication systems.