create-frootjs 1.0.9 → 1.0.11
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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/templates/fullstack.d.ts +2 -2
- package/dist/templates/fullstack.d.ts.map +1 -1
- package/dist/templates/fullstack.js +193 -132
- package/dist/templates/fullstack.js.map +1 -1
- package/dist/templates/minimal.js +54 -54
- package/dist/templates/modular.js +261 -261
- package/dist/templates/mvc.js +196 -196
- package/dist/templates/openapi.js +168 -168
- package/dist/templates/streaming.js +176 -176
- package/dist/templates/websocket.js +163 -163
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@ export const websocketTemplate = {
|
|
|
19
19
|
start: 'node dist/server.js',
|
|
20
20
|
},
|
|
21
21
|
dependencies: {
|
|
22
|
-
frootjs: '^1.0.
|
|
22
|
+
frootjs: '^1.0.5',
|
|
23
23
|
},
|
|
24
24
|
devDependencies: {
|
|
25
25
|
'@types/node': '^22.10.0',
|
|
@@ -29,169 +29,169 @@ export const websocketTemplate = {
|
|
|
29
29
|
}, null, 2) + '\n',
|
|
30
30
|
'tsconfig.json': COMMON_TSCONFIG,
|
|
31
31
|
'.gitignore': COMMON_GITIGNORE,
|
|
32
|
-
'README.md': `# ${projectName}
|
|
33
|
-
|
|
34
|
-
Froot application built with the **WebSocket & Real-Time Pub/Sub** pattern.
|
|
35
|
-
|
|
36
|
-
## Features
|
|
37
|
-
|
|
38
|
-
- **Universal WebSockets**: Powered by \`frootjs/ws\` (\`upgradeWebSocket\`)
|
|
39
|
-
- **Pub/Sub Room Hub**: Multi-room broadcast hub with automatic disconnect cleanup (\`createHub\`)
|
|
40
|
-
- **Interactive Chat Client**: Browser interface for connecting, joining rooms, and broadcasting messages
|
|
41
|
-
|
|
42
|
-
## Getting Started
|
|
43
|
-
|
|
44
|
-
\`\`\`bash
|
|
45
|
-
# 1. Install dependencies
|
|
46
|
-
pnpm install
|
|
47
|
-
|
|
48
|
-
# 2. Run in development mode
|
|
49
|
-
pnpm run dev
|
|
50
|
-
|
|
51
|
-
# 3. Open in browser
|
|
52
|
-
http://localhost:3000
|
|
53
|
-
\`\`\`
|
|
32
|
+
'README.md': `# ${projectName}
|
|
33
|
+
|
|
34
|
+
Froot application built with the **WebSocket & Real-Time Pub/Sub** pattern.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Universal WebSockets**: Powered by \`frootjs/ws\` (\`upgradeWebSocket\`)
|
|
39
|
+
- **Pub/Sub Room Hub**: Multi-room broadcast hub with automatic disconnect cleanup (\`createHub\`)
|
|
40
|
+
- **Interactive Chat Client**: Browser interface for connecting, joining rooms, and broadcasting messages
|
|
41
|
+
|
|
42
|
+
## Getting Started
|
|
43
|
+
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
# 1. Install dependencies
|
|
46
|
+
pnpm install
|
|
47
|
+
|
|
48
|
+
# 2. Run in development mode
|
|
49
|
+
pnpm run dev
|
|
50
|
+
|
|
51
|
+
# 3. Open in browser
|
|
52
|
+
http://localhost:3000
|
|
53
|
+
\`\`\`
|
|
54
54
|
`,
|
|
55
|
-
'src/server.ts': `import { Froot } from 'frootjs';
|
|
56
|
-
import { cors } from 'frootjs/cors';
|
|
57
|
-
import { upgradeWebSocket, createHub } from 'frootjs/ws';
|
|
58
|
-
|
|
59
|
-
export const app = new Froot();
|
|
60
|
-
const hub = createHub();
|
|
61
|
-
|
|
62
|
-
app.use(cors());
|
|
63
|
-
|
|
64
|
-
// Web UI Chat Client
|
|
65
|
-
app.get('/', (c) => {
|
|
66
|
-
return c.html(\`<!DOCTYPE html>
|
|
67
|
-
<html lang="en">
|
|
68
|
-
<head>
|
|
69
|
-
<meta charset="UTF-8">
|
|
70
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
71
|
-
<title>Froot WebSocket Hub</title>
|
|
72
|
-
<style>
|
|
73
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
74
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
|
|
75
|
-
.container { max-width: 720px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem; }
|
|
76
|
-
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; }
|
|
77
|
-
h1 { color: #10b981; font-size: 1.75rem; margin-bottom: 0.5rem; }
|
|
78
|
-
.status { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.85rem; font-weight: 600; margin-bottom: 1rem; }
|
|
79
|
-
.status.connected { background: #064e3b; color: #34d399; }
|
|
80
|
-
.status.disconnected { background: #7f1d1d; color: #f87171; }
|
|
81
|
-
#messages { height: 280px; overflow-y: auto; background: #0b0f19; border-radius: 6px; padding: 1rem; font-family: monospace; font-size: 0.9rem; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
82
|
-
.msg { padding: 0.4rem 0.8rem; border-radius: 4px; background: #1e293b; }
|
|
83
|
-
.msg.system { color: #38bdf8; font-style: italic; background: transparent; }
|
|
84
|
-
.input-row { display: flex; gap: 0.5rem; margin-top: 1rem; }
|
|
85
|
-
input[type="text"] { flex: 1; padding: 0.75rem; border-radius: 6px; border: 1px solid #334155; background: #0b0f19; color: #fff; font-size: 1rem; outline: none; }
|
|
86
|
-
input[type="text"]:focus { border-color: #10b981; }
|
|
87
|
-
button { background: #10b981; color: #0f172a; border: none; padding: 0.75rem 1.5rem; border-radius: 6px; font-weight: bold; cursor: pointer; }
|
|
88
|
-
button:hover { background: #059669; }
|
|
89
|
-
</style>
|
|
90
|
-
</head>
|
|
91
|
-
<body>
|
|
92
|
-
<div class="container">
|
|
93
|
-
<div>
|
|
94
|
-
<h1>Froot Universal WebSocket Hub</h1>
|
|
95
|
-
<p style="color: #94a3b8;">High-speed bidirectional event broadcasting</p>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<div class="card">
|
|
99
|
-
<div id="status" class="status disconnected">Connecting...</div>
|
|
100
|
-
<div id="messages"></div>
|
|
101
|
-
<div class="input-row">
|
|
102
|
-
<input type="text" id="chat-input" placeholder="Type a message and press Enter..." />
|
|
103
|
-
<button id="send-btn">Send</button>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
<script>
|
|
109
|
-
const statusEl = document.getElementById('status');
|
|
110
|
-
const msgBox = document.getElementById('messages');
|
|
111
|
-
const input = document.getElementById('chat-input');
|
|
112
|
-
const sendBtn = document.getElementById('send-btn');
|
|
113
|
-
|
|
114
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
115
|
-
const wsUrl = protocol + '//' + window.location.host + '/ws';
|
|
116
|
-
let ws;
|
|
117
|
-
|
|
118
|
-
function connect() {
|
|
119
|
-
ws = new WebSocket(wsUrl);
|
|
120
|
-
ws.onopen = () => {
|
|
121
|
-
statusEl.className = 'status connected';
|
|
122
|
-
statusEl.textContent = 'Connected to /ws';
|
|
123
|
-
addMessage('System: Connected to live room', true);
|
|
124
|
-
};
|
|
125
|
-
ws.onmessage = (event) => {
|
|
126
|
-
try {
|
|
127
|
-
const payload = JSON.parse(event.data);
|
|
128
|
-
addMessage(payload.user + ': ' + payload.text);
|
|
129
|
-
} catch {
|
|
130
|
-
addMessage(event.data);
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
ws.onclose = () => {
|
|
134
|
-
statusEl.className = 'status disconnected';
|
|
135
|
-
statusEl.textContent = 'Disconnected (Reconnecting...)';
|
|
136
|
-
setTimeout(connect, 2000);
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function addMessage(text, isSystem = false) {
|
|
141
|
-
const div = document.createElement('div');
|
|
142
|
-
div.className = 'msg' + (isSystem ? ' system' : '');
|
|
143
|
-
div.textContent = text;
|
|
144
|
-
msgBox.appendChild(div);
|
|
145
|
-
msgBox.scrollTop = msgBox.scrollHeight;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function sendMessage() {
|
|
149
|
-
const val = input.value.trim();
|
|
150
|
-
if (!val || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
151
|
-
const packet = JSON.stringify({ user: 'Guest-' + Math.floor(Math.random() * 900 + 100), text: val });
|
|
152
|
-
ws.send(packet);
|
|
153
|
-
input.value = '';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
sendBtn.addEventListener('click', sendMessage);
|
|
157
|
-
input.addEventListener('keydown', (e) => {
|
|
158
|
-
if (e.key === 'Enter') sendMessage();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
connect();
|
|
162
|
-
</script>
|
|
163
|
-
</body>
|
|
164
|
-
</html>\`);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// WebSocket Upgrade Endpoint
|
|
168
|
-
app.get(
|
|
169
|
-
'/ws',
|
|
170
|
-
upgradeWebSocket((c) => {
|
|
171
|
-
const room = c.req.query('room') || 'general';
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
onOpen(ws) {
|
|
175
|
-
hub.subscribe(ws, room);
|
|
176
|
-
const welcome = JSON.stringify({ user: 'System', text: \`New user joined #\${room}\` });
|
|
177
|
-
hub.publish(room, welcome);
|
|
178
|
-
},
|
|
179
|
-
onMessage(ws, event) {
|
|
180
|
-
hub.publish(room, String(event.data));
|
|
181
|
-
},
|
|
182
|
-
onClose(ws) {
|
|
183
|
-
hub.unsubscribe(ws, room);
|
|
184
|
-
const exit = JSON.stringify({ user: 'System', text: \`A user left #\${room}\` });
|
|
185
|
-
hub.publish(room, exit);
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
})
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
const PORT = Number(process.env.PORT) || 3000;
|
|
192
|
-
app.listen(PORT, () => {
|
|
193
|
-
console.log(\`WebSocket server running on http://localhost:\${PORT}\`);
|
|
194
|
-
});
|
|
55
|
+
'src/server.ts': `import { Froot } from 'frootjs';
|
|
56
|
+
import { cors } from 'frootjs/cors';
|
|
57
|
+
import { upgradeWebSocket, createHub } from 'frootjs/ws';
|
|
58
|
+
|
|
59
|
+
export const app = new Froot();
|
|
60
|
+
const hub = createHub();
|
|
61
|
+
|
|
62
|
+
app.use(cors());
|
|
63
|
+
|
|
64
|
+
// Web UI Chat Client
|
|
65
|
+
app.get('/', (c) => {
|
|
66
|
+
return c.html(\`<!DOCTYPE html>
|
|
67
|
+
<html lang="en">
|
|
68
|
+
<head>
|
|
69
|
+
<meta charset="UTF-8">
|
|
70
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
71
|
+
<title>Froot WebSocket Hub</title>
|
|
72
|
+
<style>
|
|
73
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
74
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
|
|
75
|
+
.container { max-width: 720px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem; }
|
|
76
|
+
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; }
|
|
77
|
+
h1 { color: #10b981; font-size: 1.75rem; margin-bottom: 0.5rem; }
|
|
78
|
+
.status { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.85rem; font-weight: 600; margin-bottom: 1rem; }
|
|
79
|
+
.status.connected { background: #064e3b; color: #34d399; }
|
|
80
|
+
.status.disconnected { background: #7f1d1d; color: #f87171; }
|
|
81
|
+
#messages { height: 280px; overflow-y: auto; background: #0b0f19; border-radius: 6px; padding: 1rem; font-family: monospace; font-size: 0.9rem; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
82
|
+
.msg { padding: 0.4rem 0.8rem; border-radius: 4px; background: #1e293b; }
|
|
83
|
+
.msg.system { color: #38bdf8; font-style: italic; background: transparent; }
|
|
84
|
+
.input-row { display: flex; gap: 0.5rem; margin-top: 1rem; }
|
|
85
|
+
input[type="text"] { flex: 1; padding: 0.75rem; border-radius: 6px; border: 1px solid #334155; background: #0b0f19; color: #fff; font-size: 1rem; outline: none; }
|
|
86
|
+
input[type="text"]:focus { border-color: #10b981; }
|
|
87
|
+
button { background: #10b981; color: #0f172a; border: none; padding: 0.75rem 1.5rem; border-radius: 6px; font-weight: bold; cursor: pointer; }
|
|
88
|
+
button:hover { background: #059669; }
|
|
89
|
+
</style>
|
|
90
|
+
</head>
|
|
91
|
+
<body>
|
|
92
|
+
<div class="container">
|
|
93
|
+
<div>
|
|
94
|
+
<h1>Froot Universal WebSocket Hub</h1>
|
|
95
|
+
<p style="color: #94a3b8;">High-speed bidirectional event broadcasting</p>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="card">
|
|
99
|
+
<div id="status" class="status disconnected">Connecting...</div>
|
|
100
|
+
<div id="messages"></div>
|
|
101
|
+
<div class="input-row">
|
|
102
|
+
<input type="text" id="chat-input" placeholder="Type a message and press Enter..." />
|
|
103
|
+
<button id="send-btn">Send</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<script>
|
|
109
|
+
const statusEl = document.getElementById('status');
|
|
110
|
+
const msgBox = document.getElementById('messages');
|
|
111
|
+
const input = document.getElementById('chat-input');
|
|
112
|
+
const sendBtn = document.getElementById('send-btn');
|
|
113
|
+
|
|
114
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
115
|
+
const wsUrl = protocol + '//' + window.location.host + '/ws';
|
|
116
|
+
let ws;
|
|
117
|
+
|
|
118
|
+
function connect() {
|
|
119
|
+
ws = new WebSocket(wsUrl);
|
|
120
|
+
ws.onopen = () => {
|
|
121
|
+
statusEl.className = 'status connected';
|
|
122
|
+
statusEl.textContent = 'Connected to /ws';
|
|
123
|
+
addMessage('System: Connected to live room', true);
|
|
124
|
+
};
|
|
125
|
+
ws.onmessage = (event) => {
|
|
126
|
+
try {
|
|
127
|
+
const payload = JSON.parse(event.data);
|
|
128
|
+
addMessage(payload.user + ': ' + payload.text);
|
|
129
|
+
} catch {
|
|
130
|
+
addMessage(event.data);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
ws.onclose = () => {
|
|
134
|
+
statusEl.className = 'status disconnected';
|
|
135
|
+
statusEl.textContent = 'Disconnected (Reconnecting...)';
|
|
136
|
+
setTimeout(connect, 2000);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function addMessage(text, isSystem = false) {
|
|
141
|
+
const div = document.createElement('div');
|
|
142
|
+
div.className = 'msg' + (isSystem ? ' system' : '');
|
|
143
|
+
div.textContent = text;
|
|
144
|
+
msgBox.appendChild(div);
|
|
145
|
+
msgBox.scrollTop = msgBox.scrollHeight;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function sendMessage() {
|
|
149
|
+
const val = input.value.trim();
|
|
150
|
+
if (!val || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
151
|
+
const packet = JSON.stringify({ user: 'Guest-' + Math.floor(Math.random() * 900 + 100), text: val });
|
|
152
|
+
ws.send(packet);
|
|
153
|
+
input.value = '';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
sendBtn.addEventListener('click', sendMessage);
|
|
157
|
+
input.addEventListener('keydown', (e) => {
|
|
158
|
+
if (e.key === 'Enter') sendMessage();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
connect();
|
|
162
|
+
</script>
|
|
163
|
+
</body>
|
|
164
|
+
</html>\`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// WebSocket Upgrade Endpoint
|
|
168
|
+
app.get(
|
|
169
|
+
'/ws',
|
|
170
|
+
upgradeWebSocket((c) => {
|
|
171
|
+
const room = c.req.query('room') || 'general';
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
onOpen(ws) {
|
|
175
|
+
hub.subscribe(ws, room);
|
|
176
|
+
const welcome = JSON.stringify({ user: 'System', text: \`New user joined #\${room}\` });
|
|
177
|
+
hub.publish(room, welcome);
|
|
178
|
+
},
|
|
179
|
+
onMessage(ws, event) {
|
|
180
|
+
hub.publish(room, String(event.data));
|
|
181
|
+
},
|
|
182
|
+
onClose(ws) {
|
|
183
|
+
hub.unsubscribe(ws, room);
|
|
184
|
+
const exit = JSON.stringify({ user: 'System', text: \`A user left #\${room}\` });
|
|
185
|
+
hub.publish(room, exit);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const PORT = Number(process.env.PORT) || 3000;
|
|
192
|
+
app.listen(PORT, () => {
|
|
193
|
+
console.log(\`WebSocket server running on http://localhost:\${PORT}\`);
|
|
194
|
+
});
|
|
195
195
|
`,
|
|
196
196
|
};
|
|
197
197
|
},
|