redweb 0.16.0 → 0.16.1
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/CHANGELOG.md +6 -0
- package/README.md +3 -3
- package/docs/API_EXAMPLES_VERIFICATION.md +22 -0
- package/docs/APPLICATION.md +94 -98
- package/docs/CLI.md +116 -116
- package/docs/DEVELOPMENT.md +81 -79
- package/docs/GETTING_STARTED.md +58 -58
- package/docs/LIVE_HTML.md +476 -511
- package/docs/MIGRATION.md +28 -28
- package/docs/RELEASE_TRUST.md +63 -63
- package/docs/RUNTIME_DIAGNOSTICS.md +78 -78
- package/docs/SOCKET_CONTRACTS.md +38 -38
- package/docs/generated.json +395 -326
- package/docs/guides/chatroom.md +1 -1
- package/docs/guides/jsx-without-react.md +14 -2
- package/docs/reference.json +1329 -1246
- package/docs/releases/0.16.1.json +2286 -0
- package/docs/snippets/components.tsx +24 -0
- package/docs/snippets/counter.tsx +16 -0
- package/docs/snippets/room-access.tsx +11 -12
- package/docs/snippets/site.css +2 -0
- package/docs/snippets/site.tsx +22 -0
- package/docs/topics.json +1 -1
- package/package.json +1 -1
- package/src/docs/Documentation.js +15 -8
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { action, component, defineApp, page, state } from 'redweb';
|
|
2
|
+
|
|
3
|
+
@component()
|
|
4
|
+
class Counter {
|
|
5
|
+
@state() count = 0;
|
|
6
|
+
|
|
7
|
+
@action()
|
|
8
|
+
increment() { this.count += 1; }
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
return <button rw-click="increment">Count {this.count}</button>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@page('/')
|
|
16
|
+
class CountersPage {
|
|
17
|
+
first = new Counter();
|
|
18
|
+
second = new Counter();
|
|
19
|
+
|
|
20
|
+
render() { return <main>{this.first}{this.second}</main>; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const app = defineApp({ pages: [CountersPage] });
|
|
24
|
+
app.run();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { action, defineApp, page, state } from 'redweb';
|
|
2
|
+
|
|
3
|
+
@page('/', { shared: true })
|
|
4
|
+
class CounterPage {
|
|
5
|
+
@state() count = 0;
|
|
6
|
+
|
|
7
|
+
@action()
|
|
8
|
+
increment() { this.count += 1; }
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
return <button rw-click="increment">Count {this.count}</button>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const app = defineApp({ pages: [CounterPage] });
|
|
16
|
+
app.run();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import { page,
|
|
2
|
+
import { page, defineApp, BaseHandler, SocketRoute, RedWebSocket, RedWebRequest, LivePageRequestContext } from 'redweb';
|
|
3
3
|
|
|
4
4
|
// A runnable local demonstration, not a production credential store.
|
|
5
|
-
export function createApp(port = 8181) {
|
|
5
|
+
export async function createApp(port = 8181) {
|
|
6
6
|
const token = randomBytes(32).toString('base64url');
|
|
7
7
|
let enabled = true;
|
|
8
8
|
const authenticate = (request: Pick<RedWebRequest, 'headers'>) =>
|
|
@@ -28,9 +28,9 @@ export function createApp(port = 8181) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const app =
|
|
32
|
-
|
|
33
|
-
app.
|
|
31
|
+
const app = defineApp({ pages: [Home], sockets: [Team], authenticate, port, bind: '127.0.0.1', logger: null });
|
|
32
|
+
await app.run();
|
|
33
|
+
const team = app.sockets!.routes.find(route => route instanceof Team)!;
|
|
34
34
|
return {
|
|
35
35
|
app, team, token,
|
|
36
36
|
async revoke() {
|
|
@@ -42,10 +42,9 @@ export function createApp(port = 8181) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
if (require.main === module) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
45
|
+
if (require.main === module) {
|
|
46
|
+
createApp().then(demo => {
|
|
47
|
+
console.log('Local demo: http://127.0.0.1:8181/ and ws://127.0.0.1:8181/team');
|
|
48
|
+
console.log(`Authorization: Bearer ${demo.token}`); // One fresh local-demo credential per run.
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineApp, defineSite } from 'redweb';
|
|
2
|
+
|
|
3
|
+
const site = defineSite({
|
|
4
|
+
css: 'site.css',
|
|
5
|
+
layout: content => <body>
|
|
6
|
+
<nav><a href="/">Home</a> · <a href="/about">About</a></nav>
|
|
7
|
+
<main>{content}</main>
|
|
8
|
+
</body>,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
@site.page('/', { head: { title: 'Home' } })
|
|
12
|
+
class HomePage {
|
|
13
|
+
render() { return <h1>Welcome to Redweb</h1>; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@site.page('/about', { head: { title: 'About' } })
|
|
17
|
+
class AboutPage {
|
|
18
|
+
render() { return <p>Two pages, one layout, no browser framework.</p>; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const app = defineApp({ pages: [HomePage, AboutPage] });
|
|
22
|
+
app.run();
|
package/docs/topics.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
{ "id": "getting-started", "title": "Choose a starter and build a working app", "summary": "Requirements, fit, development, tests, and production boundaries.", "source": "docs/GETTING_STARTED.md" },
|
|
3
3
|
{ "id": "application", "title": "One application, one listener", "summary": "Define pages, socket routes and application services together; run and shut down one owned HTTP/WebSocket listener.", "source": "docs/APPLICATION.md" },
|
|
4
4
|
{ "id": "guides/realtime-dashboard", "title": "Build a private realtime dashboard", "summary": "Persistent SQLite cards, account-private updates and sign-out across tabs, with explicit single-process limits.", "source": "docs/guides/realtime-dashboard.md", "recipe": { "template": "dashboard", "file": "src/cards.tsx" } },
|
|
5
|
-
{ "id": "guides/jsx-without-react", "title": "Render JSX without React", "summary": "TypeScript pages, a shared layout and external CSS, rendered on the server without browser framework code.", "source": "docs/guides/jsx-without-react.md", "recipe": { "template": "site", "file": "src/app.tsx" } },
|
|
5
|
+
{ "id": "guides/jsx-without-react", "title": "Render JSX without React", "summary": "TypeScript pages, a shared layout and external CSS, rendered on the server without browser framework code.", "source": "docs/guides/jsx-without-react.md", "codeSource": "docs/snippets/site.tsx", "recipe": { "template": "site", "file": "src/app.tsx" } },
|
|
6
6
|
{ "id": "guides/chatroom", "title": "Build a chatroom with live presence", "summary": "Reusable server-side components, validated forms and disconnect-aware presence, without custom browser socket glue.", "source": "docs/guides/chatroom.md", "recipe": { "template": "chat", "file": "src/chatroom.tsx" } },
|
|
7
7
|
{ "id": "guides/typed-websockets", "title": "Share typed WebSocket contracts", "summary": "One match route, separate join/move/resume handlers and validated client/server payloads from the same schema.", "source": "docs/guides/typed-websockets.md", "recipe": { "template": "socket", "file": "src/handlers.ts" } },
|
|
8
8
|
{ "id": "guides/http-websocket", "title": "Serve HTTP and WebSockets on one port", "summary": "An Express endpoint and raw socket route share one listener with one explicit shutdown owner.", "source": "docs/guides/http-websocket.md", "recipe": { "template": "http-ws", "file": "src/app.tsx" } },
|
package/package.json
CHANGED
|
@@ -104,21 +104,28 @@ class Documentation {
|
|
|
104
104
|
return { id: `recipes/${template}`, title: `${template[0].toUpperCase()}${template.slice(1)} starter`, summary: explanation.split('\n').find(line => line && !line.startsWith('#')), source, markdown, files };
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
recipeCode(entry) {
|
|
107
|
+
recipeCode(entry) {
|
|
108
108
|
const files = projectFiles(this.manifest.version, entry.template, this.root);
|
|
109
109
|
const file = files.find(file => file.path === entry.file);
|
|
110
110
|
if (!file) throw new Error(`Unknown documentation recipe file: ${entry.template}/${entry.file}`);
|
|
111
|
-
return normalize(file.content);
|
|
112
|
-
}
|
|
111
|
+
return normalize(file.content);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
code(entry, field) {
|
|
115
|
+
return entry.recipe ? this.recipeCode(entry.recipe) : entry.codeSource ? this.read(entry.codeSource) : entry[field];
|
|
116
|
+
}
|
|
113
117
|
|
|
114
118
|
topic(topic) {
|
|
115
|
-
let markdown = `${this.notice()}\n\n${this.links(this.read(topic.source), topic.source)}`;
|
|
119
|
+
let markdown = `${this.notice()}\n\n${this.links(this.read(topic.source), topic.source)}`;
|
|
120
|
+
markdown = markdown.replace(/<!-- source: ([\w./-]+) -->/g,
|
|
121
|
+
(_match, file) => fence(this.read(file), language(file)));
|
|
116
122
|
if (topic.recipe) {
|
|
117
123
|
const { template, file } = topic.recipe;
|
|
118
124
|
markdown += [
|
|
119
125
|
'\n## Build and run the complete application', this.setup(template),
|
|
120
|
-
`The [complete ${template} recipe](${this.basePath}/recipes/${template}.md) contains every generated file, its real acceptance tests, and deployment instructions
|
|
121
|
-
|
|
126
|
+
`The [complete ${template} recipe](${this.basePath}/recipes/${template}.md) contains every generated file, its real acceptance tests, and deployment instructions.`,
|
|
127
|
+
...(topic.codeSource ? [] : [`The source below is one of those files, not a standalone program; initialize the whole project before modifying it.`,
|
|
128
|
+
`## Source walkthrough: ${file}`, fence(this.recipeCode(topic.recipe), language(file))]),
|
|
122
129
|
].join('\n\n') + '\n';
|
|
123
130
|
}
|
|
124
131
|
return { ...topic, markdown };
|
|
@@ -128,8 +135,8 @@ class Documentation {
|
|
|
128
135
|
const pages = this.topics.map(topic => this.topic(topic));
|
|
129
136
|
pages.push(...TEMPLATES.map(template => this.recipe(template)));
|
|
130
137
|
const reference = this.reference;
|
|
131
|
-
const api = reference.api.map(section => ({ ...section, usage:
|
|
132
|
-
const examples = reference.examples.map(example => ({ ...example, code:
|
|
138
|
+
const api = reference.api.map(section => ({ ...section, usage: this.code(section, 'usage') }));
|
|
139
|
+
const examples = reference.examples.map(example => ({ ...example, code: this.code(example, 'code') }));
|
|
133
140
|
for (const example of examples) {
|
|
134
141
|
pages.push({ id: `examples/${example.id}`, title: example.title, summary: example.summary, source: 'docs/reference.json', markdown: [
|
|
135
142
|
`# ${example.title}`, this.notice(), example.summary,
|