generator-white-label 8.0.0 → 9.0.0
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/CLI.md +5 -5
- package/README.md +36 -8
- package/app/README.md +26 -0
- package/app/assets/script/tasks/TaskApplication.ts +11 -11
- package/app/assets/script/tasks/TaskMediator.ts +6 -11
- package/app/assets/script/tasks/TaskRouter.ts +1 -1
- package/app/assets/view/sections/PackageDocsSection.tsx +4 -4
- package/cli/index.ts +2 -1
- package/dist/app/assets/script/tasks/TaskApplication.js +11 -11
- package/dist/app/assets/script/tasks/TaskApplication.js.map +1 -1
- package/dist/app/assets/script/tasks/TaskMediator.d.ts +5 -10
- package/dist/app/assets/script/tasks/TaskMediator.js.map +1 -1
- package/dist/app/assets/script/tasks/TaskRouter.js +1 -1
- package/dist/app/assets/script/tasks/TaskRouter.js.map +1 -1
- package/dist/app/assets/view/sections/PackageDocsSection.js +4 -4
- package/dist/app/assets/view/sections/PackageDocsSection.js.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/scaffold/index.d.ts +5 -5
- package/dist/scaffold/index.js +26 -10
- package/dist/scaffold/index.js.map +1 -1
- package/dist/server/handler.d.ts +7 -0
- package/dist/server/handler.js +67 -0
- package/dist/server/handler.js.map +1 -0
- package/package.json +6 -5
- package/scaffold/AGENTS.md +18 -0
- package/scaffold/index.ts +22 -10
- package/scaffold/no-jsx/README.md +26 -0
- package/template-test/site.test.js +3 -1
package/CLI.md
CHANGED
|
@@ -7,7 +7,7 @@ CLI ──────> createProject() ──────> project
|
|
|
7
7
|
Node API ─┘
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
`createProject()` owns the scaffold. The CLI translates command-line input into that API.
|
|
10
|
+
`createProject()` owns the scaffold, including destination safety. The CLI translates command-line input into that API.
|
|
11
11
|
|
|
12
12
|
## Create a project
|
|
13
13
|
|
|
@@ -25,7 +25,7 @@ yarn dlx generator-white-label create my-project
|
|
|
25
25
|
pnpm dlx generator-white-label create my-project
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Project creation accepts a missing or existing **empty** directory. A non-empty destination is rejected before scaffold files are copied or written.
|
|
29
29
|
|
|
30
30
|
Then install and test the generated project with npm, Yarn, or pnpm:
|
|
31
31
|
|
|
@@ -52,7 +52,7 @@ npx generator-white-label create my-project --no-jsx
|
|
|
52
52
|
|
|
53
53
|
The same `--jsx` and `--no-jsx` flags work through `yarn dlx` and `pnpm dlx`.
|
|
54
54
|
|
|
55
|
-
If no choice can be asked interactively and neither flag is supplied, JSX
|
|
55
|
+
If no choice can be asked interactively and neither flag is supplied, JSX is the default.
|
|
56
56
|
|
|
57
57
|
Run `white-label --help` for usage. See [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md) for package-manager compatibility details.
|
|
58
58
|
|
|
@@ -69,13 +69,13 @@ await createProject({
|
|
|
69
69
|
|
|
70
70
|
Set `jsx: true` for JSX/TSX templates or `jsx: false` for plain TypeScript and HTML strings. Use `jsx: false` as the starting point when another template engine should own rendering. Omitting `jsx` defaults to `true`.
|
|
71
71
|
|
|
72
|
-
`createProject()`
|
|
72
|
+
`createProject()` uses the same destination-safety rule as the CLI: missing and empty directories are allowed; non-empty directories are rejected before project content is written.
|
|
73
73
|
|
|
74
74
|
## Read the implementation
|
|
75
75
|
|
|
76
76
|
The source is intentionally small enough to teach the design:
|
|
77
77
|
|
|
78
|
-
- `scaffold/index.ts` — project creation
|
|
78
|
+
- `scaffold/index.ts` — project creation and destination-safety boundary
|
|
79
79
|
- `cli/index.ts` — command-line adapter
|
|
80
80
|
- `test/` — executable contracts
|
|
81
81
|
|
package/README.md
CHANGED
|
@@ -60,6 +60,32 @@ DOM
|
|
|
60
60
|
|
|
61
61
|
Both generated variants provide the same application behavior and progressive enhancement. The only difference is template syntax.
|
|
62
62
|
|
|
63
|
+
## A simple View click event
|
|
64
|
+
|
|
65
|
+
Use View lifecycle hooks to add and remove browser listeners with the same callback reference:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import View from 'white-label-view';
|
|
69
|
+
|
|
70
|
+
class ButtonView extends View {
|
|
71
|
+
handleClick = () => {
|
|
72
|
+
console.log('Clicked');
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
addListeners() {
|
|
76
|
+
this.element.addEventListener('click', this.handleClick);
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
removeListeners() {
|
|
81
|
+
this.element.removeEventListener('click', this.handleClick);
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`addListeners()` runs when the View mounts. `removeListeners()` runs before replacement or destruction, so the listener lifecycle stays owned by the View.
|
|
88
|
+
|
|
63
89
|
## Read the source
|
|
64
90
|
|
|
65
91
|
For the default JSX scaffold, a useful reading order is:
|
|
@@ -140,7 +166,9 @@ npx generator-white-label create my-project --no-jsx
|
|
|
140
166
|
|
|
141
167
|
The same `--jsx` and `--no-jsx` options work through `yarn dlx` and `pnpm dlx`.
|
|
142
168
|
|
|
143
|
-
If no interactive answer is available and neither flag is supplied, JSX is the default
|
|
169
|
+
If no interactive answer is available and neither flag is supplied, JSX is the default.
|
|
170
|
+
|
|
171
|
+
The generator refuses to layer a scaffold over an existing non-empty destination. This protection is enforced by the shared `createProject()` engine, so it applies to both the CLI and programmatic use. An existing empty directory is allowed; a missing directory is created as part of generation.
|
|
144
172
|
|
|
145
173
|
See [`CLI.md`](CLI.md) for the CLI contract and [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md) for package-manager compatibility details.
|
|
146
174
|
|
|
@@ -160,9 +188,9 @@ await createProject({
|
|
|
160
188
|
|
|
161
189
|
Set `jsx: true` for JSX/TSX templates or `jsx: false` for plain TypeScript and HTML strings. Use `jsx: false` as the starting point for a third-party template engine. Omitting `jsx` defaults to `true`.
|
|
162
190
|
|
|
163
|
-
`createProject(options)` returns `Promise<void>`. A successful call resolves with `undefined`; file-system failures reject the promise instead of returning a status value.
|
|
191
|
+
`createProject(options)` returns `Promise<void>`. A successful call resolves with `undefined`; file-system failures reject the promise instead of returning a status value. If the destination already exists and contains files, it rejects before copying or writing project content.
|
|
164
192
|
|
|
165
|
-
`createProject()` is the canonical creation API. The CLI and future integrations are adapters around it rather than separate generation systems.
|
|
193
|
+
`createProject()` is the canonical creation API. The CLI and future integrations are adapters around it rather than separate generation systems. Keeping overwrite policy here prevents adapters from accidentally bypassing the same safety boundary.
|
|
166
194
|
|
|
167
195
|
This is the same design principle used throughout White Label: one responsibility, one implementation, explicit adapters at environment boundaries.
|
|
168
196
|
|
|
@@ -170,7 +198,7 @@ This is the same design principle used throughout White Label: one responsibilit
|
|
|
170
198
|
|
|
171
199
|
| Path | Purpose |
|
|
172
200
|
| --- | --- |
|
|
173
|
-
| `scaffold/index.ts` | Canonical `createProject()` implementation |
|
|
201
|
+
| `scaffold/index.ts` | Canonical `createProject()` implementation and destination-safety boundary |
|
|
174
202
|
| `scaffold/no-jsx/` | Plain-TypeScript template equivalents |
|
|
175
203
|
| `cli/index.ts` | First-party command-line adapter |
|
|
176
204
|
| `app/*.tsx` | Default JSX top-level static pages |
|
|
@@ -208,7 +236,7 @@ With `--no-jsx`, the equivalent page is ordinary TypeScript returning an HTML st
|
|
|
208
236
|
|
|
209
237
|
For larger pages, compose focused views instead of growing one renderer indefinitely.
|
|
210
238
|
|
|
211
|
-
JSX
|
|
239
|
+
White Label JSX HTML-escapes ordinary child text and ordinary attribute values by default, rejects intrinsic `on*` event-handler attributes, and uses runtime-owned identity for trusted JSX/raw values. That escaping is not a general-purpose sanitizer for URL or CSS semantics. Plain-TypeScript templates and third-party engines likewise remain responsible for their own contextual escaping, sanitization, raw-output features, and configuration. See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for the tested matrix and trust boundaries.
|
|
212
240
|
|
|
213
241
|
## Progressive enhancement
|
|
214
242
|
|
|
@@ -231,7 +259,7 @@ Cloud-specific adapters should stay at the boundary. Translate an AWS/Vercel/Net
|
|
|
231
259
|
|
|
232
260
|
Generated-project tests exercise sequential warm invocations, concurrent requests, request-data escaping, execution without browser globals, and a browser/Web-target bundle smoke test. They also enforce a 100 kB minified serverless-composition bundle budget and a 750 ms fresh-process handler-import budget. These are regression guards, not universal latency guarantees.
|
|
233
261
|
|
|
234
|
-
The Web-target bundle smoke test catches unresolved Node built-ins, but it does **not** claim blanket Cloudflare/Deno/edge-provider compatibility. The published runtime packages
|
|
262
|
+
The Web-target bundle smoke test catches unresolved Node built-ins, but it does **not** claim blanket Cloudflare/Deno/edge-provider compatibility. The published runtime packages document Node as their supported server runtime; verify a specific edge provider before deployment.
|
|
235
263
|
|
|
236
264
|
## Build and verify
|
|
237
265
|
|
|
@@ -263,7 +291,7 @@ npm run audit
|
|
|
263
291
|
npm pack --dry-run
|
|
264
292
|
```
|
|
265
293
|
|
|
266
|
-
Tests are part of the documentation. They demonstrate intended contracts while protecting behavior. Executable project source is held to 100% statement, branch, function, and line coverage.
|
|
294
|
+
Tests are part of the documentation. They demonstrate intended contracts while protecting behavior. Executable project source is held to 100% statement, branch, function, and line coverage. CI also checks the documented Node 22.18 minimum, the primary Node 24 line, packed CLI/programmatic API installation, and generated-project compatibility across npm, Yarn, and pnpm.
|
|
267
295
|
|
|
268
296
|
## White Label ecosystem
|
|
269
297
|
|
|
@@ -272,4 +300,4 @@ Tests are part of the documentation. They demonstrate intended contracts while p
|
|
|
272
300
|
- [`white-label-mediator`](https://github.com/bshack/white-label-mediator) — application events.
|
|
273
301
|
- [`white-label-router`](https://github.com/bshack/white-label-router) — routing and URL state.
|
|
274
302
|
|
|
275
|
-
The generated project imports the real packages rather than reproducing their behavior locally. That makes it both an example and an ecosystem integration test.
|
|
303
|
+
The generated project imports the real packages rather than reproducing their behavior locally. That makes it both an example and an ecosystem integration test.
|
package/app/README.md
CHANGED
|
@@ -48,6 +48,32 @@ The build output is `_deploy`. The test suite checks the starter content, progre
|
|
|
48
48
|
|
|
49
49
|
Pages live in `app/*.tsx`, page data lives in `app/assets/data/view`, browser code lives in `app/assets/script`, the provider-neutral serverless example lives in `server/handler.ts`, and shared styles live in `app/assets/style`. TypeScript is configured with `jsx: react-jsx` and `jsxImportSource: white-label-view`, so this scaffold's JSX does not require React.
|
|
50
50
|
|
|
51
|
+
## Simple View click event
|
|
52
|
+
|
|
53
|
+
View lifecycle hooks are the simplest place to own a browser listener:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import View from 'white-label-view';
|
|
57
|
+
|
|
58
|
+
class ButtonView extends View {
|
|
59
|
+
handleClick = () => {
|
|
60
|
+
console.log('Clicked');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
addListeners() {
|
|
64
|
+
this.element.addEventListener('click', this.handleClick);
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
removeListeners() {
|
|
69
|
+
this.element.removeEventListener('click', this.handleClick);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The same callback reference is used for both registration and cleanup. View calls `removeListeners()` before replacement or destruction.
|
|
76
|
+
|
|
51
77
|
## Serverless / function runtimes
|
|
52
78
|
|
|
53
79
|
`server/handler.ts` demonstrates a cloud-agnostic server function using the Web `Request` and `Response` APIs. It composes a request-scoped Mediator, Model, Router, and server View, then destroys those mutable instances before the invocation completes.
|
|
@@ -32,25 +32,25 @@ export function initializeTaskApplication(documentRoot: Document): TaskApplicati
|
|
|
32
32
|
const status = parentElement.querySelector<HTMLElement>('[data-task-status]')!;
|
|
33
33
|
const updateStatus = (): void => {status.textContent = describeTaskStatus(model.get());};
|
|
34
34
|
|
|
35
|
-
const addTask = (
|
|
36
|
-
const toggleTask = (
|
|
37
|
-
const setFilter = (
|
|
38
|
-
mediator.
|
|
39
|
-
mediator.
|
|
40
|
-
mediator.
|
|
35
|
+
const addTask = (event: CustomEvent<string>): void => {model.add(event.detail); updateStatus();};
|
|
36
|
+
const toggleTask = (event: CustomEvent<number>): void => {model.toggle(event.detail); updateStatus();};
|
|
37
|
+
const setFilter = (event: CustomEvent<'all' | 'active' | 'completed'>): void => {model.setFilter(event.detail); updateStatus();};
|
|
38
|
+
mediator.addEventListener('task:add', addTask);
|
|
39
|
+
mediator.addEventListener('task:toggle', toggleTask);
|
|
40
|
+
mediator.addEventListener('task:filter', setFilter);
|
|
41
41
|
|
|
42
42
|
const delegated = view.delegate(parentElement);
|
|
43
43
|
delegated.on('submit', '[data-task-form]', (event: Event) => {
|
|
44
44
|
event.preventDefault();
|
|
45
45
|
const form = event.target as HTMLFormElement;
|
|
46
46
|
const input = form.querySelector<HTMLInputElement>('[name="task"]')!;
|
|
47
|
-
mediator.
|
|
47
|
+
mediator.dispatchEvent(new CustomEvent('task:add', {detail: input.value}));
|
|
48
48
|
parentElement.querySelector<HTMLInputElement>('[name="task"]')!.focus();
|
|
49
49
|
});
|
|
50
50
|
delegated.on('change', '[data-task-toggle]', (event: Event) => {
|
|
51
51
|
const input = event.target as HTMLInputElement;
|
|
52
52
|
const id = Number(input.dataset.taskId);
|
|
53
|
-
mediator.
|
|
53
|
+
mediator.dispatchEvent(new CustomEvent('task:toggle', {detail: id}));
|
|
54
54
|
const focusTarget = parentElement.querySelector<HTMLInputElement>(`[data-task-id="${id}"]`)
|
|
55
55
|
?? parentElement.querySelector<HTMLAnchorElement>('[data-task-filter][aria-current="page"]')!;
|
|
56
56
|
focusTarget.focus();
|
|
@@ -66,9 +66,9 @@ export function initializeTaskApplication(documentRoot: Document): TaskApplicati
|
|
|
66
66
|
destroy() {
|
|
67
67
|
delegated.clear();
|
|
68
68
|
router.destroy();
|
|
69
|
-
mediator.
|
|
70
|
-
mediator.
|
|
71
|
-
mediator.
|
|
69
|
+
mediator.removeEventListener('task:add', addTask);
|
|
70
|
+
mediator.removeEventListener('task:toggle', toggleTask);
|
|
71
|
+
mediator.removeEventListener('task:filter', setFilter);
|
|
72
72
|
view.destroy();
|
|
73
73
|
model.destroy();
|
|
74
74
|
mediator.destroy();
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import Mediator from 'white-label-mediator';
|
|
2
2
|
import type {TaskFilter} from '../../view/examples/tasks/task-state.js';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Document the event protocol in one place even though the currently installed
|
|
6
|
-
* Mediator exposes the EventEmitter-compatible runtime without a generic map.
|
|
7
|
-
* Keeping names and payloads explicit still gives learners one clear contract
|
|
8
|
-
* to follow when tracing the example.
|
|
9
|
-
*/
|
|
4
|
+
/** Application event vocabulary shared by the generated task modules. */
|
|
10
5
|
export type TaskEvents = {
|
|
11
|
-
'task:add':
|
|
12
|
-
'task:filter':
|
|
13
|
-
'task:toggle':
|
|
6
|
+
'task:add': string;
|
|
7
|
+
'task:filter': TaskFilter;
|
|
8
|
+
'task:toggle': number;
|
|
14
9
|
};
|
|
15
10
|
|
|
16
|
-
export type TaskMediator = Mediator
|
|
11
|
+
export type TaskMediator = Mediator<TaskEvents>;
|
|
17
12
|
|
|
18
13
|
export function createTaskMediator(): TaskMediator {
|
|
19
|
-
return new Mediator().initialize();
|
|
14
|
+
return new Mediator<TaskEvents>().initialize();
|
|
20
15
|
}
|
|
@@ -22,7 +22,7 @@ export function createTaskRouter(documentRoot: Document, mediator: TaskMediator)
|
|
|
22
22
|
const route = (_scope: Element | null, location: {data: {query: Record<string, string>}}): void => {
|
|
23
23
|
const filter = normalizeTaskFilter(location.data.query.tasks);
|
|
24
24
|
const restoreFocus = documentRoot.activeElement?.hasAttribute('data-task-filter') === true;
|
|
25
|
-
mediator.
|
|
25
|
+
mediator.dispatchEvent(new CustomEvent('task:filter', {detail: filter}));
|
|
26
26
|
if (restoreFocus) {documentRoot.querySelector<HTMLAnchorElement>(`[data-task-filter="${filter}"]`)!.focus();}
|
|
27
27
|
};
|
|
28
28
|
router.routes = {'/': route, defaultRoute: route};
|
|
@@ -28,7 +28,7 @@ export default function PackageDocsSection() {
|
|
|
28
28
|
<p className="eyebrow">Model</p><h3>State is observable, not magical.</h3>
|
|
29
29
|
<CodeBlock lines={[
|
|
30
30
|
<><span className={syntax.keyword}>const</span> model = <span className={syntax.keyword}>new</span> <span className={syntax.type}>Model</span>({'{'}count: 0{'}'});</>,
|
|
31
|
-
<>model.
|
|
31
|
+
<>model.addEventListener(<span className={syntax.value}>'change'</span>, event => render(event.detail));</>,
|
|
32
32
|
<>model.update({'{'}count: 1{'}'});</>
|
|
33
33
|
]} />
|
|
34
34
|
<p><a href={repositories.model}>Model documentation</a></p>
|
|
@@ -46,8 +46,8 @@ export default function PackageDocsSection() {
|
|
|
46
46
|
<article>
|
|
47
47
|
<p className="eyebrow">Mediator</p><h3>Modules communicate through events.</h3>
|
|
48
48
|
<CodeBlock lines={[
|
|
49
|
-
<>mediator.
|
|
50
|
-
<>mediator.
|
|
49
|
+
<>mediator.addEventListener(<span className={syntax.value}>'counter:increment'</span>, increment);</>,
|
|
50
|
+
<>mediator.dispatchEvent(<span className={syntax.keyword}>new</span> <span className={syntax.type}>CustomEvent</span>(<span className={syntax.value}>'counter:increment'</span>));</>
|
|
51
51
|
]} />
|
|
52
52
|
<p><a href={repositories.mediator}>Mediator documentation</a></p>
|
|
53
53
|
</article>
|
|
@@ -56,7 +56,7 @@ export default function PackageDocsSection() {
|
|
|
56
56
|
<CodeBlock lines={[
|
|
57
57
|
<>router.routes = {'{'}</>,
|
|
58
58
|
<> <span className={syntax.value}>'/'</span>: (_scope, location) => {'{'}</>,
|
|
59
|
-
<> mediator.
|
|
59
|
+
<> mediator.dispatchEvent(<span className={syntax.keyword}>new</span> <span className={syntax.type}>CustomEvent</span>(<span className={syntax.value}>'filter:set'</span>, {'{'}detail: location.data.query.filter{'}'}));</>,
|
|
60
60
|
<> {'}'}</>,
|
|
61
61
|
<>{'}'};</>
|
|
62
62
|
]} />
|
package/cli/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import {realpathSync} from 'node:fs';
|
|
3
4
|
import {readdir} from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import process from 'node:process';
|
|
@@ -106,7 +107,7 @@ export async function runCli(args: readonly string[], dependencies: CliDependenc
|
|
|
106
107
|
|
|
107
108
|
const invokedPath = path.resolve(process.argv[1] as string);
|
|
108
109
|
const modulePath = fileURLToPath(import.meta.url);
|
|
109
|
-
if (invokedPath === modulePath) {
|
|
110
|
+
if (realpathSync(invokedPath) === realpathSync(modulePath)) {
|
|
110
111
|
runCli(process.argv.slice(2)).then(
|
|
111
112
|
(exitCode) => {process.exitCode = exitCode;},
|
|
112
113
|
(error: unknown) => {
|
|
@@ -20,24 +20,24 @@ export function initializeTaskApplication(documentRoot) {
|
|
|
20
20
|
const view = createTaskView(parentElement, model);
|
|
21
21
|
const status = parentElement.querySelector('[data-task-status]');
|
|
22
22
|
const updateStatus = () => { status.textContent = describeTaskStatus(model.get()); };
|
|
23
|
-
const addTask = (
|
|
24
|
-
const toggleTask = (
|
|
25
|
-
const setFilter = (
|
|
26
|
-
mediator.
|
|
27
|
-
mediator.
|
|
28
|
-
mediator.
|
|
23
|
+
const addTask = (event) => { model.add(event.detail); updateStatus(); };
|
|
24
|
+
const toggleTask = (event) => { model.toggle(event.detail); updateStatus(); };
|
|
25
|
+
const setFilter = (event) => { model.setFilter(event.detail); updateStatus(); };
|
|
26
|
+
mediator.addEventListener('task:add', addTask);
|
|
27
|
+
mediator.addEventListener('task:toggle', toggleTask);
|
|
28
|
+
mediator.addEventListener('task:filter', setFilter);
|
|
29
29
|
const delegated = view.delegate(parentElement);
|
|
30
30
|
delegated.on('submit', '[data-task-form]', (event) => {
|
|
31
31
|
event.preventDefault();
|
|
32
32
|
const form = event.target;
|
|
33
33
|
const input = form.querySelector('[name="task"]');
|
|
34
|
-
mediator.
|
|
34
|
+
mediator.dispatchEvent(new CustomEvent('task:add', { detail: input.value }));
|
|
35
35
|
parentElement.querySelector('[name="task"]').focus();
|
|
36
36
|
});
|
|
37
37
|
delegated.on('change', '[data-task-toggle]', (event) => {
|
|
38
38
|
const input = event.target;
|
|
39
39
|
const id = Number(input.dataset.taskId);
|
|
40
|
-
mediator.
|
|
40
|
+
mediator.dispatchEvent(new CustomEvent('task:toggle', { detail: id }));
|
|
41
41
|
const focusTarget = parentElement.querySelector(`[data-task-id="${id}"]`)
|
|
42
42
|
?? parentElement.querySelector('[data-task-filter][aria-current="page"]');
|
|
43
43
|
focusTarget.focus();
|
|
@@ -51,9 +51,9 @@ export function initializeTaskApplication(documentRoot) {
|
|
|
51
51
|
destroy() {
|
|
52
52
|
delegated.clear();
|
|
53
53
|
router.destroy();
|
|
54
|
-
mediator.
|
|
55
|
-
mediator.
|
|
56
|
-
mediator.
|
|
54
|
+
mediator.removeEventListener('task:add', addTask);
|
|
55
|
+
mediator.removeEventListener('task:toggle', toggleTask);
|
|
56
|
+
mediator.removeEventListener('task:filter', setFilter);
|
|
57
57
|
view.destroy();
|
|
58
58
|
model.destroy();
|
|
59
59
|
mediator.destroy();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskApplication.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskApplication.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,sBAAsB,EAAE,kBAAkB,EAAC,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAC,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AACxE,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAC,cAAc,EAAC,MAAM,eAAe,CAAC;AAW7C;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,YAAsB;IAC5D,MAAM,aAAa,GAAG,YAAY,CAAC,aAAa,CAAc,qBAAqB,CAAC,CAAC;IACrF,IAAI,CAAC,aAAa,EAAE,CAAC;QAAA,MAAM,IAAI,SAAS,CAAC,iEAAiE,CAAC,CAAC;IAAA,CAAC;IAE7G,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,aAAa,CAAC,aAAa,CAAc,oBAAoB,CAAE,CAAC;IAC/E,MAAM,YAAY,GAAG,GAAS,EAAE,GAAE,MAAM,CAAC,WAAW,GAAG,kBAAkB,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA,CAAC,CAAC;IAEzF,MAAM,OAAO,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"TaskApplication.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskApplication.ts"],"names":[],"mappings":"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","sourcesContent":["import type Router from 'white-label-router';\nimport type View from 'white-label-view';\nimport {createInitialTaskState, describeTaskStatus} from '../../view/examples/tasks/task-state.js';\nimport {createTaskMediator, type TaskMediator} from './TaskMediator.js';\nimport TaskModel from './TaskModel.js';\nimport {createTaskRouter} from './TaskRouter.js';\nimport {createTaskView} from './TaskView.js';\n\n/** Public handles make the example straightforward to test and tear down. */\nexport interface TaskApplication {\n mediator: TaskMediator;\n model: TaskModel;\n router: Router;\n view: View;\n destroy(): void;\n}\n\n/**\n * Compose the application at one explicit boundary.\n *\n * This is the only module that knows every moving part. Model, View, Router,\n * and Mediator stay independently understandable, while this file documents\n * the small amount of wiring needed to make them cooperate in a real feature.\n */\nexport function initializeTaskApplication(documentRoot: Document): TaskApplication {\n const parentElement = documentRoot.querySelector<HTMLElement>('[data-task-example]');\n if (!parentElement) {throw new TypeError('White Label task example requires a data-task-example container');}\n\n const mediator = createTaskMediator();\n const model = new TaskModel(createInitialTaskState());\n const view = createTaskView(parentElement, model);\n const status = parentElement.querySelector<HTMLElement>('[data-task-status]')!;\n const updateStatus = (): void => {status.textContent = describeTaskStatus(model.get());};\n\n const addTask = (event: CustomEvent<string>): void => {model.add(event.detail); updateStatus();};\n const toggleTask = (event: CustomEvent<number>): void => {model.toggle(event.detail); updateStatus();};\n const setFilter = (event: CustomEvent<'all' | 'active' | 'completed'>): void => {model.setFilter(event.detail); updateStatus();};\n mediator.addEventListener('task:add', addTask);\n mediator.addEventListener('task:toggle', toggleTask);\n mediator.addEventListener('task:filter', setFilter);\n\n const delegated = view.delegate(parentElement);\n delegated.on('submit', '[data-task-form]', (event: Event) => {\n event.preventDefault();\n const form = event.target as HTMLFormElement;\n const input = form.querySelector<HTMLInputElement>('[name=\"task\"]')!;\n mediator.dispatchEvent(new CustomEvent('task:add', {detail: input.value}));\n parentElement.querySelector<HTMLInputElement>('[name=\"task\"]')!.focus();\n });\n delegated.on('change', '[data-task-toggle]', (event: Event) => {\n const input = event.target as HTMLInputElement;\n const id = Number(input.dataset.taskId);\n mediator.dispatchEvent(new CustomEvent('task:toggle', {detail: id}));\n const focusTarget = parentElement.querySelector<HTMLInputElement>(`[data-task-id=\"${id}\"]`)\n ?? parentElement.querySelector<HTMLAnchorElement>('[data-task-filter][aria-current=\"page\"]')!;\n focusTarget.focus();\n });\n\n const router = createTaskRouter(documentRoot, mediator);\n\n return {\n mediator,\n model,\n router,\n view,\n destroy() {\n delegated.clear();\n router.destroy();\n mediator.removeEventListener('task:add', addTask);\n mediator.removeEventListener('task:toggle', toggleTask);\n mediator.removeEventListener('task:filter', setFilter);\n view.destroy();\n model.destroy();\n mediator.destroy();\n }\n };\n}\n"]}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import Mediator from 'white-label-mediator';
|
|
2
2
|
import type { TaskFilter } from '../../view/examples/tasks/task-state.js';
|
|
3
|
-
/**
|
|
4
|
-
* Document the event protocol in one place even though the currently installed
|
|
5
|
-
* Mediator exposes the EventEmitter-compatible runtime without a generic map.
|
|
6
|
-
* Keeping names and payloads explicit still gives learners one clear contract
|
|
7
|
-
* to follow when tracing the example.
|
|
8
|
-
*/
|
|
3
|
+
/** Application event vocabulary shared by the generated task modules. */
|
|
9
4
|
export type TaskEvents = {
|
|
10
|
-
'task:add':
|
|
11
|
-
'task:filter':
|
|
12
|
-
'task:toggle':
|
|
5
|
+
'task:add': string;
|
|
6
|
+
'task:filter': TaskFilter;
|
|
7
|
+
'task:toggle': number;
|
|
13
8
|
};
|
|
14
|
-
export type TaskMediator = Mediator
|
|
9
|
+
export type TaskMediator = Mediator<TaskEvents>;
|
|
15
10
|
export declare function createTaskMediator(): TaskMediator;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskMediator.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskMediator.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"TaskMediator.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskMediator.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAY5C,MAAM,UAAU,kBAAkB;IAC9B,OAAO,IAAI,QAAQ,EAAc,CAAC,UAAU,EAAE,CAAC;AACnD,CAAC","sourcesContent":["import Mediator from 'white-label-mediator';\nimport type {TaskFilter} from '../../view/examples/tasks/task-state.js';\n\n/** Application event vocabulary shared by the generated task modules. */\nexport type TaskEvents = {\n 'task:add': string;\n 'task:filter': TaskFilter;\n 'task:toggle': number;\n};\n\nexport type TaskMediator = Mediator<TaskEvents>;\n\nexport function createTaskMediator(): TaskMediator {\n return new Mediator<TaskEvents>().initialize();\n}\n"]}
|
|
@@ -17,7 +17,7 @@ export function createTaskRouter(documentRoot, mediator) {
|
|
|
17
17
|
const route = (_scope, location) => {
|
|
18
18
|
const filter = normalizeTaskFilter(location.data.query.tasks);
|
|
19
19
|
const restoreFocus = documentRoot.activeElement?.hasAttribute('data-task-filter') === true;
|
|
20
|
-
mediator.
|
|
20
|
+
mediator.dispatchEvent(new CustomEvent('task:filter', { detail: filter }));
|
|
21
21
|
if (restoreFocus) {
|
|
22
22
|
documentRoot.querySelector(`[data-task-filter="${filter}"]`).focus();
|
|
23
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskRouter.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskRouter.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAIxC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AAExE,kFAAkF;AAClF,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IACzD,OAAO,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,KAAmB,CAAC,CAAC,CAAC,CAAC,KAAmB,CAAC,CAAC,CAAC,KAAK,CAAC;AACvF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAsB,EAAE,QAAsB;IAC3E,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,MAAsB,EAAE,QAAiD,EAAQ,EAAE;QAC9F,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,EAAE,YAAY,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;QAC3F,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"TaskRouter.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskRouter.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAIxC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AAExE,kFAAkF;AAClF,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IACzD,OAAO,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,KAAmB,CAAC,CAAC,CAAC,CAAC,KAAmB,CAAC,CAAC,CAAC,KAAK,CAAC;AACvF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAsB,EAAE,QAAsB;IAC3E,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,MAAsB,EAAE,QAAiD,EAAQ,EAAE;QAC9F,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,YAAY,CAAC,aAAa,EAAE,YAAY,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;QAC3F,QAAQ,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,aAAa,EAAE,EAAC,MAAM,EAAE,MAAM,EAAC,CAAC,CAAC,CAAC;QACzE,IAAI,YAAY,EAAE,CAAC;YAAA,YAAY,CAAC,aAAa,CAAoB,sBAAsB,MAAM,IAAI,CAAE,CAAC,KAAK,EAAE,CAAC;QAAA,CAAC;IACjH,CAAC,CAAC;IACF,MAAM,CAAC,MAAM,GAAG,EAAC,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAC,CAAC;IAClD,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;AAC/B,CAAC","sourcesContent":["import Router from 'white-label-router';\nimport type {TaskFilter} from '../../view/examples/tasks/task-state.js';\nimport type {TaskMediator} from './TaskMediator.js';\n\nconst taskFilters = new Set<TaskFilter>(['all', 'active', 'completed']);\n\n/** Normalize URL input at the routing boundary before it reaches domain state. */\nexport function normalizeTaskFilter(value: string | undefined): TaskFilter {\n return value && taskFilters.has(value as TaskFilter) ? value as TaskFilter : 'all';\n}\n\n/**\n * Router translates URL state into an application event.\n *\n * It does not update the Model or DOM directly. That separation keeps routing\n * usable in different applications and makes browser navigation easy to test.\n */\nexport function createTaskRouter(documentRoot: Document, mediator: TaskMediator): Router {\n const router = new Router();\n router.scope = documentRoot.querySelector('main');\n router.mediator = mediator;\n const route = (_scope: Element | null, location: {data: {query: Record<string, string>}}): void => {\n const filter = normalizeTaskFilter(location.data.query.tasks);\n const restoreFocus = documentRoot.activeElement?.hasAttribute('data-task-filter') === true;\n mediator.dispatchEvent(new CustomEvent('task:filter', {detail: filter}));\n if (restoreFocus) {documentRoot.querySelector<HTMLAnchorElement>(`[data-task-filter=\"${filter}\"]`)!.focus();}\n };\n router.routes = {'/': route, defaultRoute: route};\n return router.initialize();\n}\n"]}
|
|
@@ -10,7 +10,7 @@ const repositories = {
|
|
|
10
10
|
export default function PackageDocsSection() {
|
|
11
11
|
return (_jsx("section", { className: "section", id: "packages", "aria-labelledby": "packages-title", children: _jsxs("div", { className: "container", children: [_jsxs("div", { className: "section-intro", children: [_jsx("p", { className: "eyebrow", children: "Documentation" }), _jsx("h2", { id: "packages-title", children: "The core flow stays explicit." }), _jsx("p", { children: "Each package has one job, can be used independently, and stays easy to replace or test because application concerns are not hidden behind a framework." })] }), _jsx("div", { className: "architecture", children: _jsxs("ol", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Router" }), _jsx("span", { children: "turns a URL into application intent" })] }), _jsxs("li", { children: [_jsx("strong", { children: "Mediator" }), _jsx("span", { children: "coordinates that intent between modules" })] }), _jsxs("li", { children: [_jsx("strong", { children: "Model" }), _jsx("span", { children: "stores and publishes application state" })] }), _jsxs("li", { children: [_jsx("strong", { children: "View" }), _jsx("span", { children: "renders the resulting interface" })] })] }) }), _jsxs("div", { className: "docs-grid", children: [_jsxs("article", { children: [_jsx("p", { className: "eyebrow", children: "Model" }), _jsx("h3", { children: "State is observable, not magical." }), _jsx(CodeBlock, { lines: [
|
|
12
12
|
_jsxs(_Fragment, { children: [_jsx("span", { className: syntax.keyword, children: "const" }), " model = ", _jsx("span", { className: syntax.keyword, children: "new" }), " ", _jsx("span", { className: syntax.type, children: "Model" }), "(", '{', "count: 0", '}', ");"] }),
|
|
13
|
-
_jsxs(_Fragment, { children: ["model.
|
|
13
|
+
_jsxs(_Fragment, { children: ["model.addEventListener(", _jsx("span", { className: syntax.value, children: "'change'" }), ", event => render(event.detail));"] }),
|
|
14
14
|
_jsxs(_Fragment, { children: ["model.update(", '{', "count: 1", '}', ");"] })
|
|
15
15
|
] }), _jsx("p", { children: _jsx("a", { href: repositories.model, children: "Model documentation" }) })] }), _jsxs("article", { children: [_jsx("p", { className: "eyebrow", children: "View" }), _jsx("h3", { children: "JSX renders through White Label." }), _jsx(CodeBlock, { lines: [
|
|
16
16
|
_jsxs(_Fragment, { children: [_jsx("span", { className: syntax.keyword, children: "const" }), " view = ", _jsx("span", { className: syntax.keyword, children: "new" }), " ", _jsx("span", { className: syntax.type, children: "View" }), "(", '{'] }),
|
|
@@ -18,12 +18,12 @@ export default function PackageDocsSection() {
|
|
|
18
18
|
_jsxs(_Fragment, { children: [" template: state => <p>", '{', "state.count", '}', "</p>"] }),
|
|
19
19
|
_jsxs(_Fragment, { children: ['}', ").initialize();"] })
|
|
20
20
|
] }), _jsx("p", { children: _jsx("a", { href: repositories.view, children: "View documentation" }) })] }), _jsxs("article", { children: [_jsx("p", { className: "eyebrow", children: "Mediator" }), _jsx("h3", { children: "Modules communicate through events." }), _jsx(CodeBlock, { lines: [
|
|
21
|
-
_jsxs(_Fragment, { children: ["mediator.
|
|
22
|
-
_jsxs(_Fragment, { children: ["mediator.
|
|
21
|
+
_jsxs(_Fragment, { children: ["mediator.addEventListener(", _jsx("span", { className: syntax.value, children: "'counter:increment'" }), ", increment);"] }),
|
|
22
|
+
_jsxs(_Fragment, { children: ["mediator.dispatchEvent(", _jsx("span", { className: syntax.keyword, children: "new" }), " ", _jsx("span", { className: syntax.type, children: "CustomEvent" }), "(", _jsx("span", { className: syntax.value, children: "'counter:increment'" }), "));"] })
|
|
23
23
|
] }), _jsx("p", { children: _jsx("a", { href: repositories.mediator, children: "Mediator documentation" }) })] }), _jsxs("article", { children: [_jsx("p", { className: "eyebrow", children: "Router" }), _jsx("h3", { children: "Routes describe intent." }), _jsx(CodeBlock, { lines: [
|
|
24
24
|
_jsxs(_Fragment, { children: ["router.routes = ", '{'] }),
|
|
25
25
|
_jsxs(_Fragment, { children: [" ", _jsx("span", { className: syntax.value, children: "'/'" }), ": (_scope, location) => ", '{'] }),
|
|
26
|
-
_jsxs(_Fragment, { children: [" mediator.
|
|
26
|
+
_jsxs(_Fragment, { children: [" mediator.dispatchEvent(", _jsx("span", { className: syntax.keyword, children: "new" }), " ", _jsx("span", { className: syntax.type, children: "CustomEvent" }), "(", _jsx("span", { className: syntax.value, children: "'filter:set'" }), ", ", '{', "detail: location.data.query.filter", '}', "));"] }),
|
|
27
27
|
_jsxs(_Fragment, { children: [" ", '}'] }),
|
|
28
28
|
_jsxs(_Fragment, { children: ['}', ";"] })
|
|
29
29
|
] }), _jsx("p", { children: _jsx("a", { href: repositories.router, children: "Router documentation" }) })] })] })] }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageDocsSection.js","sourceRoot":"","sources":["../../../../../app/assets/view/sections/PackageDocsSection.tsx"],"names":[],"mappings":";AAAA,OAAO,SAAS,EAAE,EAAC,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAElD,MAAM,YAAY,GAAG;IACjB,QAAQ,EAAE,gDAAgD;IAC1D,KAAK,EAAE,6CAA6C;IACpD,MAAM,EAAE,8CAA8C;IACtD,IAAI,EAAE,4CAA4C;CACrD,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,OAAO,UAAU,kBAAkB;IACtC,OAAO,CACH,kBAAS,SAAS,EAAC,SAAS,EAAC,EAAE,EAAC,UAAU,qBAAiB,gBAAgB,YACvE,eAAK,SAAS,EAAC,WAAW,aACtB,eAAK,SAAS,EAAC,eAAe,aAC1B,YAAG,SAAS,EAAC,SAAS,8BAAkB,EACxC,aAAI,EAAE,EAAC,gBAAgB,8CAAmC,EAC1D,iLAA6J,IAC3J,EACN,cAAK,SAAS,EAAC,cAAc,YAAC,yBAC1B,yBAAI,sCAAuB,EAAA,iEAAgD,IAAK,EAChF,yBAAI,wCAAyB,EAAA,qEAAoD,IAAK,EACtF,yBAAI,qCAAsB,EAAA,oEAAmD,IAAK,EAClF,yBAAI,oCAAqB,EAAA,6DAA4C,IAAK,IACzE,GAAM,EACX,eAAK,SAAS,EAAC,WAAW,aACtB,8BACI,YAAG,SAAS,EAAC,SAAS,sBAAU,EAAA,6DAA0C,EAC1E,KAAC,SAAS,IAAC,KAAK,EAAE;wCACd,8BAAE,eAAM,SAAS,EAAE,MAAM,CAAC,OAAO,sBAAc,eAAS,eAAM,SAAS,EAAE,MAAM,CAAC,OAAO,oBAAY,OAAC,eAAM,SAAS,EAAE,MAAM,CAAC,IAAI,sBAAc,OAAE,GAAG,cAAU,GAAG,UAAM;wCACtK,
|
|
1
|
+
{"version":3,"file":"PackageDocsSection.js","sourceRoot":"","sources":["../../../../../app/assets/view/sections/PackageDocsSection.tsx"],"names":[],"mappings":"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","sourcesContent":["import CodeBlock, {syntax} from '../CodeBlock.js';\n\nconst repositories = {\n mediator: 'https://github.com/bshack/white-label-mediator',\n model: 'https://github.com/bshack/white-label-model',\n router: 'https://github.com/bshack/white-label-router',\n view: 'https://github.com/bshack/white-label-view'\n};\n\n/** Package documentation stays close to the example it describes. */\nexport default function PackageDocsSection() {\n return (\n <section className=\"section\" id=\"packages\" aria-labelledby=\"packages-title\">\n <div className=\"container\">\n <div className=\"section-intro\">\n <p className=\"eyebrow\">Documentation</p>\n <h2 id=\"packages-title\">The core flow stays explicit.</h2>\n <p>Each package has one job, can be used independently, and stays easy to replace or test because application concerns are not hidden behind a framework.</p>\n </div>\n <div className=\"architecture\"><ol>\n <li><strong>Router</strong><span>turns a URL into application intent</span></li>\n <li><strong>Mediator</strong><span>coordinates that intent between modules</span></li>\n <li><strong>Model</strong><span>stores and publishes application state</span></li>\n <li><strong>View</strong><span>renders the resulting interface</span></li>\n </ol></div>\n <div className=\"docs-grid\">\n <article>\n <p className=\"eyebrow\">Model</p><h3>State is observable, not magical.</h3>\n <CodeBlock lines={[\n <><span className={syntax.keyword}>const</span> model = <span className={syntax.keyword}>new</span> <span className={syntax.type}>Model</span>({'{'}count: 0{'}'});</>,\n <>model.addEventListener(<span className={syntax.value}>'change'</span>, event => render(event.detail));</>,\n <>model.update({'{'}count: 1{'}'});</>\n ]} />\n <p><a href={repositories.model}>Model documentation</a></p>\n </article>\n <article>\n <p className=\"eyebrow\">View</p><h3>JSX renders through White Label.</h3>\n <CodeBlock lines={[\n <><span className={syntax.keyword}>const</span> view = <span className={syntax.keyword}>new</span> <span className={syntax.type}>View</span>({'{'}</>,\n <> model,</>,\n <> template: state => <p>{'{'}state.count{'}'}</p></>,\n <>{'}'}).initialize();</>\n ]} />\n <p><a href={repositories.view}>View documentation</a></p>\n </article>\n <article>\n <p className=\"eyebrow\">Mediator</p><h3>Modules communicate through events.</h3>\n <CodeBlock lines={[\n <>mediator.addEventListener(<span className={syntax.value}>'counter:increment'</span>, increment);</>,\n <>mediator.dispatchEvent(<span className={syntax.keyword}>new</span> <span className={syntax.type}>CustomEvent</span>(<span className={syntax.value}>'counter:increment'</span>));</>\n ]} />\n <p><a href={repositories.mediator}>Mediator documentation</a></p>\n </article>\n <article>\n <p className=\"eyebrow\">Router</p><h3>Routes describe intent.</h3>\n <CodeBlock lines={[\n <>router.routes = {'{'}</>,\n <> <span className={syntax.value}>'/'</span>: (_scope, location) => {'{'}</>,\n <> mediator.dispatchEvent(<span className={syntax.keyword}>new</span> <span className={syntax.type}>CustomEvent</span>(<span className={syntax.value}>'filter:set'</span>, {'{'}detail: location.data.query.filter{'}'}));</>,\n <> {'}'}</>,\n <>{'}'};</>\n ]} />\n <p><a href={repositories.router}>Router documentation</a></p>\n </article>\n </div>\n </div>\n </section>\n );\n}\n"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
2
3
|
import { readdir } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import process from 'node:process';
|
|
@@ -88,7 +89,7 @@ export async function runCli(args, dependencies = {}) {
|
|
|
88
89
|
}
|
|
89
90
|
const invokedPath = path.resolve(process.argv[1]);
|
|
90
91
|
const modulePath = fileURLToPath(import.meta.url);
|
|
91
|
-
if (invokedPath === modulePath) {
|
|
92
|
+
if (realpathSync(invokedPath) === realpathSync(modulePath)) {
|
|
92
93
|
runCli(process.argv.slice(2)).then((exitCode) => { process.exitCode = exitCode; }, (error) => {
|
|
93
94
|
process.stderr.write(`Unable to create White Label project: ${formatCliError(error)}\n`);
|
|
94
95
|
process.exitCode = 1;
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAC,aAAa,EAAC,MAAM,UAAU,CAAC;AACvC,OAAO,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAYnD,SAAS,KAAK;IACV,OAAO;QACH,QAAQ;QACR,qDAAqD;QACrD,EAAE;QACF,WAAW;QACX,qDAAqD;QACrD,EAAE;QACF,UAAU;QACV,kEAAkE;QAClE,4EAA4E;QAC5E,8CAA8C;KACjD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,KAA4B,EAAE,MAA6B;IAChF,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAC,KAAK,EAAE,MAAM,EAAC,CAAC,CAAC;IAClD,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAClC,8JAA8J,CACjK,CAAC;QACF,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACL,CAAC;AAED,wGAAwG;AACxG,KAAK,UAAU,0BAA0B,CAAC,WAAmB;IACzD,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,wCAAwC,WAAW,EAAE,CAAC,CAAC;QAC3E,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAAA,OAAO;QAAA,CAAC;QACnF,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAuB,EAAE,YAAY,GAAoB,EAAE;IACpF,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAC5C,MAAM,QAAQ,GAAG,YAAY,CAAC,aAAa,IAAI,aAAa,CAAC;IAC7D,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IACrD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IACrD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAClD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IACrD,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;IAEhD,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC;IACb,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD,IACI,OAAO,KAAK,QAAQ;QACpB,CAAC,WAAW;QACZ,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAC7D,CAAC;QACC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC;IACb,CAAC;IAED,IAAI,GAAY,CAAC;IACjB,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,GAAG,GAAG,IAAI,CAAC;IACf,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,GAAG,GAAG,KAAK,CAAC;IAChB,CAAC;SAAM,IAAI,YAAY,CAAC,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5F,GAAG,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACJ,GAAG,GAAG,IAAI,CAAC;IACf,CAAC;IAED,MAAM,mBAAmB,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;IAC7D,MAAM,0BAA0B,CAAC,mBAAmB,CAAC,CAAC;IACtD,MAAM,QAAQ,CAAC,EAAC,WAAW,EAAE,mBAAmB,EAAE,GAAG,EAAC,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,kCAAkC,mBAAmB,IAAI,CAAC,CAAC;IACxE,OAAO,CAAC,CAAC;AACb,CAAC;AAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAW,CAAC,CAAC;AAC5D,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport {realpathSync} from 'node:fs';\nimport {readdir} from 'node:fs/promises';\nimport path from 'node:path';\nimport process from 'node:process';\nimport {createInterface} from 'node:readline/promises';\nimport {fileURLToPath} from 'node:url';\nimport {createProject} from '../scaffold/index.js';\n\nexport interface CliDependencies {\n cwd?: () => string;\n createProject?: typeof createProject;\n stdout?: Pick<NodeJS.WriteStream, 'write'>;\n stderr?: Pick<NodeJS.WriteStream, 'write'>;\n input?: NodeJS.ReadableStream;\n output?: NodeJS.WritableStream;\n isInteractive?: boolean;\n}\n\nfunction usage() {\n return [\n 'Usage:',\n ' white-label create <directory> [--jsx | --no-jsx]',\n '',\n 'Commands:',\n ' create <directory> Create a White Label project.',\n '',\n 'Options:',\n ' --jsx Generate TypeScript with JSX/TSX templates.',\n ' --no-jsx Generate plain TypeScript with HTML string templates.',\n ' -h, --help Show this help message.'\n ].join('\\n');\n}\n\nexport function formatCliError(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function askForJsx(input: NodeJS.ReadableStream, output: NodeJS.WritableStream): Promise<boolean> {\n const readline = createInterface({input, output});\n try {\n const answer = await readline.question(\n 'Use JSX/TSX for page and view templates? Choose Yes for JSX syntax like <section>...</section>, or No for plain TypeScript that returns HTML strings. (Y/n) '\n );\n return !/^n(?:o)?$/i.test(answer.trim());\n } finally {\n readline.close();\n }\n}\n\n/** Reject an existing non-empty target so a mistyped CLI destination cannot overwrite project files. */\nasync function assertDestinationAvailable(destination: string) {\n try {\n const entries = await readdir(destination);\n if (entries.length) {\n throw new Error(`Destination directory must be empty: ${destination}`);\n }\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {return;}\n throw error;\n }\n}\n\nexport async function runCli(args: readonly string[], dependencies: CliDependencies = {}) {\n const cwd = dependencies.cwd ?? process.cwd;\n const scaffold = dependencies.createProject ?? createProject;\n const stdout = dependencies.stdout ?? process.stdout;\n const stderr = dependencies.stderr ?? process.stderr;\n const input = dependencies.input ?? process.stdin;\n const output = dependencies.output ?? process.stdout;\n const [command, destination, ...options] = args;\n\n if (command === '--help' || command === '-h' || args.length === 0) {\n stdout.write(`${usage()}\\n`);\n return 0;\n }\n\n const allowedOptions = new Set(['--jsx', '--no-jsx']);\n if (\n command !== 'create' ||\n !destination ||\n options.some(option => !allowedOptions.has(option)) ||\n (options.includes('--jsx') && options.includes('--no-jsx'))\n ) {\n stderr.write(`${usage()}\\n`);\n return 1;\n }\n\n let jsx: boolean;\n if (options.includes('--jsx')) {\n jsx = true;\n } else if (options.includes('--no-jsx')) {\n jsx = false;\n } else if (dependencies.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY)) {\n jsx = await askForJsx(input, output);\n } else {\n jsx = true;\n }\n\n const resolvedDestination = path.resolve(cwd(), destination);\n await assertDestinationAvailable(resolvedDestination);\n await scaffold({destination: resolvedDestination, jsx});\n stdout.write(`Created White Label project at ${resolvedDestination}\\n`);\n return 0;\n}\n\nconst invokedPath = path.resolve(process.argv[1] as string);\nconst modulePath = fileURLToPath(import.meta.url);\nif (realpathSync(invokedPath) === realpathSync(modulePath)) {\n runCli(process.argv.slice(2)).then(\n (exitCode) => {process.exitCode = exitCode;},\n (error: unknown) => {\n process.stderr.write(`Unable to create White Label project: ${formatCliError(error)}\\n`);\n process.exitCode = 1;\n }\n );\n}\n"]}
|
package/dist/scaffold/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export declare function createSiteManifest(): {
|
|
|
24
24
|
allowScripts: {
|
|
25
25
|
'@parcel/watcher@2.5.1': boolean;
|
|
26
26
|
'esbuild@0.28.2': boolean;
|
|
27
|
-
'white-label-view@
|
|
27
|
+
'white-label-view@6.0.0': boolean;
|
|
28
28
|
};
|
|
29
29
|
dependenciesMeta: {
|
|
30
30
|
'@parcel/watcher@2.5.1': {
|
|
@@ -33,7 +33,7 @@ export declare function createSiteManifest(): {
|
|
|
33
33
|
'esbuild@0.28.2': {
|
|
34
34
|
built: boolean;
|
|
35
35
|
};
|
|
36
|
-
'white-label-view@
|
|
36
|
+
'white-label-view@6.0.0': {
|
|
37
37
|
built: boolean;
|
|
38
38
|
};
|
|
39
39
|
};
|
|
@@ -55,10 +55,10 @@ export declare function createSiteManifest(): {
|
|
|
55
55
|
};
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
|
-
* Create a White Label project.
|
|
58
|
+
* Create a White Label project without overwriting a non-empty destination.
|
|
59
59
|
*
|
|
60
60
|
* This is the shared implementation behind every creation interface. Adapters
|
|
61
|
-
* translate their environment into these options instead of owning templates
|
|
62
|
-
* project-generation behavior themselves.
|
|
61
|
+
* translate their environment into these options instead of owning templates,
|
|
62
|
+
* overwrite policy, or project-generation behavior themselves.
|
|
63
63
|
*/
|
|
64
64
|
export declare function createProject({ destination, fileSystem, jsx }: CreateProjectOptions): Promise<void>;
|
package/dist/scaffold/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @module scaffold */
|
|
2
|
-
import { cp, mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { cp, mkdir, readdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
@@ -13,6 +13,21 @@ const nodeFileSystem = {
|
|
|
13
13
|
await writeFile(destination, `${JSON.stringify(value, null, 2)}\n`);
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
+
/** Refuse to layer a generated project over existing user files. */
|
|
17
|
+
async function assertDestinationAvailable(destination) {
|
|
18
|
+
try {
|
|
19
|
+
const entries = await readdir(destination);
|
|
20
|
+
if (entries.length) {
|
|
21
|
+
throw new Error(`Destination directory must be empty: ${destination}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
16
31
|
/** Return the package manifest used by generated White Label projects. */
|
|
17
32
|
export function createSiteManifest() {
|
|
18
33
|
return {
|
|
@@ -29,12 +44,12 @@ export function createSiteManifest() {
|
|
|
29
44
|
allowScripts: {
|
|
30
45
|
'@parcel/watcher@2.5.1': true,
|
|
31
46
|
'esbuild@0.28.2': true,
|
|
32
|
-
'white-label-view@
|
|
47
|
+
'white-label-view@6.0.0': true
|
|
33
48
|
},
|
|
34
49
|
dependenciesMeta: {
|
|
35
50
|
'@parcel/watcher@2.5.1': { built: true },
|
|
36
51
|
'esbuild@0.28.2': { built: true },
|
|
37
|
-
'white-label-view@
|
|
52
|
+
'white-label-view@6.0.0': { built: true }
|
|
38
53
|
},
|
|
39
54
|
devDependencies: {
|
|
40
55
|
'@tailwindcss/cli': '4.3.3',
|
|
@@ -47,10 +62,10 @@ export function createSiteManifest() {
|
|
|
47
62
|
'typescript': '7.0.2'
|
|
48
63
|
},
|
|
49
64
|
dependencies: {
|
|
50
|
-
'white-label-mediator': '
|
|
51
|
-
'white-label-model': '
|
|
52
|
-
'white-label-router': '
|
|
53
|
-
'white-label-view': '
|
|
65
|
+
'white-label-mediator': '5.0.0',
|
|
66
|
+
'white-label-model': '7.0.1',
|
|
67
|
+
'white-label-router': '6.0.0',
|
|
68
|
+
'white-label-view': '6.0.0'
|
|
54
69
|
}
|
|
55
70
|
};
|
|
56
71
|
}
|
|
@@ -74,13 +89,14 @@ const noJsxTemplateCopies = [
|
|
|
74
89
|
['tsconfig.site.no-jsx.json', 'tsconfig.json']
|
|
75
90
|
];
|
|
76
91
|
/**
|
|
77
|
-
* Create a White Label project.
|
|
92
|
+
* Create a White Label project without overwriting a non-empty destination.
|
|
78
93
|
*
|
|
79
94
|
* This is the shared implementation behind every creation interface. Adapters
|
|
80
|
-
* translate their environment into these options instead of owning templates
|
|
81
|
-
* project-generation behavior themselves.
|
|
95
|
+
* translate their environment into these options instead of owning templates,
|
|
96
|
+
* overwrite policy, or project-generation behavior themselves.
|
|
82
97
|
*/
|
|
83
98
|
export async function createProject({ destination, fileSystem = nodeFileSystem, jsx = true }) {
|
|
99
|
+
await assertDestinationAvailable(destination);
|
|
84
100
|
const copies = jsx ? [
|
|
85
101
|
['.editorconfig', '.editorconfig'],
|
|
86
102
|
['.yarnrc.yml', '.yarnrc.yml'],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../scaffold/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,OAAO,EAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../scaffold/index.ts"],"names":[],"mappings":"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","sourcesContent":["/** @module scaffold */\nimport {cp, mkdir, readdir, writeFile} from 'node:fs/promises';\nimport path from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nconst packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');\n\nexport interface ScaffoldFileSystem {\n copy(source: string, destination: string): void | Promise<void>;\n writeJSON(destination: string, value: unknown): void | Promise<void>;\n}\n\nexport interface CreateProjectOptions {\n destination: string;\n fileSystem?: ScaffoldFileSystem;\n jsx?: boolean;\n}\n\nconst nodeFileSystem: ScaffoldFileSystem = {\n async copy(source, destination) {\n await mkdir(path.dirname(destination), {recursive: true});\n await cp(source, destination, {recursive: true});\n },\n async writeJSON(destination, value) {\n await mkdir(path.dirname(destination), {recursive: true});\n await writeFile(destination, `${JSON.stringify(value, null, 2)}\\n`);\n }\n};\n\n/** Refuse to layer a generated project over existing user files. */\nasync function assertDestinationAvailable(destination: string) {\n try {\n const entries = await readdir(destination);\n if (entries.length) {throw new Error(`Destination directory must be empty: ${destination}`);}\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {return;}\n throw error;\n }\n}\n\n/** Return the package manifest used by generated White Label projects. */\nexport function createSiteManifest() {\n return {\n name: 'white-label-site',\n version: '1.0.0',\n private: true,\n type: 'module',\n engines: {node: '^22.18.0 || >=24.11.0'},\n scripts: {\n build: 'tsc -p tsconfig.json && node dist/scripts/build.js',\n typecheck: 'tsc -p tsconfig.json --noEmit',\n test: 'node --run typecheck && node --run build -- --version=test --production=true --site-url=https://example.com && node --test --experimental-test-coverage --test-coverage-include=dist/app/assets/script/index.js --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=100 test/*.test.js'\n },\n allowScripts: {\n '@parcel/watcher@2.5.1': true,\n 'esbuild@0.28.2': true,\n 'white-label-view@6.0.0': true\n },\n dependenciesMeta: {\n '@parcel/watcher@2.5.1': {built: true},\n 'esbuild@0.28.2': {built: true},\n 'white-label-view@6.0.0': {built: true}\n },\n devDependencies: {\n '@tailwindcss/cli': '4.3.3',\n '@types/node': '24.13.3',\n 'axe-core': '4.13.0',\n 'esbuild': '0.28.2',\n 'html-validate': '11.15.0',\n 'jsdom': '30.0.1',\n 'tailwindcss': '4.3.3',\n 'typescript': '7.0.2'\n },\n dependencies: {\n 'white-label-mediator': '5.0.0',\n 'white-label-model': '7.0.1',\n 'white-label-router': '6.0.0',\n 'white-label-view': '6.0.0'\n }\n };\n}\n\nconst commonNoJsxCopies = [\n ['app/assets/data', 'app/assets/data'],\n ['app/assets/style', 'app/assets/style'],\n ['app/assets/script/tasks/TaskApplication.ts', 'app/assets/script/tasks/TaskApplication.ts'],\n ['app/assets/script/tasks/TaskMediator.ts', 'app/assets/script/tasks/TaskMediator.ts'],\n ['app/assets/script/tasks/TaskModel.ts', 'app/assets/script/tasks/TaskModel.ts'],\n ['app/assets/script/tasks/TaskRouter.ts', 'app/assets/script/tasks/TaskRouter.ts'],\n ['app/assets/view/examples/tasks/task-state.ts', 'app/assets/view/examples/tasks/task-state.ts'],\n ['app/package.json', 'app/package.json']\n] as const;\n\nconst noJsxTemplateCopies = [\n ['scaffold/no-jsx/app/index.ts', 'app/index.ts'],\n ['scaffold/no-jsx/app/404.ts', 'app/404.ts'],\n ['scaffold/no-jsx/app/assets/script/index.ts', 'app/assets/script/index.ts'],\n ['scaffold/no-jsx/app/assets/script/tasks/TaskView.ts', 'app/assets/script/tasks/TaskView.ts'],\n ['scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts', 'app/assets/view/examples/tasks/TaskExample.ts'],\n ['scaffold/no-jsx/README.md', 'README.md'],\n ['tsconfig.site.no-jsx.json', 'tsconfig.json']\n] as const;\n\n/**\n * Create a White Label project without overwriting a non-empty destination.\n *\n * This is the shared implementation behind every creation interface. Adapters\n * translate their environment into these options instead of owning templates,\n * overwrite policy, or project-generation behavior themselves.\n */\nexport async function createProject({destination, fileSystem = nodeFileSystem, jsx = true}: CreateProjectOptions) {\n await assertDestinationAvailable(destination);\n const copies = jsx ? [\n ['.editorconfig', '.editorconfig'],\n ['.yarnrc.yml', '.yarnrc.yml'],\n ['pnpm-workspace.yaml', 'pnpm-workspace.yaml'],\n ['app', 'app'],\n ['app/README.md', 'README.md'],\n ['server', 'server'],\n ['scripts', 'scripts'],\n ['template-test', 'test'],\n ['tsconfig.site.json', 'tsconfig.json']\n ] as const : [\n ['.editorconfig', '.editorconfig'],\n ['.yarnrc.yml', '.yarnrc.yml'],\n ['pnpm-workspace.yaml', 'pnpm-workspace.yaml'],\n ...commonNoJsxCopies,\n ...noJsxTemplateCopies,\n ['server', 'server'],\n ['scripts', 'scripts'],\n ['template-test', 'test']\n ] as const;\n\n for (const [source, target] of copies) {\n await fileSystem.copy(path.join(packageRoot, source), path.join(destination, target));\n }\n\n await fileSystem.writeJSON(path.join(destination, 'package.json'), createSiteManifest());\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral serverless example using the Web Request/Response contract.
|
|
3
|
+
*
|
|
4
|
+
* Keep mutable White Label instances request-scoped. Cloud adapters should only
|
|
5
|
+
* translate their provider event into a Request and translate the Response back.
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleRequest(request: Request): Promise<Response>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Mediator from 'white-label-mediator';
|
|
2
|
+
import { Model } from 'white-label-model';
|
|
3
|
+
import Router from 'white-label-router';
|
|
4
|
+
import View from 'white-label-view/server';
|
|
5
|
+
function escapeHtml(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replaceAll('&', '&')
|
|
8
|
+
.replaceAll('<', '<')
|
|
9
|
+
.replaceAll('>', '>')
|
|
10
|
+
.replaceAll('"', '"')
|
|
11
|
+
.replaceAll("'", ''');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Provider-neutral serverless example using the Web Request/Response contract.
|
|
15
|
+
*
|
|
16
|
+
* Keep mutable White Label instances request-scoped. Cloud adapters should only
|
|
17
|
+
* translate their provider event into a Request and translate the Response back.
|
|
18
|
+
*/
|
|
19
|
+
export async function handleRequest(request) {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const mediator = new Mediator();
|
|
22
|
+
const model = new Model({
|
|
23
|
+
status: 404,
|
|
24
|
+
title: 'Not found',
|
|
25
|
+
message: `No route for ${url.pathname}.`
|
|
26
|
+
});
|
|
27
|
+
const router = new Router();
|
|
28
|
+
const view = new View({
|
|
29
|
+
model,
|
|
30
|
+
template(data) {
|
|
31
|
+
const state = data;
|
|
32
|
+
return `<main><h1>${escapeHtml(state.title)}</h1><p>${escapeHtml(state.message)}</p></main>`;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
router.mediator = mediator;
|
|
36
|
+
router.routes = {
|
|
37
|
+
'/health': () => model.update({
|
|
38
|
+
status: 200,
|
|
39
|
+
title: 'Healthy',
|
|
40
|
+
message: 'Serverless request handled.'
|
|
41
|
+
}),
|
|
42
|
+
'/hello': (_scope, location) => {
|
|
43
|
+
const name = String(location.data.query.name ?? 'world');
|
|
44
|
+
return model.update({
|
|
45
|
+
status: 200,
|
|
46
|
+
title: 'Hello',
|
|
47
|
+
message: `Hello ${name}.`
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
defaultRoute: () => true
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
router.navigate(`${url.pathname}${url.search}`);
|
|
54
|
+
view.initialize();
|
|
55
|
+
return new Response(`<!doctype html>${view.toString()}`, {
|
|
56
|
+
status: model.get().status,
|
|
57
|
+
headers: { 'content-type': 'text/html; charset=utf-8' }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
view.destroy();
|
|
62
|
+
router.destroy();
|
|
63
|
+
mediator.destroy();
|
|
64
|
+
model.destroy();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../server/handler.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,yBAAyB,CAAC;AAQ3C,SAAS,UAAU,CAAC,KAAa;IAC7B,OAAO,KAAK;SACP,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAgB;IAChD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACpB,MAAM,EAAE,GAAG;QACX,KAAK,EAAE,WAAW;QAClB,OAAO,EAAE,gBAAgB,GAAG,CAAC,QAAQ,GAAG;KAC3C,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC;QAClB,KAAK;QACL,QAAQ,CAAC,IAAI;YACT,MAAM,KAAK,GAAG,IAAmB,CAAC;YAClC,OAAO,aAAa,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;QACjG,CAAC;KACJ,CAAC,CAAC;IAEH,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,MAAM,CAAC,MAAM,GAAG;QACZ,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;YAC1B,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,6BAA6B;SACzC,CAAC;QACF,QAAQ,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;YAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC;YACzD,OAAO,KAAK,CAAC,MAAM,CAAC;gBAChB,MAAM,EAAE,GAAG;gBACX,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,SAAS,IAAI,GAAG;aAC5B,CAAC,CAAC;QACP,CAAC;QACD,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI;KAC3B,CAAC;IAEF,IAAI,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,IAAI,QAAQ,CAAC,kBAAkB,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE;YACrD,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM;YAC1B,OAAO,EAAE,EAAC,cAAc,EAAE,0BAA0B,EAAC;SACxD,CAAC,CAAC;IACP,CAAC;YAAS,CAAC;QACP,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnB,KAAK,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC;AACL,CAAC","sourcesContent":["import Mediator from 'white-label-mediator';\nimport {Model} from 'white-label-model';\nimport Router from 'white-label-router';\nimport View from 'white-label-view/server';\n\ntype ServerState = {\n status: number;\n title: string;\n message: string;\n};\n\nfunction escapeHtml(value: string) {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n}\n\n/**\n * Provider-neutral serverless example using the Web Request/Response contract.\n *\n * Keep mutable White Label instances request-scoped. Cloud adapters should only\n * translate their provider event into a Request and translate the Response back.\n */\nexport async function handleRequest(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const mediator = new Mediator();\n const model = new Model({\n status: 404,\n title: 'Not found',\n message: `No route for ${url.pathname}.`\n });\n const router = new Router();\n const view = new View({\n model,\n template(data) {\n const state = data as ServerState;\n return `<main><h1>${escapeHtml(state.title)}</h1><p>${escapeHtml(state.message)}</p></main>`;\n }\n });\n\n router.mediator = mediator;\n router.routes = {\n '/health': () => model.update({\n status: 200,\n title: 'Healthy',\n message: 'Serverless request handled.'\n }),\n '/hello': (_scope, location) => {\n const name = String(location.data.query.name ?? 'world');\n return model.update({\n status: 200,\n title: 'Hello',\n message: `Hello ${name}.`\n });\n },\n defaultRoute: () => true\n };\n\n try {\n router.navigate(`${url.pathname}${url.search}`);\n view.initialize();\n return new Response(`<!doctype html>${view.toString()}`, {\n status: model.get().status,\n headers: {'content-type': 'text/html; charset=utf-8'}\n });\n } finally {\n view.destroy();\n router.destroy();\n mediator.destroy();\n model.destroy();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "generator-white-label",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Framework-independent TypeScript project generator for accessible, SEO-friendly sites with progressive enhancement, optional JSX, and a provider-neutral serverless handler example.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/scaffold/index.js",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"jsdom": "30.0.1",
|
|
80
80
|
"tailwindcss": "4.3.3",
|
|
81
81
|
"typescript": "7.0.2",
|
|
82
|
-
"white-label-mediator": "
|
|
83
|
-
"white-label-model": "
|
|
84
|
-
"white-label-router": "
|
|
85
|
-
"white-label-view": "
|
|
82
|
+
"white-label-mediator": "5.0.0",
|
|
83
|
+
"white-label-model": "7.0.1",
|
|
84
|
+
"white-label-router": "6.0.0",
|
|
85
|
+
"white-label-view": "6.0.0",
|
|
86
86
|
"@babel/core": "8.0.5",
|
|
87
87
|
"@babel/eslint-parser": "8.0.5",
|
|
88
88
|
"@babel/plugin-syntax-jsx": "8.0.1",
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"cli/**/*.ts",
|
|
98
98
|
"scripts/**/*.ts",
|
|
99
99
|
"scaffold/**/*.ts",
|
|
100
|
+
"server/**/*.ts",
|
|
100
101
|
"app/**/*.ts",
|
|
101
102
|
"app/**/*.tsx"
|
|
102
103
|
],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# generator-white-label scaffold instructions
|
|
2
|
+
|
|
3
|
+
These instructions are more specific than the repository-root agent guide for files under `scaffold/`.
|
|
4
|
+
|
|
5
|
+
## Generator 9 creation contract
|
|
6
|
+
|
|
7
|
+
- `createProject()` is the canonical project-creation engine. CLI and future adapters must call it rather than reimplement scaffold copying or policy.
|
|
8
|
+
- Destination safety belongs in `createProject()`: missing and empty destinations are allowed; an existing non-empty destination must be rejected before scaffold files are copied or package metadata is written.
|
|
9
|
+
- Do not add a force/overwrite path unless the user explicitly requests and approves that product change.
|
|
10
|
+
- JSX and no-JSX scaffolds must preserve equivalent White Label architecture and core behavior. JSX is a rendering-syntax choice, not a capability tier.
|
|
11
|
+
- Keep third-party template engines application-owned. Start those integrations from the no-JSX scaffold; do not add renderer-specific adapters or runtime dependencies to the generator without an explicit requirement.
|
|
12
|
+
- Generated JSX uses the `white-label-view` trust contract: ordinary text/attributes are HTML-escaped, intrinsic `on*` handlers are not supported, and URL/CSS semantics remain application validation concerns.
|
|
13
|
+
- Keep generated serverless examples provider-neutral and request-scoped. Do not make Express, a cloud provider SDK, or mutable module-level request state a requirement.
|
|
14
|
+
- Do not hand-edit dependency lockfiles. Regenerate them with the package manager only after the coordinated runtime versions actually exist in the registry.
|
|
15
|
+
|
|
16
|
+
## Coordinated release dependency
|
|
17
|
+
|
|
18
|
+
Generator 9 targets `white-label-mediator@5`, `white-label-model@7`, `white-label-router@6`, and `white-label-view@6`. Registry installation failures for unpublished target versions are a real release gate; do not bypass them with Git references in the release lockfile.
|
package/scaffold/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @module scaffold */
|
|
2
|
-
import {cp, mkdir, writeFile} from 'node:fs/promises';
|
|
2
|
+
import {cp, mkdir, readdir, writeFile} from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import {fileURLToPath} from 'node:url';
|
|
5
5
|
|
|
@@ -27,6 +27,17 @@ const nodeFileSystem: ScaffoldFileSystem = {
|
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/** Refuse to layer a generated project over existing user files. */
|
|
31
|
+
async function assertDestinationAvailable(destination: string) {
|
|
32
|
+
try {
|
|
33
|
+
const entries = await readdir(destination);
|
|
34
|
+
if (entries.length) {throw new Error(`Destination directory must be empty: ${destination}`);}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {return;}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
/** Return the package manifest used by generated White Label projects. */
|
|
31
42
|
export function createSiteManifest() {
|
|
32
43
|
return {
|
|
@@ -43,12 +54,12 @@ export function createSiteManifest() {
|
|
|
43
54
|
allowScripts: {
|
|
44
55
|
'@parcel/watcher@2.5.1': true,
|
|
45
56
|
'esbuild@0.28.2': true,
|
|
46
|
-
'white-label-view@
|
|
57
|
+
'white-label-view@6.0.0': true
|
|
47
58
|
},
|
|
48
59
|
dependenciesMeta: {
|
|
49
60
|
'@parcel/watcher@2.5.1': {built: true},
|
|
50
61
|
'esbuild@0.28.2': {built: true},
|
|
51
|
-
'white-label-view@
|
|
62
|
+
'white-label-view@6.0.0': {built: true}
|
|
52
63
|
},
|
|
53
64
|
devDependencies: {
|
|
54
65
|
'@tailwindcss/cli': '4.3.3',
|
|
@@ -61,10 +72,10 @@ export function createSiteManifest() {
|
|
|
61
72
|
'typescript': '7.0.2'
|
|
62
73
|
},
|
|
63
74
|
dependencies: {
|
|
64
|
-
'white-label-mediator': '
|
|
65
|
-
'white-label-model': '
|
|
66
|
-
'white-label-router': '
|
|
67
|
-
'white-label-view': '
|
|
75
|
+
'white-label-mediator': '5.0.0',
|
|
76
|
+
'white-label-model': '7.0.1',
|
|
77
|
+
'white-label-router': '6.0.0',
|
|
78
|
+
'white-label-view': '6.0.0'
|
|
68
79
|
}
|
|
69
80
|
};
|
|
70
81
|
}
|
|
@@ -91,13 +102,14 @@ const noJsxTemplateCopies = [
|
|
|
91
102
|
] as const;
|
|
92
103
|
|
|
93
104
|
/**
|
|
94
|
-
* Create a White Label project.
|
|
105
|
+
* Create a White Label project without overwriting a non-empty destination.
|
|
95
106
|
*
|
|
96
107
|
* This is the shared implementation behind every creation interface. Adapters
|
|
97
|
-
* translate their environment into these options instead of owning templates
|
|
98
|
-
* project-generation behavior themselves.
|
|
108
|
+
* translate their environment into these options instead of owning templates,
|
|
109
|
+
* overwrite policy, or project-generation behavior themselves.
|
|
99
110
|
*/
|
|
100
111
|
export async function createProject({destination, fileSystem = nodeFileSystem, jsx = true}: CreateProjectOptions) {
|
|
112
|
+
await assertDestinationAvailable(destination);
|
|
101
113
|
const copies = jsx ? [
|
|
102
114
|
['.editorconfig', '.editorconfig'],
|
|
103
115
|
['.yarnrc.yml', '.yarnrc.yml'],
|
|
@@ -34,6 +34,32 @@ pnpm build
|
|
|
34
34
|
pnpm test
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
## Simple View click event
|
|
38
|
+
|
|
39
|
+
View lifecycle hooks can own a browser listener directly:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import View from 'white-label-view';
|
|
43
|
+
|
|
44
|
+
class ButtonView extends View {
|
|
45
|
+
handleClick = () => {
|
|
46
|
+
console.log('Clicked');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
addListeners() {
|
|
50
|
+
this.element.addEventListener('click', this.handleClick);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
removeListeners() {
|
|
55
|
+
this.element.removeEventListener('click', this.handleClick);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The same callback reference is used for both registration and cleanup. View calls `removeListeners()` before replacement or destruction.
|
|
62
|
+
|
|
37
63
|
## Serverless / function runtimes
|
|
38
64
|
|
|
39
65
|
`server/handler.ts` is the same provider-neutral serverless example included in the JSX scaffold. It uses Web `Request` and `Response`, request-scoped Model/View/Router/Mediator instances, and the server View entrypoint; it does not require JSX or a provider SDK.
|
|
@@ -141,7 +141,9 @@ test('all client packages cooperate through the task application without losing
|
|
|
141
141
|
|
|
142
142
|
assert.equal(normalizeTaskFilter('nope'), 'all');
|
|
143
143
|
application.destroy();
|
|
144
|
-
assert.
|
|
144
|
+
assert.deepEqual(application.model.get(), {});
|
|
145
|
+
application.mediator.dispatchEvent(new CustomEvent('task:add', {detail: 'Ignored after destroy'}));
|
|
146
|
+
assert.deepEqual(application.model.get(), {});
|
|
145
147
|
});
|
|
146
148
|
|
|
147
149
|
test('serverless handler keeps warm and concurrent requests isolated', async () => {
|