jsgui3-server 0.0.151 → 0.0.152

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 (77) hide show
  1. package/README.md +21 -0
  2. package/admin-ui/v1/controls/admin_shell.js +33 -0
  3. package/admin-ui/v1/server.js +14 -1
  4. package/docs/api-reference.md +120 -2
  5. package/docs/books/website-design/01-introduction.md +73 -0
  6. package/docs/books/website-design/02-current-state.md +195 -0
  7. package/docs/books/website-design/03-base-class.md +181 -0
  8. package/docs/books/website-design/04-webpage.md +307 -0
  9. package/docs/books/website-design/05-website.md +456 -0
  10. package/docs/books/website-design/06-pages-storage.md +170 -0
  11. package/docs/books/website-design/07-api-layer.md +285 -0
  12. package/docs/books/website-design/08-server-integration.md +271 -0
  13. package/docs/books/website-design/09-cross-agent-review.md +190 -0
  14. package/docs/books/website-design/10-open-questions.md +196 -0
  15. package/docs/books/website-design/11-converged-recommendation.md +205 -0
  16. package/docs/books/website-design/12-content-model.md +395 -0
  17. package/docs/books/website-design/13-webpage-module-spec.md +404 -0
  18. package/docs/books/website-design/14-website-module-spec.md +541 -0
  19. package/docs/books/website-design/15-multi-repo-plan.md +275 -0
  20. package/docs/books/website-design/16-minimal-first.md +203 -0
  21. package/docs/books/website-design/17-implementation-report-codex.md +81 -0
  22. package/docs/books/website-design/README.md +43 -0
  23. package/docs/configuration-reference.md +54 -0
  24. package/docs/proposals/jsgui3-website-and-webpage-design-jsgui3-server-support.md +257 -0
  25. package/docs/proposals/jsgui3-website-and-webpage-design-review.md +73 -0
  26. package/docs/proposals/jsgui3-website-and-webpage-design.md +732 -0
  27. package/docs/swagger.md +316 -0
  28. package/examples/controls/1) window/server.js +6 -1
  29. package/examples/controls/21) mvvm and declarative api/check.js +94 -0
  30. package/examples/controls/21) mvvm and declarative api/check_output.txt +25 -0
  31. package/examples/controls/21) mvvm and declarative api/check_output_2.txt +27 -0
  32. package/examples/controls/21) mvvm and declarative api/client.js +241 -0
  33. declarative api/e2e-screenshot-1-name-change.png +0 -0
  34. declarative api/e2e-screenshot-2-toggled.png +0 -0
  35. declarative api/e2e-screenshot-3-final.png +0 -0
  36. declarative api/e2e-screenshot-final.png +0 -0
  37. package/examples/controls/21) mvvm and declarative api/e2e-test.js +175 -0
  38. package/examples/controls/21) mvvm and declarative api/out.html +1 -0
  39. package/examples/controls/21) mvvm and declarative api/page_out.html +1 -0
  40. package/examples/controls/21) mvvm and declarative api/server.js +18 -0
  41. package/examples/data-views/01) query-endpoint/server.js +61 -0
  42. package/labs/website-design/001-base-class-overhead/check.js +162 -0
  43. package/labs/website-design/002-pages-storage/check.js +244 -0
  44. package/labs/website-design/002-pages-storage/results.txt +0 -0
  45. package/labs/website-design/003-type-detection/check.js +193 -0
  46. package/labs/website-design/003-type-detection/results.txt +0 -0
  47. package/labs/website-design/004-two-stage-validation/check.js +314 -0
  48. package/labs/website-design/004-two-stage-validation/results.txt +0 -0
  49. package/labs/website-design/005-normalize-input/check.js +303 -0
  50. package/labs/website-design/006-serve-website-spike/check.js +290 -0
  51. package/labs/website-design/README.md +34 -0
  52. package/labs/website-design/manifest.json +68 -0
  53. package/labs/website-design/run-all.js +60 -0
  54. package/middleware/json-body.js +126 -0
  55. package/openapi.js +474 -0
  56. package/package.json +11 -8
  57. package/publishers/Publishers.js +6 -5
  58. package/publishers/http-function-publisher.js +135 -126
  59. package/publishers/http-webpage-publisher.js +89 -11
  60. package/publishers/query-publisher.js +116 -0
  61. package/publishers/swagger-publisher.js +203 -0
  62. package/publishers/swagger-ui.js +578 -0
  63. package/resources/adapters/array-adapter.js +143 -0
  64. package/resources/query-resource.js +131 -0
  65. package/serve-factory.js +728 -18
  66. package/server.js +421 -103
  67. package/tests/README.md +23 -1
  68. package/tests/admin-ui-jsgui-controls.test.js +16 -1
  69. package/tests/helpers/playwright-e2e-harness.js +326 -0
  70. package/tests/openapi.test.js +319 -0
  71. package/tests/playwright-smoke.test.js +134 -0
  72. package/tests/publish-enhancements.test.js +673 -0
  73. package/tests/query-publisher.test.js +430 -0
  74. package/tests/quick-json-body-test.js +169 -0
  75. package/tests/serve.test.js +425 -122
  76. package/tests/swagger-publisher.test.js +1076 -0
  77. package/tests/test-runner.js +1 -0
