jsgui3-server 0.0.150 → 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.
- package/.github/instructions/copilot.instructions.md +1 -0
- package/AGENTS.md +2 -0
- package/README.md +89 -13
- package/admin-ui/v1/controls/admin_shell.js +702 -669
- package/admin-ui/v1/server.js +14 -1
- package/docs/api-reference.md +504 -306
- package/docs/books/creating-a-new-admin-ui/README.md +20 -20
- package/docs/books/website-design/01-introduction.md +73 -0
- package/docs/books/website-design/02-current-state.md +195 -0
- package/docs/books/website-design/03-base-class.md +181 -0
- package/docs/books/website-design/04-webpage.md +307 -0
- package/docs/books/website-design/05-website.md +456 -0
- package/docs/books/website-design/06-pages-storage.md +170 -0
- package/docs/books/website-design/07-api-layer.md +285 -0
- package/docs/books/website-design/08-server-integration.md +271 -0
- package/docs/books/website-design/09-cross-agent-review.md +190 -0
- package/docs/books/website-design/10-open-questions.md +196 -0
- package/docs/books/website-design/11-converged-recommendation.md +205 -0
- package/docs/books/website-design/12-content-model.md +395 -0
- package/docs/books/website-design/13-webpage-module-spec.md +404 -0
- package/docs/books/website-design/14-website-module-spec.md +541 -0
- package/docs/books/website-design/15-multi-repo-plan.md +275 -0
- package/docs/books/website-design/16-minimal-first.md +203 -0
- package/docs/books/website-design/17-implementation-report-codex.md +81 -0
- package/docs/books/website-design/README.md +43 -0
- package/docs/comprehensive-documentation.md +220 -220
- package/docs/configuration-reference.md +281 -204
- package/docs/middleware-guide.md +236 -0
- package/docs/proposals/jsgui3-website-and-webpage-design-jsgui3-server-support.md +257 -0
- package/docs/proposals/jsgui3-website-and-webpage-design-review.md +73 -0
- package/docs/proposals/jsgui3-website-and-webpage-design.md +732 -0
- package/docs/swagger.md +316 -0
- package/docs/system-architecture.md +24 -18
- package/examples/controls/1) window/server.js +6 -1
- package/examples/controls/21) mvvm and declarative api/check.js +94 -0
- package/examples/controls/21) mvvm and declarative api/check_output.txt +25 -0
- package/examples/controls/21) mvvm and declarative api/check_output_2.txt +27 -0
- package/examples/controls/21) mvvm and declarative api/client.js +241 -0
- declarative api/e2e-screenshot-1-name-change.png +0 -0
- declarative api/e2e-screenshot-2-toggled.png +0 -0
- declarative api/e2e-screenshot-3-final.png +0 -0
- declarative api/e2e-screenshot-final.png +0 -0
- package/examples/controls/21) mvvm and declarative api/e2e-test.js +175 -0
- package/examples/controls/21) mvvm and declarative api/out.html +1 -0
- package/examples/controls/21) mvvm and declarative api/page_out.html +1 -0
- package/examples/controls/21) mvvm and declarative api/server.js +18 -0
- package/examples/data-views/01) query-endpoint/server.js +61 -0
- package/labs/website-design/001-base-class-overhead/check.js +162 -0
- package/labs/website-design/002-pages-storage/check.js +244 -0
- package/labs/website-design/002-pages-storage/results.txt +0 -0
- package/labs/website-design/003-type-detection/check.js +193 -0
- package/labs/website-design/003-type-detection/results.txt +0 -0
- package/labs/website-design/004-two-stage-validation/check.js +314 -0
- package/labs/website-design/004-two-stage-validation/results.txt +0 -0
- package/labs/website-design/005-normalize-input/check.js +303 -0
- package/labs/website-design/006-serve-website-spike/check.js +290 -0
- package/labs/website-design/README.md +34 -0
- package/labs/website-design/manifest.json +68 -0
- package/labs/website-design/run-all.js +60 -0
- package/middleware/compression.js +217 -0
- package/middleware/index.js +15 -0
- package/middleware/json-body.js +126 -0
- package/module.js +3 -0
- package/openapi.js +474 -0
- package/package.json +11 -8
- package/publishers/Publishers.js +6 -5
- package/publishers/http-function-publisher.js +135 -126
- package/publishers/http-webpage-publisher.js +89 -11
- package/publishers/query-publisher.js +116 -0
- package/publishers/swagger-publisher.js +203 -0
- package/publishers/swagger-ui.js +578 -0
- package/resources/adapters/array-adapter.js +143 -0
- package/resources/query-resource.js +131 -0
- package/serve-factory.js +756 -18
- package/server.js +502 -123
- package/tests/README.md +23 -1
- package/tests/admin-ui-jsgui-controls.test.js +16 -1
- package/tests/helpers/playwright-e2e-harness.js +326 -0
- package/tests/openapi.test.js +319 -0
- package/tests/playwright-smoke.test.js +134 -0
- package/tests/publish-enhancements.test.js +673 -0
- package/tests/query-publisher.test.js +430 -0
- package/tests/quick-json-body-test.js +169 -0
- package/tests/serve.test.js +425 -122
- package/tests/swagger-publisher.test.js +1076 -0
- package/tests/test-runner.js +1 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Chapter 7: The API Layer
|
|
2
|
+
|
|
3
|
+
A Website can describe API endpoints — functions that return data rather than HTML pages. This chapter discusses how those endpoints should be represented in the abstract, before the server gets involved.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What the Server Does Today
|
|
8
|
+
|
|
9
|
+
`jsgui3-server` already supports API endpoints in two ways:
|
|
10
|
+
|
|
11
|
+
### 1. The `api` option in `Server.serve()`
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
Server.serve({
|
|
15
|
+
api: {
|
|
16
|
+
'get-users': () => db.get_users(),
|
|
17
|
+
'create-user': (data) => db.create_user(data)
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Each key becomes a route at `/api/<name>`. The value is a handler function. This is simple and effective for basic APIs.
|
|
23
|
+
|
|
24
|
+
### 2. The `server.publish()` method
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
server.publish('get-users', () => db.get_users());
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This creates an `HTTP_Function_Publisher` and registers it at `/api/<name>`. If the name starts with `/`, it's used as a full route.
|
|
31
|
+
|
|
32
|
+
Both approaches are just `name → function` mappings. There's no metadata — no HTTP method, no description, no authentication spec, no documentation.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## What Should a Website's API Know?
|
|
37
|
+
|
|
38
|
+
### Minimal: name + handler
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
site.api = {
|
|
42
|
+
'get-users': () => db.get_users()
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This mirrors `Server.serve({ api: {...} })` exactly. The server already knows how to consume this format.
|
|
47
|
+
|
|
48
|
+
### With method
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
site.api = {
|
|
52
|
+
'get-users': { handler: () => db.get_users(), method: 'GET' },
|
|
53
|
+
'create-user': { handler: (data) => db.create_user(data), method: 'POST' }
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
HTTP method matters for RESTful APIs. Today's server publishes everything as `GET` by default. Adding method information lets the server set up more correct routing.
|
|
58
|
+
|
|
59
|
+
### With metadata
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
site.api = {
|
|
63
|
+
'get-users': {
|
|
64
|
+
handler: () => db.get_users(),
|
|
65
|
+
method: 'GET',
|
|
66
|
+
description: 'Returns all users',
|
|
67
|
+
auth: 'required',
|
|
68
|
+
tags: ['users', 'read']
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Full metadata enables admin dashboards to show API documentation, access control to be enforced at the server level, and potentially OpenAPI/Swagger spec generation.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Option 1: Plain Object (Status Quo)
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
class Website extends Evented_Class {
|
|
81
|
+
constructor(spec = {}) {
|
|
82
|
+
super();
|
|
83
|
+
this.api = {};
|
|
84
|
+
if (spec.api) Object.assign(this.api, spec.api);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Usage
|
|
89
|
+
site.api['get-users'] = () => db.get_users();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Discussion
|
|
93
|
+
|
|
94
|
+
**Pro**:
|
|
95
|
+
- Perfectly mirrors what `Server.serve()` already accepts
|
|
96
|
+
- Zero learning curve — it's a plain JavaScript object
|
|
97
|
+
- Easy to iterate: `Object.entries(site.api)`
|
|
98
|
+
- Can hold functions directly OR objects with metadata — flexible
|
|
99
|
+
|
|
100
|
+
**Con**:
|
|
101
|
+
- No validation — you can put anything in
|
|
102
|
+
- No way to distinguish "handler function" from "endpoint config object" without inspecting each value
|
|
103
|
+
- No `.add()` or `.get()` convenience methods
|
|
104
|
+
- No introspection API — admin UI would need to understand the mixed format
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Option 2: Dedicated API Object with `add_endpoint()`
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
class Website extends Evented_Class {
|
|
112
|
+
constructor(spec = {}) {
|
|
113
|
+
super();
|
|
114
|
+
this._api = new Map();
|
|
115
|
+
|
|
116
|
+
if (spec.api) {
|
|
117
|
+
for (const [name, value] of Object.entries(spec.api)) {
|
|
118
|
+
if (typeof value === 'function') {
|
|
119
|
+
this.add_endpoint(name, value);
|
|
120
|
+
} else {
|
|
121
|
+
this.add_endpoint(name, value.handler, value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
add_endpoint(name, handler, options = {}) {
|
|
128
|
+
this._api.set(name, {
|
|
129
|
+
handler,
|
|
130
|
+
method: options.method || 'GET',
|
|
131
|
+
description: options.description || undefined,
|
|
132
|
+
auth: options.auth || undefined
|
|
133
|
+
});
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get_endpoint(name) {
|
|
138
|
+
return this._api.get(name);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get api_endpoints() {
|
|
142
|
+
return [...this._api.entries()].map(([name, cfg]) => ({
|
|
143
|
+
name, ...cfg
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Discussion
|
|
150
|
+
|
|
151
|
+
**Pro**:
|
|
152
|
+
- Consistent, structured storage — every endpoint has the same shape
|
|
153
|
+
- Auto-normalizes bare functions: `add_endpoint('x', fn)` becomes `{ handler: fn, method: 'GET' }`
|
|
154
|
+
- Accepts the plain object format from spec for backward compatibility
|
|
155
|
+
- `api_endpoints` getter provides a stable introspection surface for admin/tooling
|
|
156
|
+
- Easy to add validation (handler must be function, method must be valid HTTP verb)
|
|
157
|
+
|
|
158
|
+
**Con**:
|
|
159
|
+
- More code than Option 1
|
|
160
|
+
- The constructor does normalization work — functions vs. config objects need to be detected
|
|
161
|
+
- `api_endpoints` returns a new array each time (same minor issue as `pages`)
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Option 3: Separate API Class
|
|
166
|
+
|
|
167
|
+
Move API concerns into a dedicated class, keeping Website focused.
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
class Website_API extends Evented_Class {
|
|
171
|
+
constructor() {
|
|
172
|
+
super();
|
|
173
|
+
this._endpoints = new Map();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
publish(name, handler, options = {}) {
|
|
177
|
+
if (typeof handler !== 'function') {
|
|
178
|
+
throw new TypeError(`API handler for "${name}" must be a function`);
|
|
179
|
+
}
|
|
180
|
+
this._endpoints.set(name, {
|
|
181
|
+
handler,
|
|
182
|
+
method: options.method || 'GET',
|
|
183
|
+
path: options.path || undefined,
|
|
184
|
+
description: options.description || undefined,
|
|
185
|
+
auth: options.auth || undefined
|
|
186
|
+
});
|
|
187
|
+
this.raise('endpoint-added', { name, ...options });
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get(name) { return this._endpoints.get(name); }
|
|
192
|
+
has(name) { return this._endpoints.has(name); }
|
|
193
|
+
remove(name) { return this._endpoints.delete(name); }
|
|
194
|
+
|
|
195
|
+
get endpoints() {
|
|
196
|
+
return [...this._endpoints.entries()].map(([name, cfg]) => ({
|
|
197
|
+
name, ...cfg
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get count() {
|
|
202
|
+
return this._endpoints.size;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
toJSON() {
|
|
206
|
+
return this.endpoints.map(({ name, method, path, description }) => ({
|
|
207
|
+
name, method, path, description
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
[Symbol.iterator]() {
|
|
212
|
+
return this._endpoints[Symbol.iterator]();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Discussion
|
|
218
|
+
|
|
219
|
+
**Pro**:
|
|
220
|
+
- **Single Responsibility** — API concerns are encapsulated, not mixed into Website
|
|
221
|
+
- **Own event system** — `'endpoint-added'` events without polluting Website's event namespace
|
|
222
|
+
- **`.publish()` method** — matches the existing `API.js` stub's method name, so it's a natural evolution
|
|
223
|
+
- **`toJSON()`** — stable serialization for admin/docs
|
|
224
|
+
- **Iterable** — `for (const [name, endpoint] of site.api)` works
|
|
225
|
+
- **Validation** — handler type-checking at registration time
|
|
226
|
+
- The existing `jsgui3-website/API.js` already establishes this class's existence — it just needs to be fleshed out
|
|
227
|
+
|
|
228
|
+
**Con**:
|
|
229
|
+
- Another class to maintain and understand
|
|
230
|
+
- More indirection: `site.api.publish(name, fn)` vs. `site.api[name] = fn`
|
|
231
|
+
- Extends `Evented_Class` — do API endpoint registrations need events?
|
|
232
|
+
- The current server's `server.publish()` method has the same name, which might be confusing. "Is this the website's publish or the server's publish?"
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## The `publish()` Name Question
|
|
237
|
+
|
|
238
|
+
Both the existing `API.js` stub and `server.js` use a method called `publish()`. This creates potential confusion:
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
// Website API — declares an endpoint exists
|
|
242
|
+
site.api.publish('get-users', handler);
|
|
243
|
+
|
|
244
|
+
// Server — actually makes it available over HTTP
|
|
245
|
+
server.publish('get-users', handler);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Should the Website API use a different name to distinguish declaration from activation?
|
|
249
|
+
|
|
250
|
+
| Name | Meaning | Example |
|
|
251
|
+
|------|---------|---------|
|
|
252
|
+
| `publish()` | Matches existing stub, familiar | `site.api.publish('x', fn)` |
|
|
253
|
+
| `add()` | Explicit about what it does | `site.api.add('x', fn)` |
|
|
254
|
+
| `define()` | Emphasizes declaration over action | `site.api.define('x', fn)` |
|
|
255
|
+
| `register()` | Common in routing frameworks | `site.api.register('x', fn)` |
|
|
256
|
+
| `endpoint()` | Noun-verb clarity | `site.api.endpoint('x', fn)` |
|
|
257
|
+
|
|
258
|
+
`define()` or `register()` make the clearest distinction: the Website *defines* endpoints, the Server *publishes* them.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Comparison
|
|
263
|
+
|
|
264
|
+
| Criterion | Plain Object | On Website Map | Separate API Class |
|
|
265
|
+
|---|:---:|:---:|:---:|
|
|
266
|
+
| Simplicity | ★★★ | ★★☆ | ★☆☆ |
|
|
267
|
+
| Server compat | ★★★ | ★★★ | ★★☆ |
|
|
268
|
+
| Validation | ☆ | ★★ | ★★★ |
|
|
269
|
+
| Introspection | ★☆☆ | ★★☆ | ★★★ |
|
|
270
|
+
| Metadata support | ★☆☆ | ★★★ | ★★★ |
|
|
271
|
+
| Event support | ☆ | ★★ | ★★★ |
|
|
272
|
+
| Continuation of API.js | ☆ | ☆ | ★★★ |
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## What About the Existing API.js?
|
|
277
|
+
|
|
278
|
+
The current `jsgui3-website/API.js` has:
|
|
279
|
+
- A constructor that takes `spec.server`
|
|
280
|
+
- An empty `publish(name, fn)` stub
|
|
281
|
+
- A fatal export-statement bug
|
|
282
|
+
|
|
283
|
+
If we go with Option 3 (separate API class), the natural path is to fix and evolve `API.js` into `Website_API`. The server reference would be removed (the API class shouldn't know about the server — that's a deployment concern, not a definition concern), and `publish()` might be renamed to `define()` or `register()`.
|
|
284
|
+
|
|
285
|
+
If we go with Option 1 or 2, `API.js` becomes dead code and should be removed.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Chapter 8: Server Integration
|
|
2
|
+
|
|
3
|
+
This chapter discusses how `jsgui3-server` would consume Website and Webpage objects — without requiring them.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The Constraint
|
|
8
|
+
|
|
9
|
+
`jsgui3-server` must continue to work in all its current modes:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// Mode 1: Single control
|
|
13
|
+
Server.serve(MyCtrl);
|
|
14
|
+
|
|
15
|
+
// Mode 2: Multi-page options
|
|
16
|
+
Server.serve({ pages: { '/': { content: Home } } });
|
|
17
|
+
|
|
18
|
+
// Mode 3: API-only
|
|
19
|
+
Server.serve({ api: { 'get-data': handler } });
|
|
20
|
+
|
|
21
|
+
// Mode 4: Full options
|
|
22
|
+
Server.serve({ Ctrl: MyCtrl, api: {...}, middleware: [...] });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Website and Webpage support is **additive** — new input shapes that the server learns to accept alongside the existing ones.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Detection Strategy
|
|
30
|
+
|
|
31
|
+
### Option A: `instanceof` checks
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
const Website = require('jsgui3-website');
|
|
35
|
+
const Webpage = require('jsgui3-webpage');
|
|
36
|
+
|
|
37
|
+
if (input instanceof Website) { ... }
|
|
38
|
+
if (input instanceof Webpage) { ... }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Pro**: Clean, readable, unambiguous.
|
|
42
|
+
**Con**: Breaks when packages are duplicated (npm link, workspaces, multiple installs). Two copies of `jsgui3-website` will have different class references, so `instanceof` fails even when the object is shape-compatible.
|
|
43
|
+
|
|
44
|
+
### Option B: Duck typing / capability checks
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
function is_website(input) {
|
|
48
|
+
return input
|
|
49
|
+
&& typeof input.add_page === 'function'
|
|
50
|
+
&& typeof input.get_page === 'function'
|
|
51
|
+
&& (input._pages instanceof Map || Array.isArray(input.pages));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function is_webpage(input) {
|
|
55
|
+
return input
|
|
56
|
+
&& input.hasOwnProperty('path')
|
|
57
|
+
&& input.hasOwnProperty('content');
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Pro**: Works across duplicate installs, works with subclasses, works with compatible plain objects.
|
|
62
|
+
**Con**: Could false-positive on objects that happen to have the right properties. More code.
|
|
63
|
+
|
|
64
|
+
### Option C: Both — `instanceof` first, duck type as fallback
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
function is_website(input) {
|
|
68
|
+
try {
|
|
69
|
+
const Website = require('jsgui3-website');
|
|
70
|
+
if (input instanceof Website) return true;
|
|
71
|
+
} catch (e) { /* jsgui3-website not installed */ }
|
|
72
|
+
|
|
73
|
+
// Fallback: duck typing
|
|
74
|
+
return input && typeof input.add_page === 'function'
|
|
75
|
+
&& typeof input.get_page === 'function';
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Pro**: Best of both. `instanceof` when available (fastest, most reliable), duck typing as a safety net.
|
|
80
|
+
**Con**: Most code. The `try/catch` around `require` is somewhat unusual.
|
|
81
|
+
|
|
82
|
+
### Option D: Explicit type marker
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
// In jsgui3-website:
|
|
86
|
+
class Website {
|
|
87
|
+
get [Symbol.for('jsgui.type')]() { return 'Website'; }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// In jsgui3-server:
|
|
91
|
+
function is_website(input) {
|
|
92
|
+
return input && input[Symbol.for('jsgui.type')] === 'Website';
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Pro**: Works across duplicate installs. No false positives. Clean.
|
|
97
|
+
**Con**: Requires coordination between packages. Symbol-based type markers aren't a common JavaScript pattern.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Integration Points
|
|
102
|
+
|
|
103
|
+
### Where `serve-factory.js` would change
|
|
104
|
+
|
|
105
|
+
Today, `Server.serve()` inspects its input to determine the mode:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
if (typeof input === 'function') → single control
|
|
109
|
+
if (input.Ctrl) → control from options
|
|
110
|
+
if (input.pages) → multi-page
|
|
111
|
+
if (input.api) → API mode
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Adding Website/Webpage support means adding branches:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
if (is_website(input)) {
|
|
118
|
+
// Extract pages, register each route
|
|
119
|
+
// Extract API endpoints, publish each
|
|
120
|
+
// Apply site-wide metadata
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (is_webpage(input)) {
|
|
124
|
+
// Single-page shorthand — extract path and content
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### The Normalization Approach
|
|
129
|
+
|
|
130
|
+
Rather than adding more `if` branches, an alternative design normalizes all inputs into a common internal format first:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
function normalize_serve_input(input) {
|
|
134
|
+
const manifest = {
|
|
135
|
+
pages: [],
|
|
136
|
+
api: [],
|
|
137
|
+
meta: {},
|
|
138
|
+
assets: {}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (is_website(input)) {
|
|
142
|
+
manifest.pages = input.pages.map(normalize_page);
|
|
143
|
+
manifest.api = input.api_endpoints || [];
|
|
144
|
+
manifest.meta = input.meta || {};
|
|
145
|
+
} else if (is_webpage(input)) {
|
|
146
|
+
manifest.pages = [normalize_page(input)];
|
|
147
|
+
} else if (typeof input === 'function') {
|
|
148
|
+
manifest.pages = [{ path: '/', content: input }];
|
|
149
|
+
} else if (input.pages) {
|
|
150
|
+
// Existing multi-page format
|
|
151
|
+
for (const [path, cfg] of Object.entries(input.pages)) {
|
|
152
|
+
manifest.pages.push({ path, ...cfg });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ... etc
|
|
156
|
+
|
|
157
|
+
return manifest;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
All publishers then consume the normalized manifest, not the raw input. This has two benefits:
|
|
162
|
+
|
|
163
|
+
1. **Single place for input handling** — all format detection and conversion lives in one function
|
|
164
|
+
2. **Publishers are simpler** — they don't need to handle multiple input shapes
|
|
165
|
+
|
|
166
|
+
The OpenAI reviewer's server support document proposed this approach in detail (see Chapter 9).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## What Changes in the Server?
|
|
171
|
+
|
|
172
|
+
### Minimal approach
|
|
173
|
+
|
|
174
|
+
Add Website/Webpage detection to `serve-factory.js`. When detected, iterate pages and call the existing `prepare_webpage_route()` for each. Register API endpoints using the existing `server.publish()`.
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
if (is_website(input)) {
|
|
178
|
+
const website = input;
|
|
179
|
+
const page_routes = [];
|
|
180
|
+
|
|
181
|
+
for (const page of website.pages) {
|
|
182
|
+
page_routes.push(
|
|
183
|
+
prepare_webpage_route(server, page.path, {
|
|
184
|
+
content: page.content,
|
|
185
|
+
title: page.title,
|
|
186
|
+
name: page.name,
|
|
187
|
+
client_js: page.client_js
|
|
188
|
+
}, defaults)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (website.api_endpoints) {
|
|
193
|
+
for (const endpoint of website.api_endpoints) {
|
|
194
|
+
server.publish(endpoint.name, endpoint.handler);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await Promise.all(page_routes);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Pro**: Minimal change to the server. Reuses existing pipeline.
|
|
203
|
+
**Con**: Doesn't surface website-level metadata. No normalization layer — the special-casing stays.
|
|
204
|
+
|
|
205
|
+
### Full approach
|
|
206
|
+
|
|
207
|
+
Introduce the normalization layer, refactor publishers to consume manifests, add introspection APIs.
|
|
208
|
+
|
|
209
|
+
**Pro**: Cleaner architecture, better admin support.
|
|
210
|
+
**Con**: Significant refactoring of working code such as the publisher pipeline.
|
|
211
|
+
|
|
212
|
+
### Pragmatic recommendation
|
|
213
|
+
|
|
214
|
+
Start minimal. Get Website/Webpage objects flowing through the existing pipeline. Add normalization and publisher refactoring as a follow-up when the basic integration proves out.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Recommended Contract (Converged)
|
|
219
|
+
|
|
220
|
+
From the tradeoffs in this chapter and the cross-agent review, a practical contract emerges:
|
|
221
|
+
|
|
222
|
+
1. **Normalize first** — convert all `Server.serve(...)` input variants into one internal manifest
|
|
223
|
+
2. **Publish second** — publishers consume only normalized manifests, never raw user input
|
|
224
|
+
3. **Detect by capability** — avoid hard `instanceof` dependence at server boundaries
|
|
225
|
+
4. **Keep compatibility** — legacy inputs (`Ctrl`, `pages`, `api`) must map cleanly into the manifest
|
|
226
|
+
5. **Expose introspection** — surface manifest/publication summaries for admin tooling
|
|
227
|
+
|
|
228
|
+
This keeps the integration additive and reduces future complexity in publisher code.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Admin UI Opportunities
|
|
233
|
+
|
|
234
|
+
The admin UI already introspects the resource pool, router, and publishers. With Website/Webpage objects, it could show:
|
|
235
|
+
|
|
236
|
+
1. **Website manifest** — name, base path, page count
|
|
237
|
+
2. **Page catalog** — path, title, content control name, render mode
|
|
238
|
+
3. **API endpoint catalog** — name, method, description
|
|
239
|
+
4. **Route table** — which routes are static vs. dynamic, which have bundles
|
|
240
|
+
|
|
241
|
+
This requires the server to expose `server.website_manifest` or equivalent. The `toJSON()` methods on Website and Webpage make this straightforward.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Integration with the Publisher Pipeline
|
|
246
|
+
|
|
247
|
+
Today's pipeline:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
Ctrl → Webpage → HTTP_Webpage_Publisher → bundled output → router
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
With Website support:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
Website → for each page:
|
|
257
|
+
Webpage → HTTP_Webpage_Publisher → bundled output → router
|
|
258
|
+
for each API endpoint:
|
|
259
|
+
→ server.publish() → HTTP_Function_Publisher → router
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The Website itself doesn't need a publisher — it's a container that the server iterates. Each page gets its own publisher, and each API endpoint gets its own publisher. The Website provides the structure; the server provides the pipeline.
|
|
263
|
+
|
|
264
|
+
This means `HTTP_Website_Publisher` might become a thin coordinator rather than a monolithic publisher. It would:
|
|
265
|
+
|
|
266
|
+
1. Accept a Website
|
|
267
|
+
2. Create per-page publishers
|
|
268
|
+
3. Wait for all publishers to be ready
|
|
269
|
+
4. Emit a combined `'ready'` event
|
|
270
|
+
|
|
271
|
+
This is much simpler than the current 580-line stub with NYI comments.
|