redweb 0.13.1 → 0.13.3
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 +18 -0
- package/README.md +286 -290
- package/docs/AGENT_READY_ACCEPTANCE.md +4 -0
- package/docs/CLI.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/LIVE_HTML.md +3 -3
- package/docs/MIGRATION.md +1 -1
- package/docs/RELEASE_TRUST.md +10 -6
- package/docs/RUNTIME_DIAGNOSTICS.md +1 -1
- package/docs/SOCKET_CONTRACTS.md +2 -2
- package/docs/generated.json +2154 -2154
- package/docs/releases/0.13.2.json +2154 -0
- package/docs/releases/0.13.3.json +2154 -0
- package/examples/live-html/chatroom.js +207 -207
- package/examples/live-html/jsx-page.js +1 -1
- package/examples/live-html/jsx-page.tsx +1 -1
- package/package.json +2 -1
- package/recipes/add/artifact.test.cjs +2 -2
- package/recipes/add/live.tsx +1 -1
- package/recipes/realtime/README.md +1 -1
- package/recipes/realtime/app.test.cjs +5 -5
- package/recipes/realtime/app.tsx +1 -1
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
-
var useValue = arguments.length > 2;
|
|
4
|
-
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
-
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
-
}
|
|
7
|
-
return useValue ? value : void 0;
|
|
8
|
-
};
|
|
9
|
-
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
-
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
-
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
-
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
-
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
-
var _, done = false;
|
|
15
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
-
var context = {};
|
|
17
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
-
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
-
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
-
if (kind === "accessor") {
|
|
22
|
-
if (result === void 0) continue;
|
|
23
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
-
}
|
|
28
|
-
else if (_ = accept(result)) {
|
|
29
|
-
if (kind === "field") initializers.unshift(_);
|
|
30
|
-
else descriptor[key] = _;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
-
done = true;
|
|
35
|
-
};
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.ChatroomComponent = exports.chatInputs = void 0;
|
|
38
|
-
exports.createChatroomPage = createChatroomPage;
|
|
39
|
-
const jsx_runtime_1 = require('../../jsx-runtime');
|
|
40
|
-
const redweb_1 = require('../..');
|
|
41
|
-
const zod_1 = require("zod");
|
|
42
|
-
const MAX_VISIBLE_MEMBERS = 100;
|
|
43
|
-
const visibleText = (maximum) => zod_1.z.string()
|
|
44
|
-
.transform(value => value.normalize('NFKC').trim())
|
|
45
|
-
.pipe(zod_1.z.string().min(1).max(maximum).regex(/^[^\p{Cc}\p{Cf}]+$/u));
|
|
46
|
-
exports.chatInputs = {
|
|
47
|
-
join: zod_1.z.object({ name: visibleText(40) }).strict(),
|
|
48
|
-
send: zod_1.z.object({ message: visibleText(500) }).strict(),
|
|
49
|
-
};
|
|
50
|
-
class ChatRoom {
|
|
51
|
-
history = [];
|
|
52
|
-
nextMessageId = 0;
|
|
53
|
-
participants = new Set();
|
|
54
|
-
online = new Set();
|
|
55
|
-
join(participant) {
|
|
56
|
-
const name = participant.displayName.toLocaleLowerCase();
|
|
57
|
-
if ([...this.participants].some(member => member !== participant && member.displayName.toLocaleLowerCase() === name))
|
|
58
|
-
return false;
|
|
59
|
-
this.participants.add(participant);
|
|
60
|
-
this.online.add(participant);
|
|
61
|
-
participant.updateMessages(this.history);
|
|
62
|
-
this.publishPresence();
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
disconnect(participant) {
|
|
66
|
-
if (this.online.delete(participant))
|
|
67
|
-
this.publishPresence();
|
|
68
|
-
}
|
|
69
|
-
leave(participant) {
|
|
70
|
-
this.online.delete(participant);
|
|
71
|
-
if (this.participants.delete(participant))
|
|
72
|
-
this.publishPresence();
|
|
73
|
-
}
|
|
74
|
-
send(participant, text) {
|
|
75
|
-
if (!this.online.has(participant))
|
|
76
|
-
return false;
|
|
77
|
-
this.history = [...this.history, { id: ++this.nextMessageId, sender: participant.displayName, text }].slice(-100);
|
|
78
|
-
for (const member of this.participants)
|
|
79
|
-
member.updateMessages(this.history);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
publishPresence() {
|
|
83
|
-
const members = [...this.online].map(participant => participant.displayName);
|
|
84
|
-
for (const participant of this.participants)
|
|
85
|
-
participant.updatePresence(members);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
let ChatroomComponent = (() => {
|
|
89
|
-
let _classDecorators = [(0, redweb_1.component)()];
|
|
90
|
-
let _classDescriptor;
|
|
91
|
-
let _classExtraInitializers = [];
|
|
92
|
-
let _classThis;
|
|
93
|
-
let _instanceExtraInitializers = [];
|
|
94
|
-
let _displayName_decorators;
|
|
95
|
-
let _displayName_initializers = [];
|
|
96
|
-
let _displayName_extraInitializers = [];
|
|
97
|
-
let _feedback_decorators;
|
|
98
|
-
let _feedback_initializers = [];
|
|
99
|
-
let _feedback_extraInitializers = [];
|
|
100
|
-
let _messages_decorators;
|
|
101
|
-
let _messages_initializers = [];
|
|
102
|
-
let _messages_extraInitializers = [];
|
|
103
|
-
let _members_decorators;
|
|
104
|
-
let _members_initializers = [];
|
|
105
|
-
let _members_extraInitializers = [];
|
|
106
|
-
let _join_decorators;
|
|
107
|
-
let _send_decorators;
|
|
108
|
-
let _leave_decorators;
|
|
109
|
-
var ChatroomComponent = class {
|
|
110
|
-
static { _classThis = this; }
|
|
111
|
-
static {
|
|
112
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
113
|
-
_displayName_decorators = [(0, redweb_1.state)()];
|
|
114
|
-
_feedback_decorators = [(0, redweb_1.state)()];
|
|
115
|
-
_messages_decorators = [(0, redweb_1.state)()];
|
|
116
|
-
_members_decorators = [(0, redweb_1.state)()];
|
|
117
|
-
_join_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.join })];
|
|
118
|
-
_send_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.send })];
|
|
119
|
-
_leave_decorators = [(0, redweb_1.action)()];
|
|
120
|
-
__esDecorate(this, null, _join_decorators, { kind: "method", name: "join", static: false, private: false, access: { has: obj => "join" in obj, get: obj => obj.join }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
121
|
-
__esDecorate(this, null, _send_decorators, { kind: "method", name: "send", static: false, private: false, access: { has: obj => "send" in obj, get: obj => obj.send }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
122
|
-
__esDecorate(this, null, _leave_decorators, { kind: "method", name: "leave", static: false, private: false, access: { has: obj => "leave" in obj, get: obj => obj.leave }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
123
|
-
__esDecorate(null, null, _displayName_decorators, { kind: "field", name: "displayName", static: false, private: false, access: { has: obj => "displayName" in obj, get: obj => obj.displayName, set: (obj, value) => { obj.displayName = value; } }, metadata: _metadata }, _displayName_initializers, _displayName_extraInitializers);
|
|
124
|
-
__esDecorate(null, null, _feedback_decorators, { kind: "field", name: "feedback", static: false, private: false, access: { has: obj => "feedback" in obj, get: obj => obj.feedback, set: (obj, value) => { obj.feedback = value; } }, metadata: _metadata }, _feedback_initializers, _feedback_extraInitializers);
|
|
125
|
-
__esDecorate(null, null, _messages_decorators, { kind: "field", name: "messages", static: false, private: false, access: { has: obj => "messages" in obj, get: obj => obj.messages, set: (obj, value) => { obj.messages = value; } }, metadata: _metadata }, _messages_initializers, _messages_extraInitializers);
|
|
126
|
-
__esDecorate(null, null, _members_decorators, { kind: "field", name: "members", static: false, private: false, access: { has: obj => "members" in obj, get: obj => obj.members, set: (obj, value) => { obj.members = value; } }, metadata: _metadata }, _members_initializers, _members_extraInitializers);
|
|
127
|
-
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
128
|
-
ChatroomComponent = _classThis = _classDescriptor.value;
|
|
129
|
-
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
130
|
-
__runInitializers(_classThis, _classExtraInitializers);
|
|
131
|
-
}
|
|
132
|
-
room = __runInitializers(this, _instanceExtraInitializers);
|
|
133
|
-
displayName = __runInitializers(this, _displayName_initializers, '');
|
|
134
|
-
feedback = (__runInitializers(this, _displayName_extraInitializers), __runInitializers(this, _feedback_initializers, ''));
|
|
135
|
-
messages = (__runInitializers(this, _feedback_extraInitializers), __runInitializers(this, _messages_initializers, []));
|
|
136
|
-
members = (__runInitializers(this, _messages_extraInitializers), __runInitializers(this, _members_initializers, []));
|
|
137
|
-
constructor(room) {
|
|
138
|
-
__runInitializers(this, _members_extraInitializers);
|
|
139
|
-
this.room = room;
|
|
140
|
-
}
|
|
141
|
-
connected() { if (this.displayName)
|
|
142
|
-
this.room.join(this); }
|
|
143
|
-
disconnected() { this.room.disconnect(this); }
|
|
144
|
-
disposed() { this.room.leave(this); }
|
|
145
|
-
join({ name }) {
|
|
146
|
-
if (this.displayName)
|
|
147
|
-
return false;
|
|
148
|
-
this.displayName = name;
|
|
149
|
-
if (!this.room.join(this)) {
|
|
150
|
-
this.displayName = '';
|
|
151
|
-
this.feedback = 'That display name is already in use.';
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
this.feedback = '';
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
send({ message }) {
|
|
158
|
-
return this.room.send(this, message);
|
|
159
|
-
}
|
|
160
|
-
leave() {
|
|
161
|
-
this.room.leave(this);
|
|
162
|
-
this.displayName = '';
|
|
163
|
-
this.feedback = '';
|
|
164
|
-
this.messages = [];
|
|
165
|
-
this.members = [];
|
|
166
|
-
}
|
|
167
|
-
updateMessages(messages) { this.messages = messages; }
|
|
168
|
-
updatePresence(members) { this.members = members; }
|
|
169
|
-
render() {
|
|
170
|
-
return (0, jsx_runtime_1.jsx)("section", { class: "chatroom", children: this.displayName ? this.roomScreen() : this.joinScreen() });
|
|
171
|
-
}
|
|
172
|
-
joinScreen() {
|
|
173
|
-
return ((0, jsx_runtime_1.jsxs)("section", { class: "join-panel", children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Live room" }), (0, jsx_runtime_1.jsx)("h1", { children: "Join the chatroom" }), (0, jsx_runtime_1.jsx)("p", { children: "Choose a name once, then chat in realtime with everyone currently in the room." }), this.feedback && (0, jsx_runtime_1.jsx)("p", { class: "form-error", role: "alert", children: this.feedback }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "join", class: "join-form", children: [(0, jsx_runtime_1.jsx)("label", { for: "display-name", children: "Display name" }), (0, jsx_runtime_1.jsxs)("div", { class: "input-row", children: [(0, jsx_runtime_1.jsx)("input", { id: "display-name", name: "name", maxlength: "40", autocomplete: "nickname", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Join room" })] })] })] }));
|
|
174
|
-
}
|
|
175
|
-
roomScreen() {
|
|
176
|
-
const remaining = this.members.length - MAX_VISIBLE_MEMBERS;
|
|
177
|
-
return ((0, jsx_runtime_1.jsxs)("div", { class: "room-layout", children: [(0, jsx_runtime_1.jsxs)("section", { class: "conversation", children: [(0, jsx_runtime_1.jsxs)("header", { class: "room-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Connected as" }), (0, jsx_runtime_1.jsx)("h1", { children: this.displayName })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", class: "quiet-button", "rw-click": "leave", children: "Leave" })] }), (0, jsx_runtime_1.jsx)("ol", { class: "message-list", "aria-live": "polite", children: this.messages.length ? this.messages.map(entry => ((0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: entry.sender }), (0, jsx_runtime_1.jsx)("p", { children: entry.text })] }, entry.id))) : (0, jsx_runtime_1.jsx)("li", { class: "empty-message", children: "No messages yet. Say hello." }) }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "send", class: "composer", children: [(0, jsx_runtime_1.jsx)("label", { class: "sr-only", for: "chat-message", children: "Message" }), (0, jsx_runtime_1.jsx)("input", { id: "chat-message", name: "message", maxlength: "500", autocomplete: "off", placeholder: "Message the room\u2026", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Send" })] })] }), (0, jsx_runtime_1.jsxs)("aside", { class: "presence", "aria-label": "People in the room", children: [(0, jsx_runtime_1.jsxs)("p", { class: "eyebrow", children: ["Online \u00B7 ", this.members.length] }), (0, jsx_runtime_1.jsxs)("ul", { children: [this.members.slice(0, MAX_VISIBLE_MEMBERS).map(member => (0, jsx_runtime_1.jsx)("li", { children: member }, member)), remaining > 0 && (0, jsx_runtime_1.jsxs)("li", { class: "more-members", children: ["+", remaining, " more"] })] })] })] }));
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
return ChatroomComponent = _classThis;
|
|
181
|
-
})();
|
|
182
|
-
exports.ChatroomComponent = ChatroomComponent;
|
|
183
|
-
function createChatroomPage() {
|
|
184
|
-
const room = new ChatRoom();
|
|
185
|
-
let ChatroomPage = (() => {
|
|
186
|
-
let _classDecorators = [(0, redweb_1.page)('/', { css: 'chatroom.css' })];
|
|
187
|
-
let _classDescriptor;
|
|
188
|
-
let _classExtraInitializers = [];
|
|
189
|
-
let _classThis;
|
|
190
|
-
var ChatroomPage = class {
|
|
191
|
-
static { _classThis = this; }
|
|
192
|
-
static {
|
|
193
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
194
|
-
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
195
|
-
ChatroomPage = _classThis = _classDescriptor.value;
|
|
196
|
-
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
197
|
-
__runInitializers(_classThis, _classExtraInitializers);
|
|
198
|
-
}
|
|
199
|
-
chat = new ChatroomComponent(room);
|
|
200
|
-
render() { return (0, jsx_runtime_1.jsx)("main", { children: this.chat }); }
|
|
201
|
-
};
|
|
202
|
-
return ChatroomPage = _classThis;
|
|
203
|
-
})();
|
|
204
|
-
return ChatroomPage;
|
|
205
|
-
}
|
|
206
|
-
if (require.main === module)
|
|
207
|
-
(0, redweb_1.start)(createChatroomPage(), { port: 8080 });
|
|
1
|
+
"use strict";
|
|
2
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
+
var useValue = arguments.length > 2;
|
|
4
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
+
}
|
|
7
|
+
return useValue ? value : void 0;
|
|
8
|
+
};
|
|
9
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
+
var _, done = false;
|
|
15
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
+
var context = {};
|
|
17
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
+
if (kind === "accessor") {
|
|
22
|
+
if (result === void 0) continue;
|
|
23
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
+
}
|
|
28
|
+
else if (_ = accept(result)) {
|
|
29
|
+
if (kind === "field") initializers.unshift(_);
|
|
30
|
+
else descriptor[key] = _;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
+
done = true;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ChatroomComponent = exports.chatInputs = void 0;
|
|
38
|
+
exports.createChatroomPage = createChatroomPage;
|
|
39
|
+
const jsx_runtime_1 = require('../../jsx-runtime');
|
|
40
|
+
const redweb_1 = require('../..');
|
|
41
|
+
const zod_1 = require("zod");
|
|
42
|
+
const MAX_VISIBLE_MEMBERS = 100;
|
|
43
|
+
const visibleText = (maximum) => zod_1.z.string()
|
|
44
|
+
.transform(value => value.normalize('NFKC').trim())
|
|
45
|
+
.pipe(zod_1.z.string().min(1).max(maximum).regex(/^[^\p{Cc}\p{Cf}]+$/u));
|
|
46
|
+
exports.chatInputs = {
|
|
47
|
+
join: zod_1.z.object({ name: visibleText(40) }).strict(),
|
|
48
|
+
send: zod_1.z.object({ message: visibleText(500) }).strict(),
|
|
49
|
+
};
|
|
50
|
+
class ChatRoom {
|
|
51
|
+
history = [];
|
|
52
|
+
nextMessageId = 0;
|
|
53
|
+
participants = new Set();
|
|
54
|
+
online = new Set();
|
|
55
|
+
join(participant) {
|
|
56
|
+
const name = participant.displayName.toLocaleLowerCase();
|
|
57
|
+
if ([...this.participants].some(member => member !== participant && member.displayName.toLocaleLowerCase() === name))
|
|
58
|
+
return false;
|
|
59
|
+
this.participants.add(participant);
|
|
60
|
+
this.online.add(participant);
|
|
61
|
+
participant.updateMessages(this.history);
|
|
62
|
+
this.publishPresence();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
disconnect(participant) {
|
|
66
|
+
if (this.online.delete(participant))
|
|
67
|
+
this.publishPresence();
|
|
68
|
+
}
|
|
69
|
+
leave(participant) {
|
|
70
|
+
this.online.delete(participant);
|
|
71
|
+
if (this.participants.delete(participant))
|
|
72
|
+
this.publishPresence();
|
|
73
|
+
}
|
|
74
|
+
send(participant, text) {
|
|
75
|
+
if (!this.online.has(participant))
|
|
76
|
+
return false;
|
|
77
|
+
this.history = [...this.history, { id: ++this.nextMessageId, sender: participant.displayName, text }].slice(-100);
|
|
78
|
+
for (const member of this.participants)
|
|
79
|
+
member.updateMessages(this.history);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
publishPresence() {
|
|
83
|
+
const members = [...this.online].map(participant => participant.displayName);
|
|
84
|
+
for (const participant of this.participants)
|
|
85
|
+
participant.updatePresence(members);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let ChatroomComponent = (() => {
|
|
89
|
+
let _classDecorators = [(0, redweb_1.component)()];
|
|
90
|
+
let _classDescriptor;
|
|
91
|
+
let _classExtraInitializers = [];
|
|
92
|
+
let _classThis;
|
|
93
|
+
let _instanceExtraInitializers = [];
|
|
94
|
+
let _displayName_decorators;
|
|
95
|
+
let _displayName_initializers = [];
|
|
96
|
+
let _displayName_extraInitializers = [];
|
|
97
|
+
let _feedback_decorators;
|
|
98
|
+
let _feedback_initializers = [];
|
|
99
|
+
let _feedback_extraInitializers = [];
|
|
100
|
+
let _messages_decorators;
|
|
101
|
+
let _messages_initializers = [];
|
|
102
|
+
let _messages_extraInitializers = [];
|
|
103
|
+
let _members_decorators;
|
|
104
|
+
let _members_initializers = [];
|
|
105
|
+
let _members_extraInitializers = [];
|
|
106
|
+
let _join_decorators;
|
|
107
|
+
let _send_decorators;
|
|
108
|
+
let _leave_decorators;
|
|
109
|
+
var ChatroomComponent = class {
|
|
110
|
+
static { _classThis = this; }
|
|
111
|
+
static {
|
|
112
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
113
|
+
_displayName_decorators = [(0, redweb_1.state)()];
|
|
114
|
+
_feedback_decorators = [(0, redweb_1.state)()];
|
|
115
|
+
_messages_decorators = [(0, redweb_1.state)()];
|
|
116
|
+
_members_decorators = [(0, redweb_1.state)()];
|
|
117
|
+
_join_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.join })];
|
|
118
|
+
_send_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.send })];
|
|
119
|
+
_leave_decorators = [(0, redweb_1.action)()];
|
|
120
|
+
__esDecorate(this, null, _join_decorators, { kind: "method", name: "join", static: false, private: false, access: { has: obj => "join" in obj, get: obj => obj.join }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
121
|
+
__esDecorate(this, null, _send_decorators, { kind: "method", name: "send", static: false, private: false, access: { has: obj => "send" in obj, get: obj => obj.send }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
122
|
+
__esDecorate(this, null, _leave_decorators, { kind: "method", name: "leave", static: false, private: false, access: { has: obj => "leave" in obj, get: obj => obj.leave }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
123
|
+
__esDecorate(null, null, _displayName_decorators, { kind: "field", name: "displayName", static: false, private: false, access: { has: obj => "displayName" in obj, get: obj => obj.displayName, set: (obj, value) => { obj.displayName = value; } }, metadata: _metadata }, _displayName_initializers, _displayName_extraInitializers);
|
|
124
|
+
__esDecorate(null, null, _feedback_decorators, { kind: "field", name: "feedback", static: false, private: false, access: { has: obj => "feedback" in obj, get: obj => obj.feedback, set: (obj, value) => { obj.feedback = value; } }, metadata: _metadata }, _feedback_initializers, _feedback_extraInitializers);
|
|
125
|
+
__esDecorate(null, null, _messages_decorators, { kind: "field", name: "messages", static: false, private: false, access: { has: obj => "messages" in obj, get: obj => obj.messages, set: (obj, value) => { obj.messages = value; } }, metadata: _metadata }, _messages_initializers, _messages_extraInitializers);
|
|
126
|
+
__esDecorate(null, null, _members_decorators, { kind: "field", name: "members", static: false, private: false, access: { has: obj => "members" in obj, get: obj => obj.members, set: (obj, value) => { obj.members = value; } }, metadata: _metadata }, _members_initializers, _members_extraInitializers);
|
|
127
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
128
|
+
ChatroomComponent = _classThis = _classDescriptor.value;
|
|
129
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
130
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
131
|
+
}
|
|
132
|
+
room = __runInitializers(this, _instanceExtraInitializers);
|
|
133
|
+
displayName = __runInitializers(this, _displayName_initializers, '');
|
|
134
|
+
feedback = (__runInitializers(this, _displayName_extraInitializers), __runInitializers(this, _feedback_initializers, ''));
|
|
135
|
+
messages = (__runInitializers(this, _feedback_extraInitializers), __runInitializers(this, _messages_initializers, []));
|
|
136
|
+
members = (__runInitializers(this, _messages_extraInitializers), __runInitializers(this, _members_initializers, []));
|
|
137
|
+
constructor(room) {
|
|
138
|
+
__runInitializers(this, _members_extraInitializers);
|
|
139
|
+
this.room = room;
|
|
140
|
+
}
|
|
141
|
+
connected() { if (this.displayName)
|
|
142
|
+
this.room.join(this); }
|
|
143
|
+
disconnected() { this.room.disconnect(this); }
|
|
144
|
+
disposed() { this.room.leave(this); }
|
|
145
|
+
join({ name }) {
|
|
146
|
+
if (this.displayName)
|
|
147
|
+
return false;
|
|
148
|
+
this.displayName = name;
|
|
149
|
+
if (!this.room.join(this)) {
|
|
150
|
+
this.displayName = '';
|
|
151
|
+
this.feedback = 'That display name is already in use.';
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
this.feedback = '';
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
send({ message }) {
|
|
158
|
+
return this.room.send(this, message);
|
|
159
|
+
}
|
|
160
|
+
leave() {
|
|
161
|
+
this.room.leave(this);
|
|
162
|
+
this.displayName = '';
|
|
163
|
+
this.feedback = '';
|
|
164
|
+
this.messages = [];
|
|
165
|
+
this.members = [];
|
|
166
|
+
}
|
|
167
|
+
updateMessages(messages) { this.messages = messages; }
|
|
168
|
+
updatePresence(members) { this.members = members; }
|
|
169
|
+
render() {
|
|
170
|
+
return (0, jsx_runtime_1.jsx)("section", { class: "chatroom", children: this.displayName ? this.roomScreen() : this.joinScreen() });
|
|
171
|
+
}
|
|
172
|
+
joinScreen() {
|
|
173
|
+
return ((0, jsx_runtime_1.jsxs)("section", { class: "join-panel", children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Live room" }), (0, jsx_runtime_1.jsx)("h1", { children: "Join the chatroom" }), (0, jsx_runtime_1.jsx)("p", { children: "Choose a name once, then chat in realtime with everyone currently in the room." }), this.feedback && (0, jsx_runtime_1.jsx)("p", { class: "form-error", role: "alert", children: this.feedback }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "join", class: "join-form", children: [(0, jsx_runtime_1.jsx)("label", { for: "display-name", children: "Display name" }), (0, jsx_runtime_1.jsxs)("div", { class: "input-row", children: [(0, jsx_runtime_1.jsx)("input", { id: "display-name", name: "name", maxlength: "40", autocomplete: "nickname", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Join room" })] })] })] }));
|
|
174
|
+
}
|
|
175
|
+
roomScreen() {
|
|
176
|
+
const remaining = this.members.length - MAX_VISIBLE_MEMBERS;
|
|
177
|
+
return ((0, jsx_runtime_1.jsxs)("div", { class: "room-layout", children: [(0, jsx_runtime_1.jsxs)("section", { class: "conversation", children: [(0, jsx_runtime_1.jsxs)("header", { class: "room-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Connected as" }), (0, jsx_runtime_1.jsx)("h1", { children: this.displayName })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", class: "quiet-button", "rw-click": "leave", children: "Leave" })] }), (0, jsx_runtime_1.jsx)("ol", { class: "message-list", "aria-live": "polite", children: this.messages.length ? this.messages.map(entry => ((0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: entry.sender }), (0, jsx_runtime_1.jsx)("p", { children: entry.text })] }, entry.id))) : (0, jsx_runtime_1.jsx)("li", { class: "empty-message", children: "No messages yet. Say hello." }) }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "send", class: "composer", children: [(0, jsx_runtime_1.jsx)("label", { class: "sr-only", for: "chat-message", children: "Message" }), (0, jsx_runtime_1.jsx)("input", { id: "chat-message", name: "message", maxlength: "500", autocomplete: "off", placeholder: "Message the room\u2026", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Send" })] })] }), (0, jsx_runtime_1.jsxs)("aside", { class: "presence", "aria-label": "People in the room", children: [(0, jsx_runtime_1.jsxs)("p", { class: "eyebrow", children: ["Online \u00B7 ", this.members.length] }), (0, jsx_runtime_1.jsxs)("ul", { children: [this.members.slice(0, MAX_VISIBLE_MEMBERS).map(member => (0, jsx_runtime_1.jsx)("li", { children: member }, member)), remaining > 0 && (0, jsx_runtime_1.jsxs)("li", { class: "more-members", children: ["+", remaining, " more"] })] })] })] }));
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return ChatroomComponent = _classThis;
|
|
181
|
+
})();
|
|
182
|
+
exports.ChatroomComponent = ChatroomComponent;
|
|
183
|
+
function createChatroomPage() {
|
|
184
|
+
const room = new ChatRoom();
|
|
185
|
+
let ChatroomPage = (() => {
|
|
186
|
+
let _classDecorators = [(0, redweb_1.page)('/', { css: 'chatroom.css' })];
|
|
187
|
+
let _classDescriptor;
|
|
188
|
+
let _classExtraInitializers = [];
|
|
189
|
+
let _classThis;
|
|
190
|
+
var ChatroomPage = class {
|
|
191
|
+
static { _classThis = this; }
|
|
192
|
+
static {
|
|
193
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
194
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
195
|
+
ChatroomPage = _classThis = _classDescriptor.value;
|
|
196
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
197
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
198
|
+
}
|
|
199
|
+
chat = new ChatroomComponent(room);
|
|
200
|
+
render() { return (0, jsx_runtime_1.jsx)("main", { children: this.chat }); }
|
|
201
|
+
};
|
|
202
|
+
return ChatroomPage = _classThis;
|
|
203
|
+
})();
|
|
204
|
+
return ChatroomPage;
|
|
205
|
+
}
|
|
206
|
+
if (require.main === module)
|
|
207
|
+
(0, redweb_1.start)(createChatroomPage(), { port: 8080 });
|
|
@@ -67,7 +67,7 @@ let JsxPage = (() => {
|
|
|
67
67
|
this.count += 1;
|
|
68
68
|
}
|
|
69
69
|
render() {
|
|
70
|
-
return ((0, jsx_runtime_1.jsxs)("main", { class: "page-shell", children: [(0, jsx_runtime_1.jsx)("h1", { children: "Redweb JSX" }), (0, jsx_runtime_1.jsxs)(Card, { title: "Server rendered", children: [(0, jsx_runtime_1.jsx)("p", { children: "Plain TSX, escaped by default, with no browser framework." }), (0, jsx_runtime_1.jsxs)("button", { type: "button", "rw-click": "increment", children: ["Count ",
|
|
70
|
+
return ((0, jsx_runtime_1.jsxs)("main", { class: "page-shell", children: [(0, jsx_runtime_1.jsx)("h1", { children: "Redweb JSX" }), (0, jsx_runtime_1.jsxs)(Card, { title: "Server rendered", children: [(0, jsx_runtime_1.jsx)("p", { children: "Plain TSX, escaped by default, with no browser framework." }), (0, jsx_runtime_1.jsxs)("button", { type: "button", "rw-click": "increment", children: ["Count ", this.count] })] })] }));
|
|
71
71
|
}
|
|
72
72
|
constructor() {
|
|
73
73
|
super(...arguments);
|
|
@@ -30,7 +30,7 @@ export class JsxPage extends LivePage {
|
|
|
30
30
|
<Card title="Server rendered">
|
|
31
31
|
<p>Plain TSX, escaped by default, with no browser framework.</p>
|
|
32
32
|
<button type="button" rw-click="increment">
|
|
33
|
-
Count
|
|
33
|
+
Count {this.count}
|
|
34
34
|
</button>
|
|
35
35
|
</Card>
|
|
36
36
|
</main>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redweb",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "A small Node.js foundation for HTTP, WebSockets, multiplayer services, and server-rendered HTML",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"scripts": {
|
|
45
45
|
"pretest": "node scripts/build-live-html-examples.js --check && node scripts/generate-protocol-types.js --check && node scripts/generate-docs.js --check && tsc -p tests/types/tsconfig.json && tsc -p tests/types/tsconfig.jsxdev.json && tsc -p tests/types/tsconfig.standard.json",
|
|
46
46
|
"prepack": "node scripts/build-live-html-examples.js --check && node scripts/generate-docs.js --check",
|
|
47
|
+
"prepublishOnly": "node scripts/generate-docs.js --release-check",
|
|
47
48
|
"generate:docs": "node scripts/generate-docs.js",
|
|
48
49
|
"verify:docs:mcp": "npm --prefix integrations/docs-mcp test",
|
|
49
50
|
"verify:cli": "c8 --all --src=bin --include=bin/redweb.js --reporter=text --reporter=json --reports-dir=coverage/cli-entrypoint --check-coverage --lines=100 --branches=100 --functions=100 --statements=100 node node_modules/jest/bin/jest.js tests/integration/init-cli.integration.test.js tests/integration/add-cli.integration.test.js --testNamePattern=\"redweb init CLI integration|human and subprocess CLI\" --runInBand --coverage=false",
|
|
@@ -32,7 +32,7 @@ test('__NAME__ generated __KIND__ works over the network', { timeout: 10000 }, a
|
|
|
32
32
|
const response = await fetch(`${origin}${'__KIND__' === 'page' ? '/__NAME__' : '/'}`);
|
|
33
33
|
assert.equal(response.status, 200);
|
|
34
34
|
const document = await response.text();
|
|
35
|
-
assert.match(document,
|
|
35
|
+
assert.match(document, /Count 0/);
|
|
36
36
|
config = JSON.parse(document.match(/id="__redweb_page">([^<]+)</)[1]);
|
|
37
37
|
}
|
|
38
38
|
const endpoint = config
|
|
@@ -49,7 +49,7 @@ test('__NAME__ generated __KIND__ works over the network', { timeout: 10000 }, a
|
|
|
49
49
|
} }));
|
|
50
50
|
} else await artifact.contract.client(socket).send('ping', { text: 'Hello' }, { requestId: 'probe' });
|
|
51
51
|
const success = message => config
|
|
52
|
-
? message.type === 'redweb:patch' && message.payload.patches.some(patch => patch.html?.includes('
|
|
52
|
+
? message.type === 'redweb:patch' && message.payload.patches.some(patch => patch.html?.includes('Count 1'))
|
|
53
53
|
: message.type === 'pong' && message.payload.text === 'Hello' && message.requestId === 'probe';
|
|
54
54
|
const deadline = Date.now() + 3000;
|
|
55
55
|
while (!received.some(success) && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 10));
|
package/recipes/add/live.tsx
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
The browser button invokes only the decorated `increment` action; it does not supply the new count.
|
|
5
5
|
Open two tabs to check the broadcast. State is in memory and resets when the server restarts.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`{this.count}` updates automatically because the page reads decorated state during rendering; no wrapper element is required.
|
|
8
8
|
No repeated binding name or browser-side state is needed. State changes are assignment-driven; render methods must not modify state.
|
|
@@ -6,10 +6,10 @@ test('one server action updates both visitors', { timeout: 10000 }, async t => {
|
|
|
6
6
|
const origin = await listen(t);
|
|
7
7
|
const first = await live(t, origin);
|
|
8
8
|
const second = await live(t, origin);
|
|
9
|
-
await first.patch(patch => patch.html.includes('
|
|
10
|
-
await second.patch(patch => patch.html.includes('
|
|
9
|
+
await first.patch(patch => patch.html.includes('Count 0'));
|
|
10
|
+
await second.patch(patch => patch.html.includes('Count 0'));
|
|
11
11
|
first.action('increment');
|
|
12
|
-
await first.patch(patch => patch.html.includes('
|
|
13
|
-
await second.patch(patch => patch.html.includes('
|
|
14
|
-
assert.match(await (await fetch(origin)).text(),
|
|
12
|
+
await first.patch(patch => patch.html.includes('Count 1'));
|
|
13
|
+
await second.patch(patch => patch.html.includes('Count 1'));
|
|
14
|
+
assert.match(await (await fetch(origin)).text(), /Count 1/);
|
|
15
15
|
});
|
package/recipes/realtime/app.tsx
CHANGED