hyperclay
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data ā all from the browser, and deployed in milliseconds.
server.js
āāā Configuration (lines 1-43)
ā āāā Imports (Hono, Val.town services)
ā āāā Environment variables
ā āāā Storage keys
ā
āāā Core Services (lines 45-162)
ā āāā readBlob() - Consistent blob reading
ā āāā auth object - All authentication logic
ā
āāā Templates & Helpers (lines 164-358)
ā āāā TEMPLATES - All HTML templates
ā āāā respond - Response helpers
ā āāā handleAsync - Error handling wrapper
ā
āāā Middleware (lines 459-486)
ā āāā requireAdmin - Protected route auth
ā
āāā Routes (lines 490-651)
āāā GET / - Main app
āāā Auth endpoints (/auth/*)
āāā POST /save/app - Save content
āāā GET /versions - History
āāā GET /reset-dangerous - Reset
Let's add a simple page counter:
// Find DEFAULT_HTML (line ~22)
const DEFAULT_HTML = `<!DOCTYPE html>
<html>
<head>
<script src="https://hyperclay.com/js/hyperclay-starter-kit.js"></script>
</head>
<body>
<h1>My Notes</h1>
<div id="counter">Views: 0</div> <!-- Add this -->
<textarea persist edit-mode-input></textarea>
</body>
</html>`;
// Add after auth object (line ~163)
const counter = {
async increment() {
const key = `${APP_NAMESPACE}_counter`;
const current = await blob.getJSON(key) || { count: 0 };
current.count++;
await blob.setJSON(key, current);
return current.count;
},
async get() {
const key = `${APP_NAMESPACE}_counter`;
const data = await blob.getJSON(key) || { count: 0 };
return data.count;
}
};
// In the GET "/" route (line ~491)
app.get("/", async (c) => {
const html = await getCurrentHTML();
const count = await counter.increment(); // Add this
// Inject count into HTML
const updatedHtml = html.replace(
'Views: 0',
`Views: ${count}`
);
// ... rest of route
return c.html(updatedHtml);
});
// Line ~22 - Modify DEFAULT_HTML
const DEFAULT_HTML = `
<!-- Your custom starting template -->
`;
// Add after other routes (line ~600+)
app.get("/api/stats", requireAdmin, handleAsync(async (c) => {
const versions = await getVersionHistory();
return c.json({
versions: versions.length,
lastUpdated: new Date()
});
}));
// Modify token expiry (line ~64)
const expiry = Date.now() + (30 * 60 * 1000); // 30 mins instead of 15
// New endpoint for external services
app.post("/webhook", handleAsync(async (c) => {
const payload = await c.req.json();
// Verify webhook secret
const secret = c.req.header('X-Webhook-Secret');
if (secret !== Deno.env.get('WEBHOOK_SECRET')) {
return respond.unauthorized(c);
}
// Process webhook
console.log('Webhook received:', payload);
return respond.success(c, 'Webhook processed');
}));
Component | Purpose | Line # |
---|---|---|
STORAGE_KEYS | All storage key patterns | 37-43 |
auth object | Authentication logic | 60-162 |
TEMPLATES | HTML templates | 165-286 |
respond | Response helpers | 289-331 |
handleAsync | Error handling | 334-358 |
requireAdmin | Auth middleware | 460-486 |
Routes | All endpoints | 490-651 |
Variable | Required | Description |
---|---|---|
ADMIN_EMAIL | ā Yes | Your email for admin access |
APP_NAMESPACE | No | Storage namespace (default: "default") |
DEBUG | No | Enable debug logs ("true"/"false") |