@@ -0,0 +1,732 @@
1
+ # Design Proposals: jsgui3-website & jsgui3-webpage
2
+
3
+ > **Date**: 2026-02-15
4
+ > **Status**: Proposals for review — no implementation yet
5
+ > **Context**: These two sibling NPM packages are intended to provide abstract definitions of websites and webpages within the jsgui3 ecosystem. They should be useful abstractions but NOT required by `jsgui3-server`.
6
+
7
+ ---
8
+
9
+ ## 1. Current State of the Repos
10
+
11
+ Both repos are near-empty skeletons. Here is the **exact, complete** code in each.
12
+
13
+ ### 1.1 jsgui3-webpage
14
+
15
+ **Repository**: `github.com/metabench/jsgui3-webpage`
16
+ **Version**: 0.0.8
17
+ **Files**: 4 source files (+ `.git`, `.gitignore`)
18
+
19
+ #### `Webpage.js` — The entire module (14 lines)
20
+
21
+ ```js
22
+ /*
23
+ Probably does not need to do very much apart from hold info for the moment.
24
+ Could make subclasses do things like generare its specific parts from spec.
25
+ */
26
+
27
+ class Webpage {
28
+ constructor(spec) {
29
+ Object.assign(this, spec);
30
+ }
31
+ }
32
+
33
+ module.exports = Webpage;
34
+ ```
35
+
36
+ #### `package.json`
37
+
38
+ ```json
39
+ {
40
+ "name": "jsgui3-webpage",
41
+ "main": "Webpage.js",
42
+ "license": "MIT",
43
+ "comments": [
44
+ "Just depend on jsgui3-html for the moment. This would be a website in the abstract sense.",
45
+ { "jsgui3-html": "^0.0.139" }
46
+ ],
47
+ "dependencies": {},
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/metabench/jsgui3-webpage.git"
51
+ },
52
+ "author": "James Vickers <james@metabench.com>",
53
+ "version": "0.0.8"
54
+ }
55
+ ```
56
+
57
+ > **Note**: `jsgui3-html` is referenced in `comments` but is NOT in `dependencies`. There are zero runtime dependencies.
58
+
59
+ #### `README.md`
60
+
61
+ ```
62
+ # jsgui3-webpage
63
+ A class that represents a webpage.
64
+ ```
65
+
66
+ **Summary**: A single class that does `Object.assign(this, spec)`. No dependencies, no methods, no validation.
67
+
68
+ ---
69
+
70
+ ### 1.2 jsgui3-website
71
+
72
+ **Repository**: `github.com/metabench/jsgui3-website`
73
+ **Version**: 0.0.8
74
+ **Files**: 5 source files (+ `.git`, `.gitignore`)
75
+
76
+ #### `Website.js` — Main module (16 lines)
77
+
78
+ ```js
79
+ /*
80
+ Probably does not need to do very much apart from hold info for the moment.
81
+ Could make subclasses do things like generare its specific parts from spec.
82
+ */
83
+ const API = require('./API');
84
+
85
+ class Website {
86
+ constructor(spec) {
87
+ Object.assign(this, spec);
88
+ this.api = new API({ server: this.server });
89
+ }
90
+ }
91
+
92
+ module.exports = Website;
93
+ ```
94
+
95
+ #### `API.js` — API stub (15 lines, contains a bug)
96
+
97
+ ```js
98
+ class API {
99
+ constructor(spec) {
100
+ this.server = spec.server;
101
+ }
102
+
103
+ publish(name, fn) {
104
+ // Need to access the appropriate resource publisher.
105
+ const {server} = this;
106
+ }
107
+ }
108
+
109
+ MediaSourceHandle.exports = API
110
+ ```
111
+
112
+ > **BUG**: Line 15 says `MediaSourceHandle.exports = API` — this is a typo for `module.exports = API` and will crash at runtime.
113
+
114
+ #### `package.json`
115
+
116
+ ```json
117
+ {
118
+ "name": "jsgui3-website",
119
+ "main": "Website.js",
120
+ "license": "MIT",
121
+ "comments": [
122
+ "Just depend on jsgui3-html for the moment. This would be a website in the abstract sense.",
123
+ { "jsgui3-html": "^0.0.139" }
124
+ ],
125
+ "dependencies": {},
126
+ "repository": {
127
+ "type": "git",
128
+ "url": "https://github.com/metabench/jsgui3-website.git"
129
+ },
130
+ "author": "James Vickers <james@metabench.com>",
131
+ "version": "0.0.8"
132
+ }
133
+ ```
134
+
135
+ > **Note**: Same pattern — `jsgui3-html` mentioned in `comments` but zero actual dependencies.
136
+
137
+ #### `README.md`
138
+
139
+ ```
140
+ # jsgui3-website
141
+ A class that represents a website. Also has functionality to deploy the website.
142
+ ```
143
+
144
+ **Summary**: Two classes. `Website` does `Object.assign` + creates an `API`. `API` has an empty `publish()` stub and a fatal export bug. No dependencies, no pages collection, no routing.
145
+
146
+ ---
147
+
148
+ ### 1.3 How jsgui3-server Currently Uses These
149
+
150
+ `jsgui3-server` depends on both packages (`^0.0.8`). Here's how they're currently consumed:
151
+
152
+ | Server file | What it does |
153
+ |---|---|
154
+ | `website/website.js` | `module.exports = require('jsgui3-website')` — direct re-export (has dead `Obselete_Style_Website` class) |
155
+ | `website/webpage.js` | `module.exports = require('jsgui3-webpage')` — direct re-export (has dead `Obselete_Style_Webpage` class) |
156
+ | `server.js:354` | `new Webpage({ content: Ctrl })` — creates webpage from a control constructor |
157
+ | `server.js:428` | `new Website(opts_website)` — creates website when no Ctrl is provided |
158
+ | `http-website-publisher.js:117` | `spec.website instanceof Website` — type-checks the website |
159
+ | `serve-factory.js:33` | `new Webpage({ name, title, content, path })` — creates webpages from serve options |
160
+
161
+ The Webpage is used as a simple property bag. The Website is used as a placeholder that the publisher wraps.
162
+
163
+ ---
164
+
165
+ ## 2. Design Goals
166
+
167
+ 1. **Useful abstraction** — describe a website/webpage in the abstract, independent of how it's served
168
+ 2. **Optional** — `jsgui3-server` must continue to work without these modules being required in user code
169
+ 3. **Ecosystem-consistent** — follow jsgui3 patterns (obext, Collection, etc.) where it makes sense
170
+ 4. **Inspectable** — admin/tooling can introspect the website structure
171
+ 5. **Extensible** — subclassable for specific website types
172
+
173
+ ---
174
+
175
+ ## 3. Proposals for jsgui3-webpage
176
+
177
+ ### Proposal A: Minimal — Enhanced Property Bag
178
+
179
+ Keep the current approach but add explicit property support and documentation.
180
+
181
+ ```js
182
+ const jsgui = require('jsgui3-html');
183
+
184
+ class Webpage {
185
+ constructor(spec = {}) {
186
+ this.name = spec.name || undefined;
187
+ this.title = spec.title || undefined;
188
+ this.path = spec.path || '/';
189
+ this.content = spec.content || undefined;
190
+ this.meta = spec.meta || {};
191
+ this.client_js = spec.client_js || undefined;
192
+ }
193
+ }
194
+
195
+ module.exports = Webpage;
196
+ ```
197
+
198
+ **Advantages**:
199
+ - Dead simple — easy to understand, debug, and extend
200
+ - Minimal surface area — less to break
201
+ - Consistent with the current usage pattern in `serve-factory.js`
202
+ - Easy for the server to duck-type (just check for `.path` and `.content`)
203
+ - Extremely small module size
204
+
205
+ **Disadvantages**:
206
+ - No validation — any rubbish can be put in
207
+ - No computed properties or helpers
208
+ - No change tracking — can't observe mutations
209
+ - Properties are all public and mutable — no encapsulation
210
+ - Not using any jsgui3-html features despite depending on it
211
+
212
+ ---
213
+
214
+ ### Proposal B: Observable Properties via obext
215
+
216
+ Use `obext` (already in the ecosystem) for getter/setter properties with potential change observation.
217
+
218
+ ```js
219
+ const jsgui = require('jsgui3-html');
220
+ const { prop, read_only } = require('obext');
221
+
222
+ class Webpage {
223
+ constructor(spec = {}) {
224
+ prop(this, 'name', spec.name);
225
+ prop(this, 'title', spec.title);
226
+ prop(this, 'path', spec.path || '/');
227
+ prop(this, 'content', spec.content);
228
+ prop(this, 'meta', spec.meta || {});
229
+ prop(this, 'client_js', spec.client_js);
230
+ prop(this, 'favicon', spec.favicon);
231
+ prop(this, 'scripts', spec.scripts || []);
232
+ prop(this, 'stylesheets', spec.stylesheets || []);
233
+ }
234
+
235
+ get has_content() {
236
+ return this.content !== undefined;
237
+ }
238
+
239
+ get is_dynamic() {
240
+ return typeof this.content === 'function';
241
+ }
242
+
243
+ toJSON() {
244
+ return {
245
+ name: this.name,
246
+ title: this.title,
247
+ path: this.path,
248
+ has_content: this.has_content,
249
+ is_dynamic: this.is_dynamic,
250
+ meta: this.meta
251
+ };
252
+ }
253
+ }
254
+
255
+ module.exports = Webpage;
256
+ ```
257
+
258
+ **Advantages**:
259
+ - Consistent with how `jsgui3-server` defines properties (uses `obext` extensively)
260
+ - Properties are encapsulated via getter/setter — room for future change events
261
+ - `toJSON()` makes it inspectable (useful for admin, debugging, serialization)
262
+ - Computed properties (`has_content`, `is_dynamic`) are useful for the server/publisher
263
+ - Adding `obext` as a dependency is trivial since `jsgui3-html` already depends on it
264
+
265
+ **Disadvantages**:
266
+ - More complex than a plain object
267
+ - `obext` dependency (though it's already transitive via `jsgui3-html`)
268
+ - Property interception may have slight performance overhead (negligible in practice)
269
+ - Might be over-engineering for what is currently a property bag
270
+
271
+ ---
272
+
273
+ ### Proposal C: Evented — Extends jsgui3's Evented_Class
274
+
275
+ Make Webpage an evented object that can emit change events for reactive systems.
276
+
277
+ ```js
278
+ const jsgui = require('jsgui3-html');
279
+ const { Evented_Class } = jsgui;
280
+
281
+ class Webpage extends Evented_Class {
282
+ constructor(spec = {}) {
283
+ super();
284
+ this._name = spec.name;
285
+ this._title = spec.title;
286
+ this._path = spec.path || '/';
287
+ this._content = spec.content;
288
+ this._meta = spec.meta || {};
289
+ this._client_js = spec.client_js;
290
+ }
291
+
292
+ get name() { return this._name; }
293
+ set name(v) {
294
+ const old = this._name;
295
+ this._name = v;
296
+ this.raise('change', { field: 'name', old, value: v });
297
+ }
298
+
299
+ get title() { return this._title; }
300
+ set title(v) {
301
+ const old = this._title;
302
+ this._title = v;
303
+ this.raise('change', { field: 'title', old, value: v });
304
+ }
305
+
306
+ get path() { return this._path; }
307
+ set path(v) {
308
+ const old = this._path;
309
+ this._path = v;
310
+ this.raise('change', { field: 'path', old, value: v });
311
+ }
312
+
313
+ get content() { return this._content; }
314
+ set content(v) {
315
+ const old = this._content;
316
+ this._content = v;
317
+ this.raise('change', { field: 'content', old, value: v });
318
+ }
319
+
320
+ get has_content() { return this._content !== undefined; }
321
+ get is_dynamic() { return typeof this._content === 'function'; }
322
+
323
+ toJSON() {
324
+ return {
325
+ name: this._name, title: this._title, path: this._path,
326
+ has_content: this.has_content, is_dynamic: this.is_dynamic,
327
+ meta: this._meta
328
+ };
329
+ }
330
+ }
331
+
332
+ module.exports = Webpage;
333
+ ```
334
+
335
+ **Advantages**:
336
+ - Fully reactive — admin UI or other systems can watch for page changes
337
+ - Consistent with other jsgui3 evented objects
338
+ - Enables live-reloading workflows (change page content → event fires → server re-bundles)
339
+ - `Evented_Class` is battle-tested within the ecosystem
340
+
341
+ **Disadvantages**:
342
+ - Significantly more code and complexity
343
+ - Every property setter fires events even when nobody is listening (slight overhead)
344
+ - Inheriting from `Evented_Class` couples this to jsgui3's event system
345
+ - YAGNI risk — no current use case needs change events on webpages
346
+ - Makes the class harder to understand for newcomers
347
+ - Boilerplate-heavy getter/setter pattern for every property
348
+
349
+ ---
350
+
351
+ ## 4. Proposals for jsgui3-website
352
+
353
+ ### Proposal A: Minimal — Pages Array + API Object
354
+
355
+ ```js
356
+ const jsgui = require('jsgui3-html');
357
+ const Webpage = require('jsgui3-webpage');
358
+
359
+ class Website {
360
+ constructor(spec = {}) {
361
+ this.name = spec.name || undefined;
362
+ this.pages = [];
363
+ this.api = {};
364
+ this.meta = spec.meta || {};
365
+ this.assets = spec.assets || {};
366
+
367
+ // Initialize pages from spec
368
+ if (spec.pages) {
369
+ if (Array.isArray(spec.pages)) {
370
+ for (const p of spec.pages) {
371
+ this.add_page(p);
372
+ }
373
+ } else if (typeof spec.pages === 'object') {
374
+ // { '/': { content: Home }, '/about': { content: About } }
375
+ for (const [path, cfg] of Object.entries(spec.pages)) {
376
+ this.add_page({ path, ...cfg });
377
+ }
378
+ }
379
+ }
380
+
381
+ if (spec.api && typeof spec.api === 'object') {
382
+ Object.assign(this.api, spec.api);
383
+ }
384
+ }
385
+
386
+ add_page(page_or_spec) {
387
+ if (page_or_spec instanceof Webpage) {
388
+ this.pages.push(page_or_spec);
389
+ } else {
390
+ this.pages.push(new Webpage(page_or_spec));
391
+ }
392
+ return this;
393
+ }
394
+
395
+ get_page(path) {
396
+ return this.pages.find(p => p.path === path);
397
+ }
398
+
399
+ get page_count() {
400
+ return this.pages.length;
401
+ }
402
+
403
+ toJSON() {
404
+ return {
405
+ name: this.name,
406
+ page_count: this.page_count,
407
+ pages: this.pages.map(p => p.toJSON ? p.toJSON() : { path: p.path, title: p.title }),
408
+ api_endpoints: Object.keys(this.api),
409
+ meta: this.meta
410
+ };
411
+ }
412
+ }
413
+
414
+ module.exports = Website;
415
+ ```
416
+
417
+ **Advantages**:
418
+ - Simple and understandable — uses standard JS arrays and objects
419
+ - `add_page()` accepts both `Webpage` instances and plain specs (flexible)
420
+ - `pages` object format mirrors `serve-factory.js`'s existing `pages` option exactly
421
+ - `toJSON()` provides admin/debug introspection
422
+ - Minimal code — easy to maintain, review, and extend
423
+ - No coupling to jsgui3 Collection internals
424
+
425
+ **Disadvantages**:
426
+ - Plain array — no built-in duplicate-path checking, no ordering semantics
427
+ - No event system — can't observe pages being added/removed
428
+ - Doesn't use `jsgui.Collection` — inconsistent with rest of ecosystem
429
+ - `api` is a plain object — no per-endpoint middleware or metadata
430
+
431
+ ---
432
+
433
+ ### Proposal B: Collection-Based — Ecosystem Consistent
434
+
435
+ Use `jsgui.Collection` for pages, matching the pattern used throughout `jsgui3-html` and `jsgui3-server`.
436
+
437
+ ```js
438
+ const jsgui = require('jsgui3-html');
439
+ const { Collection } = jsgui;
440
+ const Webpage = require('jsgui3-webpage');
441
+
442
+ class Website {
443
+ constructor(spec = {}) {
444
+ this.name = spec.name || undefined;
445
+ this.pages = new Collection();
446
+ this.api = {};
447
+ this.meta = spec.meta || {};
448
+ this.assets = spec.assets || {};
449
+
450
+ if (spec.pages) {
451
+ if (Array.isArray(spec.pages)) {
452
+ spec.pages.forEach(p => this.add_page(p));
453
+ } else if (typeof spec.pages === 'object') {
454
+ for (const [path, cfg] of Object.entries(spec.pages)) {
455
+ this.add_page({ path, ...cfg });
456
+ }
457
+ }
458
+ }
459
+
460
+ if (spec.api && typeof spec.api === 'object') {
461
+ Object.assign(this.api, spec.api);
462
+ }
463
+ }
464
+
465
+ add_page(page_or_spec) {
466
+ const page = page_or_spec instanceof Webpage
467
+ ? page_or_spec
468
+ : new Webpage(page_or_spec);
469
+ this.pages.push(page);
470
+ return this;
471
+ }
472
+
473
+ get_page(path) {
474
+ // Collection uses _arr internally
475
+ return this.pages._arr.find(p => p.path === path);
476
+ }
477
+
478
+ get page_count() {
479
+ return this.pages.length();
480
+ }
481
+
482
+ toJSON() {
483
+ const page_list = this.pages._arr.map(p =>
484
+ p.toJSON ? p.toJSON() : { path: p.path, title: p.title }
485
+ );
486
+ return {
487
+ name: this.name,
488
+ page_count: this.page_count,
489
+ pages: page_list,
490
+ api_endpoints: Object.keys(this.api),
491
+ meta: this.meta
492
+ };
493
+ }
494
+ }
495
+
496
+ module.exports = Website;
497
+ ```
498
+
499
+ **Advantages**:
500
+ - Consistent with `jsgui3-server/website/website.js` which already uses `Collection` for pages
501
+ - The existing `http-website-publisher.js` iterates `website.pages._arr` — this matches
502
+ - Collection provides `each()`, `length()`, etc. — ecosystem-native iteration
503
+ - Follows the pattern established in `website-group.js` (`extends Collection`)
504
+
505
+ **Disadvantages**:
506
+ - `Collection` has quirks — `length()` is a method not a property, requires `_arr` for array access
507
+ - Heavier dependency chain — Collection pulls in Data_Structures from jsgui3-html
508
+ - Less familiar to new developers — `pages._arr` is an internal detail leaking out
509
+ - Collection's API is not well-documented and could change
510
+
511
+ ---
512
+
513
+ ### Proposal C: Rich Model — API Class + Validation + Helpers
514
+
515
+ A more feature-rich approach with a proper API class, validation, and convenience methods.
516
+
517
+ ```js
518
+ const jsgui = require('jsgui3-html');
519
+ const { Collection } = jsgui;
520
+ const Webpage = require('jsgui3-webpage');
521
+
522
+ class Website_API {
523
+ constructor() {
524
+ this._endpoints = new Map();
525
+ }
526
+
527
+ publish(name, handler, options = {}) {
528
+ if (typeof handler !== 'function') {
529
+ throw new Error(`API endpoint "${name}" handler must be a function`);
530
+ }
531
+ this._endpoints.set(name, { handler, ...options });
532
+ return this;
533
+ }
534
+
535
+ get(name) {
536
+ return this._endpoints.get(name);
537
+ }
538
+
539
+ get endpoints() {
540
+ return [...this._endpoints.entries()].map(([name, cfg]) => ({
541
+ name,
542
+ method: cfg.method || 'GET',
543
+ description: cfg.description
544
+ }));
545
+ }
546
+
547
+ [Symbol.iterator]() {
548
+ return this._endpoints[Symbol.iterator]();
549
+ }
550
+ }
551
+
552
+ class Website {
553
+ constructor(spec = {}) {
554
+ this.name = spec.name || undefined;
555
+ this._pages = new Map(); // path → Webpage
556
+ this.api = new Website_API();
557
+ this.meta = spec.meta || {};
558
+ this.assets = spec.assets || {};
559
+
560
+ if (spec.pages) {
561
+ if (Array.isArray(spec.pages)) {
562
+ spec.pages.forEach(p => this.add_page(p));
563
+ } else if (typeof spec.pages === 'object') {
564
+ for (const [path, cfg] of Object.entries(spec.pages)) {
565
+ this.add_page({ path, ...cfg });
566
+ }
567
+ }
568
+ }
569
+
570
+ if (spec.api && typeof spec.api === 'object') {
571
+ for (const [name, handler] of Object.entries(spec.api)) {
572
+ if (typeof handler === 'function') {
573
+ this.api.publish(name, handler);
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ add_page(page_or_spec) {
580
+ const page = page_or_spec instanceof Webpage
581
+ ? page_or_spec
582
+ : new Webpage(page_or_spec);
583
+ const path = page.path || '/';
584
+ if (this._pages.has(path)) {
585
+ throw new Error(`Duplicate page path: "${path}"`);
586
+ }
587
+ this._pages.set(path, page);
588
+ return this;
589
+ }
590
+
591
+ get_page(path) {
592
+ return this._pages.get(path);
593
+ }
594
+
595
+ get pages() {
596
+ return [...this._pages.values()];
597
+ }
598
+
599
+ get page_count() {
600
+ return this._pages.size;
601
+ }
602
+
603
+ get routes() {
604
+ return [...this._pages.keys()];
605
+ }
606
+
607
+ has_page(path) {
608
+ return this._pages.has(path);
609
+ }
610
+
611
+ remove_page(path) {
612
+ return this._pages.delete(path);
613
+ }
614
+
615
+ toJSON() {
616
+ return {
617
+ name: this.name,
618
+ page_count: this.page_count,
619
+ routes: this.routes,
620
+ pages: this.pages.map(p => p.toJSON ? p.toJSON() : { path: p.path, title: p.title }),
621
+ api: this.api.endpoints,
622
+ meta: this.meta
623
+ };
624
+ }
625
+ }
626
+
627
+ module.exports = Website;
628
+ ```
629
+
630
+ **Advantages**:
631
+ - `Map`-based pages — O(1) lookup by path, duplicate detection, ordering preserved
632
+ - `Website_API` class — structured endpoint registration with optional metadata (`method`, `description`)
633
+ - Validation — throws on duplicate paths, validates handler is a function
634
+ - Rich query interface — `has_page()`, `remove_page()`, `routes` getter
635
+ - `Symbol.iterator` on API — modern JS iteration support
636
+ - Admin-friendly — `toJSON()` provides comprehensive introspection
637
+ - Ready for documentation generation (API endpoint descriptions)
638
+
639
+ **Disadvantages**:
640
+ - Most complex option — more code to maintain and test
641
+ - `Website_API` is a new class that doesn't exist elsewhere in the ecosystem
642
+ - `pages` getter returns a new array each call (minor allocation)
643
+ - Doesn't use `jsgui.Collection` — inconsistent with existing server code
644
+ - Duplicate path validation might be too strict (some apps want to override pages)
645
+ - The `remove_page()` method implies mutability that publishers might not handle well
646
+
647
+ ---
648
+
649
+ ## 5. Cross-Cutting Concerns
650
+
651
+ ### 5.1 The API.js Bug
652
+
653
+ All proposals must fix `MediaSourceHandle.exports = API` → `module.exports = API` in the current `jsgui3-website/API.js`. This is a runtime crash.
654
+
655
+ ### 5.2 Dependency on jsgui3-html
656
+
657
+ Both modules will depend on `jsgui3-html`. This is a significant dependency (~180 versions, many controls). The question is **how much** of it to use:
658
+
659
+ | Usage level | What it pulls in | Benefit |
660
+ |---|---|---|
661
+ | **Light** — just import, type-check controls | `require('jsgui3-html')` available for `instanceof` | Validates content is a proper control |
662
+ | **Medium** — use Collection, tof | Collection for pages, tof for type checking | Ecosystem consistency |
663
+ | **Heavy** — use Evented_Class, Data_Object | Event system, observable properties | Full reactivity |
664
+
665
+ ### 5.3 Server Integration Strategy
666
+
667
+ Regardless of Webpage/Website design, the server integration is the same pattern:
668
+
669
+ ```js
670
+ // In serve-factory.js — detect and unwrap Website/Webpage
671
+ const Website = require('jsgui3-website');
672
+ const Webpage = require('jsgui3-webpage');
673
+
674
+ if (input instanceof Website) {
675
+ // Iterate pages, register each via prepare_webpage_route
676
+ // Register API endpoints via server.publish()
677
+ } else if (input instanceof Webpage) {
678
+ // Single page shorthand
679
+ serve_options.ctrl = input.content;
680
+ serve_options.page_route = input.path;
681
+ }
682
+ ```
683
+
684
+ This is additive — all existing `Server.serve()` call patterns remain unchanged.
685
+
686
+ ### 5.4 Backward Compatibility
687
+
688
+ The current server code does:
689
+ - `new Webpage({ content: Ctrl })` → all proposals support this
690
+ - `website.pages._arr` iteration → Proposal B (Collection) supports this natively; others would need adaptation in the publisher
691
+ - `spec.website instanceof Website` → all proposals support this (same class)
692
+
693
+ ---
694
+
695
+ ## 6. Recommendation Matrix
696
+
697
+ | Criterion | Webpage A<br>(Minimal) | Webpage B<br>(obext) | Webpage C<br>(Evented) | Website A<br>(Array) | Website B<br>(Collection) | Website C<br>(Rich) |
698
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
699
+ | Simplicity | ★★★ | ★★☆ | ★☆☆ | ★★★ | ★★☆ | ★☆☆ |
700
+ | Ecosystem consistency | ★☆☆ | ★★★ | ★★★ | ★☆☆ | ★★★ | ★★☆ |
701
+ | Extensibility | ★★☆ | ★★★ | ★★★ | ★★☆ | ★★☆ | ★★★ |
702
+ | Admin/tooling support | ★☆☆ | ★★☆ | ★★★ | ★★☆ | ★★☆ | ★★★ |
703
+ | Backward compat | ★★★ | ★★★ | ★★☆ | ★★☆ | ★★★ | ★★☆ |
704
+ | Code maintainability | ★★★ | ★★☆ | ★☆☆ | ★★★ | ★★☆ | ★★☆ |
705
+ | Future-proofing | ★☆☆ | ★★☆ | ★★★ | ★☆☆ | ★★☆ | ★★★ |
706
+
707
+ ---
708
+
709
+ ## 7. Possible Combinations
710
+
711
+ The Webpage and Website proposals can be mixed. Here are the most coherent pairings:
712
+
713
+ | Combo | Webpage | Website | Character |
714
+ |---|---|---|---|
715
+ | **Conservative** | A (Minimal) | A (Array) | Get it working with least code. Easy to review and extend later. |
716
+ | **Ecosystem-native** | B (obext) | B (Collection) | Matches existing jsgui3 patterns. Easiest server integration. |
717
+ | **Progressive** | B (obext) | C (Rich) | obext properties for pages, rich Map + API class for websites. Balance of simplicity and power. |
718
+ | **Full reactive** | C (Evented) | C (Rich) | Maximum capability. Good for admin UI, live workflows. Most code to maintain. |
719
+
720
+ ---
721
+
722
+ ## 8. Open Design Questions
723
+
724
+ 1. **Should Webpage know about client-side JS?** Today `src_path_client_js` is a server/publisher concern. Should it move to the page definition, or stay on the server side?
725
+
726
+ 2. **Dynamic pages**: Should there be explicit support for pages whose content is generated per-request (user dashboards, etc.) vs. static pages that can be pre-bundled?
727
+
728
+ 3. **Non-server use cases**: Could these modules be used by a static site generator or a build tool that pre-renders pages to HTML files on disk?
729
+
730
+ 4. **Page ordering**: In a multi-page website, does page order matter? (For menus, navigation, sitemaps.)
731
+
732
+ 5. **Sub-routes and nesting**: Should a Website support nested route groups? (e.g. `/blog/*` routed to a sub-website or different handler pattern)