Processing client requests with WSX server handlers
import { html } from "@wsx-sh/core";
wsx.on("button-click", async (request, connection) => {
return {
id: request.id,
target: request.target,
html: html`<div>Button clicked at ${new Date().toLocaleTimeString()}</div>`,
};
});
// Catch-all handler for unmatched requests
wsx.on(async (request, connection) => {
console.log(`Unhandled request: ${request.handler}`);
return {
id: request.id,
target: request.target,
html: html`<div>Handler "${request.handler}" not found</div>`,
};
});
interface WSXRequest {
id: string; // Unique request ID
handler: string; // Handler name (from wx-send)
target: string; // Target selector (from wx-target)
trigger: string; // Trigger event type
data?: Record<string, any>; // Form data or custom data
swap?: string; // Swap specification
}
interface WSXConnection {
id: string; // Unique connection ID
sessionData?: Record<string, any>; // Session storage
send(data: string): void; // Send data to client
close(): void; // Close connection
}
interface WSXResponse {
id: string; // Must match request.id
target: string; // CSS selector for target
html: string; // HTML content to swap
swap?: string; // How to swap content
oob?: WSXOOBUpdate[]; // Out-of-band updates
}
wsx.on("submit-form", async (request, connection) => {
const { username, email, password } = request.data;
// Validate input
if (!username || !email || !password) {
return {
id: request.id,
target: request.target,
html: `<div class="error">All fields are required</div>`,
};
}
try {
// Save to database
const user = await createUser({ username, email, password });
return {
id: request.id,
target: request.target,
html: `<div class="success">User ${user.username} created successfully!</div>`,
};
} catch (error) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Registration failed: ${error.message}</div>`,
};
}
});
wsx.on("load-users", async (request, connection) => {
try {
const users = await User.findAll({
order: [["createdAt", "DESC"]],
limit: 20,
});
const usersHtml = users
.map(
(user) => `
<div class="user-card">
<h3>${user.name}</h3>
<p>${user.email}</p>
<button wx-send="delete-user" data-user-id="${user.id}">Delete</button>
</div>
`
)
.join("");
return {
id: request.id,
target: request.target,
html: usersHtml,
};
} catch (error) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Failed to load users</div>`,
};
}
});
// Create
wsx.on("create-post", async (request, connection) => {
const { title, content } = request.data;
const userId = connection.sessionData?.user?.id;
if (!userId) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Authentication required</div>`,
};
}
const post = await Post.create({ title, content, userId });
return {
id: request.id,
target: request.target,
html: `<div class="success">Post "${title}" created</div>`,
oob: [
{
target: "#post-list",
html: `<div class="post">${title}</div>`,
swap: "afterbegin",
},
],
};
});
// Update
wsx.on("update-post", async (request, connection) => {
const { id, title, content } = request.data;
await Post.update({ title, content }, { where: { id } });
return {
id: request.id,
target: request.target,
html: `<div class="success">Post updated</div>`,
};
});
// Delete
wsx.on("delete-post", async (request, connection) => {
const { id } = request.data;
await Post.destroy({ where: { id } });
return {
id: request.id,
target: `#post-${id}`,
html: "",
swap: "outerHTML",
};
});
wsx.on("login", async (request, connection) => {
const { username, password } = request.data;
const user = await authenticate(username, password);
if (user) {
// Store user in session
connection.sessionData = {
...connection.sessionData,
user,
loginTime: new Date(),
};
return {
id: request.id,
target: request.target,
html: `<div>Welcome, ${user.name}!</div>`,
};
} else {
return {
id: request.id,
target: request.target,
html: `<div class="error">Invalid credentials</div>`,
};
}
});
// Use session data in other handlers
wsx.on("get-profile", async (request, connection) => {
const user = connection.sessionData?.user;
if (!user) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Please log in first</div>`,
};
}
return {
id: request.id,
target: request.target,
html: `
<div class="profile">
<h2>${user.name}</h2>
<p>Email: ${user.email}</p>
<p>Logged in: ${connection.sessionData.loginTime}</p>
</div>
`,
};
});
wsx.on("complex-update", async (request, connection) => {
const updates = await getMultipleUpdates();
// Return array of responses
return updates.map((update) => ({
id: request.id,
target: update.target,
html: update.html,
swap: update.swap,
}));
});
wsx.on("admin-action", async (request, connection) => {
const user = connection.sessionData?.user;
// Check permissions
if (!user?.isAdmin) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Admin access required</div>`,
};
}
// Perform admin action
const result = await performAdminAction(request.data);
return {
id: request.id,
target: request.target,
html: `<div class="success">Admin action completed: ${result}</div>`,
};
});
wsx.on("database-operation", async (request, connection) => {
try {
const result = await performDatabaseOperation(request.data);
return {
id: request.id,
target: request.target,
html: `<div class="success">Operation completed: ${result.id}</div>`,
};
} catch (error) {
console.error("Database operation failed:", error);
return {
id: request.id,
target: request.target,
html: `<div class="error">Operation failed. Please try again.</div>`,
};
}
});
wsx.on("validate-data", async (request, connection) => {
const { email, age } = request.data;
// Validation logic
const errors = [];
if (!email || !email.includes("@")) {
errors.push("Valid email is required");
}
if (!age || age < 18) {
errors.push("Age must be 18 or older");
}
if (errors.length > 0) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Validation errors: ${errors.join(", ")}</div>`,
};
}
// Proceed with valid data
const result = await processValidData({ email, age });
return {
id: request.id,
target: request.target,
html: `<div class="success">Data processed successfully</div>`,
};
});
wsx.on("external-api-call", async (request, connection) => {
try {
// Try primary API
const result = await primaryAPI.call(request.data);
return {
id: request.id,
target: request.target,
html: `<div>Primary API result: ${result}</div>`,
};
} catch (primaryError) {
try {
// Fallback to secondary API
const result = await secondaryAPI.call(request.data);
return {
id: request.id,
target: request.target,
html: `<div>Fallback API result: ${result}</div>`,
};
} catch (secondaryError) {
return {
id: request.id,
target: request.target,
html: `<div class="error">All APIs unavailable. Please try again later.</div>`,
};
}
}
});
function requireAuth(handler) {
return async (request, connection) => {
const user = connection.sessionData?.user;
if (!user) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Authentication required</div>`,
};
}
return handler(request, connection);
};
}
// Use middleware
wsx.on(
"protected-action",
requireAuth(async (request, connection) => {
return {
id: request.id,
target: request.target,
html: `<div>Protected action executed</div>`,
};
})
);
function validateInput(schema) {
return (handler) => async (request, connection) => {
const validation = schema.validate(request.data);
if (validation.error) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Validation failed: ${validation.error.message}</div>`,
};
}
return handler(request, connection);
};
}
// Use validation middleware
const userSchema = {
username: { required: true, minLength: 3 },
email: { required: true, type: "email" },
};
wsx.on(
"create-user",
validateInput(userSchema)(async (request, connection) => {
const user = await createUser(request.data);
return {
id: request.id,
target: request.target,
html: `<div>User ${user.username} created</div>`,
};
})
);
function logRequests(handler) {
return async (request, connection) => {
const startTime = Date.now();
console.log(
`[${new Date().toISOString()}] ${request.handler} - ${connection.id}`
);
try {
const result = await handler(request, connection);
const duration = Date.now() - startTime;
console.log(
`[${new Date().toISOString()}] ${
request.handler
} completed in ${duration}ms`
);
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error(
`[${new Date().toISOString()}] ${
request.handler
} failed after ${duration}ms:`,
error
);
throw error;
}
};
}
const cache = new Map();
wsx.on("expensive-operation", async (request, connection) => {
const cacheKey = `${request.handler}-${JSON.stringify(request.data)}`;
// Check cache first
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
// Perform expensive operation
const result = await performExpensiveOperation(request.data);
const response = {
id: request.id,
target: request.target,
html: `<div>Result: ${result}</div>`,
};
// Cache result for 5 minutes
cache.set(cacheKey, response);
setTimeout(() => cache.delete(cacheKey), 5 * 60 * 1000);
return response;
});
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20,
idleTimeoutMillis: 30000,
});
wsx.on("database-query", async (request, connection) => {
const client = await pool.connect();
try {
const result = await client.query("SELECT * FROM users WHERE id = $1", [
request.data.id,
]);
return {
id: request.id,
target: request.target,
html: `<div>User: ${result.rows[0]?.name || "Not found"}</div>`,
};
} finally {
client.release();
}
});
// handler-tests.js
import { jest } from "@jest/globals";
describe("WSX Handlers", () => {
test("should handle user creation", async () => {
const mockRequest = {
id: "test-id",
handler: "create-user",
target: "#result",
data: { username: "testuser", email: "test@example.com" },
};
const mockConnection = {
id: "conn-1",
sessionData: {},
};
const response = await handlers["create-user"](mockRequest, mockConnection);
expect(response.id).toBe("test-id");
expect(response.html).toContain("testuser");
});
test("should require authentication", async () => {
const mockRequest = {
id: "test-id",
handler: "protected-action",
target: "#result",
data: {},
};
const mockConnection = {
id: "conn-1",
sessionData: {}, // No user
};
const response = await handlers["protected-action"](
mockRequest,
mockConnection
);
expect(response.html).toContain("Authentication required");
});
});
describe("Handler Integration", () => {
test("should update database and return response", async () => {
const wsx = createTestWSXServer();
wsx.on("update-profile", async (request, connection) => {
await User.update(request.data, {
where: { id: connection.sessionData.user.id },
});
return {
id: request.id,
target: request.target,
html: "<div>Profile updated</div>",
};
});
const response = await wsx.handleRequest(
{
id: "test-id",
handler: "update-profile",
target: "#result",
data: { name: "New Name" },
},
mockConnection
);
expect(response.html).toContain("Profile updated");
// Verify database was updated
const user = await User.findByPk(mockConnection.sessionData.user.id);
expect(user.name).toBe("New Name");
});
});
sessionData