Refactor Documentation for Backend API Method Naming and Iframe Communication

- Updated iframe documentation to remove token handling from the setup message, clarifying the focus on theme and locale.
- Added a new section in MCP documentation detailing HTTP transport configuration for API access, enhancing clarity on authorization.
- Revised frontend API documentation to standardize method naming by removing the 'Api' prefix in examples, improving consistency across backend calls.
- Enhanced event handling documentation by updating method names in examples, ensuring alignment with the new naming conventions.
This commit is contained in:
Max 2026-01-03 11:42:23 +08:00
parent aa01ea216c
commit ebf3dcc870
9 changed files with 100 additions and 82 deletions

View file

@ -47,8 +47,8 @@ When the iframe loads, CUI sends a `setup` message:
// In your page script // In your page script
window.addEventListener("message", (e) => { window.addEventListener("message", (e) => {
if (e.data.type === "setup") { if (e.data.type === "setup") {
const { theme, locale, token } = e.data.message; const { theme, locale } = e.data.message;
// Apply theme, store token, set locale // Apply theme, set locale
document.documentElement.setAttribute("data-theme", theme); document.documentElement.setAttribute("data-theme", theme);
} }
}); });
@ -165,7 +165,7 @@ window.addEventListener("message", (e) => {
switch (type) { switch (type) {
case "setup": case "setup":
// Initial setup with theme, locale, token // Initial setup with theme, locale
break; break;
case "refresh": case "refresh":
// CUI requests page refresh // CUI requests page refresh

View file

@ -68,6 +68,16 @@ Map Yao Processes directly to MCP tools:
} }
``` ```
**HTTP (REST API)**
```json
{
"transport": "http",
"url": "https://mcp.example.com/api",
"authorization_token": "$ENV.TOKEN"
}
```
**SSE (Server-Sent Events)** **SSE (Server-Sent Events)**
```json ```json

View file

@ -182,7 +182,7 @@ const self = this as Component;
// Event handler bound to s:on-click="HandleClick" // Event handler bound to s:on-click="HandleClick"
self.HandleClick = async (event: Event, data: EventData) => { self.HandleClick = async (event: Event, data: EventData) => {
const result = await $Backend().Call("ApiGetData", data.id); const result = await $Backend().Call("GetData", data.id);
console.log(result); console.log(result);
}; };
@ -191,7 +191,7 @@ self.HandleSubmit = async (event: Event) => {
event.preventDefault(); event.preventDefault();
const form = event.target as HTMLFormElement; const form = event.target as HTMLFormElement;
const formData = new FormData(form); const formData = new FormData(form);
await $Backend().Call("ApiSubmit", Object.fromEntries(formData)); await $Backend().Call("Submit", Object.fromEntries(formData));
}; };
``` ```
@ -201,7 +201,7 @@ self.HandleSubmit = async (event: Event) => {
import { $Backend, Yao } from "@yao/sui"; import { $Backend, Yao } from "@yao/sui";
// Call backend method // Call backend method
const data = await $Backend().Call("ApiMethodName", arg1, arg2); const data = await $Backend().Call("MethodName", arg1, arg2);
// Direct API calls // Direct API calls
const yao = new Yao(); const yao = new Yao();
@ -550,7 +550,7 @@ ctx.Send({
import { $Backend, Yao } from "@yao/sui"; import { $Backend, Yao } from "@yao/sui";
// Call backend method defined in .backend.ts // Call backend method defined in .backend.ts
const data = await $Backend().Call("ApiMethodName", arg1, arg2); const data = await $Backend().Call("MethodName", arg1, arg2);
// Direct API calls // Direct API calls
const yao = new Yao(); const yao = new Yao();

View file

@ -273,7 +273,7 @@ const self = this as Component;
// Event handler bound to s:on-click="HandleClick" // Event handler bound to s:on-click="HandleClick"
self.HandleClick = async (event: Event, data: EventData) => { self.HandleClick = async (event: Event, data: EventData) => {
const result = await $Backend().Call("ApiGetData", data.id); const result = await $Backend().Call("GetData", data.id);
console.log(result); console.log(result);
}; };
@ -282,7 +282,7 @@ self.HandleSubmit = async (event: Event) => {
event.preventDefault(); event.preventDefault();
const form = event.target as HTMLFormElement; const form = event.target as HTMLFormElement;
const formData = new FormData(form); const formData = new FormData(form);
await $Backend().Call("ApiSubmit", Object.fromEntries(formData)); await $Backend().Call("Submit", Object.fromEntries(formData));
}; };
``` ```

View file

@ -58,20 +58,20 @@ function BeforeRender(request: Request): Record<string, any> {
## API Methods ## API Methods
Functions prefixed with `Api` are exposed as callable endpoints: Functions prefixed with `Api` are exposed as callable endpoints. The backend automatically adds the `Api` prefix, so frontend calls use the method name without the prefix:
```typescript ```typescript
// Callable from frontend as: this.backend.ApiGetUsers() // Callable from frontend as: $Backend().Call("GetUsers")
function ApiGetUsers(request: Request): any[] { function ApiGetUsers(request: Request): any[] {
return Process("models.user.Get", {}); return Process("models.user.Get", {});
} }
// Callable from frontend as: this.backend.ApiCreateUser(name, email) // Callable from frontend as: $Backend().Call("CreateUser", name, email)
function ApiCreateUser(name: string, email: string, request: Request): any { function ApiCreateUser(name: string, email: string, request: Request): any {
return Process("models.user.Create", { name, email }); return Process("models.user.Create", { name, email });
} }
// Callable from frontend as: this.backend.ApiDeleteUser(id) // Callable from frontend as: $Backend().Call("DeleteUser", id)
function ApiDeleteUser(id: string, request: Request): boolean { function ApiDeleteUser(id: string, request: Request): boolean {
Process("models.user.Delete", id); Process("models.user.Delete", id);
return true; return true;
@ -81,19 +81,20 @@ function ApiDeleteUser(id: string, request: Request): boolean {
### Calling from Frontend ### Calling from Frontend
```typescript ```typescript
function Page(component: HTMLElement) { import { $Backend, Component } from "@yao/sui";
this.root = component;
this.loadUsers = async () => { const self = this as Component;
const users = await this.backend.ApiGetUsers();
console.log(users);
};
this.createUser = async () => { self.LoadUsers = async () => {
const user = await this.backend.ApiCreateUser("John", "john@example.com"); // Call "ApiGetUsers" in backend script (without "Api" prefix)
console.log("Created:", user); const users = await $Backend().Call("GetUsers");
}; console.log(users);
} };
self.CreateUser = async () => {
const user = await $Backend().Call("CreateUser", "John", "john@example.com");
console.log("Created:", user);
};
``` ```
## Constants ## Constants
@ -115,10 +116,12 @@ const __sui_constants = {
Access in frontend: Access in frontend:
```typescript ```typescript
function Page(component: HTMLElement) { import { Component } from "@yao/sui";
console.log(this.constants.API_URL); // "/api/v1"
console.log(this.constants.MAX_ITEMS); // 100 const self = this as Component;
}
console.log(self.constants.API_URL); // "/api/v1"
console.log(self.constants.MAX_ITEMS); // 100
``` ```
## Helpers ## Helpers
@ -147,11 +150,13 @@ function validateEmail(email: string): boolean {
Access in frontend: Access in frontend:
```typescript ```typescript
function Page(component: HTMLElement) { import { Component } from "@yao/sui";
const formatted = this.helpers.formatDate("2024-01-15");
const price = this.helpers.formatCurrency(99.99); const self = this as Component;
const isValid = this.helpers.validateEmail("test@example.com");
} const formatted = self.helpers.formatDate("2024-01-15");
const price = self.helpers.formatCurrency(99.99);
const isValid = self.helpers.validateEmail("test@example.com");
``` ```
## Request Object ## Request Object

View file

@ -43,11 +43,13 @@ A component is just a page with a single root element:
**`/card/card.ts`**: **`/card/card.ts`**:
```typescript ```typescript
function card(component: HTMLElement) { import { Component } from "@yao/sui";
this.root = component;
this.store = new __sui_store(component); const self = this as Component;
this.props = new __sui_props(component);
} // self.root - Root element
// self.store - Data store
// self.props - Props from attributes
``` ```
## Using Components ## Using Components
@ -94,17 +96,16 @@ Props are passed as attributes:
Access props in the component script: Access props in the component script:
```typescript ```typescript
function userCard(component: HTMLElement) { import { Component } from "@yao/sui";
this.root = component;
this.props = new __sui_props(component);
// Get single prop const self = this as Component;
const name = this.props.Get("name");
// Get all props // Get single prop
const allProps = this.props.List(); const name = self.props.Get("name");
// { name: "John", email: "john@example.com", avatar: "...", role: "admin" }
} // Get all props
const allProps = self.props.List();
// { name: "John", email: "john@example.com", avatar: "...", role: "admin" }
``` ```
Access props in backend script: Access props in backend script:
@ -221,7 +222,7 @@ self.watch = {
// Event handlers (bound to s:on-click="HandleClick") // Event handlers (bound to s:on-click="HandleClick")
self.HandleClick = async (event: Event, data: EventData) => { self.HandleClick = async (event: Event, data: EventData) => {
const result = await $Backend().Call("ApiMethod", data.id); const result = await $Backend().Call("Method", data.id);
// Handle result // Handle result
}; };
``` ```
@ -339,7 +340,6 @@ Component CSS is automatically scoped using namespace attributes:
## Important Notes ## Important Notes
1. **Single Root Element**: Components must have exactly one root element 1. **Single Root Element**: Components must have exactly one root element
2. **Route as Identifier**: The page route becomes the component name (e.g., `/card``card()`) 2. **Scoped Styles**: CSS is automatically scoped to prevent conflicts
3. **Scoped Styles**: CSS is automatically scoped to prevent conflicts 3. **Recursive Prevention**: SUI detects and prevents recursive component inclusion
4. **Recursive Prevention**: SUI detects and prevents recursive component inclusion 4. **Component Pattern**: Use `const self = this as Component` to access component APIs
5. **Script Naming**: Function name is derived from the route path

View file

@ -121,7 +121,7 @@ self.DeleteItem = async (event: Event, data: EventData) => {
const item = data.item; // Object from s:json-item const item = data.item; // Object from s:json-item
if (confirm(`Delete ${item.name}?`)) { if (confirm(`Delete ${item.name}?`)) {
await $Backend().Call("ApiDeleteItem", id); await $Backend().Call("DeleteItem", id);
(event.target as HTMLElement).closest(".item")?.remove(); (event.target as HTMLElement).closest(".item")?.remove();
} }
}; };
@ -285,7 +285,7 @@ self.HandleSubmit = async (event: Event) => {
const password = formData.get("password"); const password = formData.get("password");
try { try {
await $Backend().Call("ApiLogin", email, password); await $Backend().Call("Login", email, password);
window.location.href = "/dashboard"; window.location.href = "/dashboard";
} catch (error) { } catch (error) {
alert("Login failed"); alert("Login failed");
@ -380,7 +380,7 @@ self.AddTodo = async (event: Event) => {
const input = form.querySelector("input") as HTMLInputElement; const input = form.querySelector("input") as HTMLInputElement;
if (input.value.trim()) { if (input.value.trim()) {
const todo = await $Backend().Call("ApiAddTodo", input.value); const todo = await $Backend().Call("AddTodo", input.value);
const todos = self.state.Get("todos") || []; const todos = self.state.Get("todos") || [];
self.state.Set("todos", [...todos, todo]); self.state.Set("todos", [...todos, todo]);
input.value = ""; input.value = "";
@ -389,11 +389,11 @@ self.AddTodo = async (event: Event) => {
self.ToggleTodo = async (event: Event, data: EventData) => { self.ToggleTodo = async (event: Event, data: EventData) => {
const checkbox = event.target as HTMLInputElement; const checkbox = event.target as HTMLInputElement;
await $Backend().Call("ApiToggleTodo", data.id, checkbox.checked); await $Backend().Call("ToggleTodo", data.id, checkbox.checked);
}; };
self.DeleteTodo = async (event: Event, data: EventData) => { self.DeleteTodo = async (event: Event, data: EventData) => {
await $Backend().Call("ApiDeleteTodo", data.id); await $Backend().Call("DeleteTodo", data.id);
const todos = self.state.Get("todos").filter((t: any) => t.id !== data.id); const todos = self.state.Get("todos").filter((t: any) => t.id !== data.id);
self.state.Set("todos", todos); self.state.Set("todos", todos);
}; };

View file

@ -40,23 +40,26 @@ const items = component.queryAll(".item"); // Returns NodeList
### Via $Backend ### Via $Backend
The backend automatically adds the `Api` prefix to method names, so you call without the prefix:
```typescript ```typescript
import { $Backend } from "@yao/sui"; import { $Backend } from "@yao/sui";
// Call backend API methods // Call backend API methods (backend functions are ApiGetUsers, ApiGetUser, ApiCreateUser)
const users = await $Backend().Call("ApiGetUsers"); const users = await $Backend().Call("GetUsers");
const user = await $Backend().Call("ApiGetUser", 123); const user = await $Backend().Call("GetUser", 123);
const result = await $Backend().Call("ApiCreateUser", "John", "john@example.com"); const result = await $Backend().Call("CreateUser", "John", "john@example.com");
``` ```
### Direct Call ### Direct Call
```typescript ```typescript
// __sui_backend_call(route, headers, method, ...args) // __sui_backend_call(route, headers, method, ...args)
// Note: method name here also gets Api prefix added automatically
const result = await __sui_backend_call( const result = await __sui_backend_call(
"/users/list", // Page route "/users/list", // Page route
{ "X-Custom-Header": "value" }, // Custom headers { "X-Custom-Header": "value" }, // Custom headers
"ApiGetUsers", // Method name "GetUsers", // Method name (backend has ApiGetUsers)
{ page: 1, limit: 10 } // Arguments { page: 1, limit: 10 } // Arguments
); );
``` ```
@ -81,7 +84,7 @@ import { $Backend, Component } from "@yao/sui";
const self = this as Component; const self = this as Component;
self.RefreshUsers = async () => { self.RefreshUsers = async () => {
const users = await $Backend().Call("ApiGetUsers"); const users = await $Backend().Call("GetUsers");
// Render with data // Render with data
await self.render("userList", { users }); await self.render("userList", { users });
@ -352,7 +355,7 @@ async function loadUsers() {
// Create user // Create user
self.CreateUser = async (event: Event, data: EventData) => { self.CreateUser = async (event: Event, data: EventData) => {
const response = await $Backend().Call("ApiCreateUser", data.name, data.email); const response = await $Backend().Call("CreateUser", data.name, data.email);
const users = self.state.Get("users"); const users = self.state.Get("users");
self.state.Set("users", [...users, response]); self.state.Set("users", [...users, response]);
}; };
@ -500,7 +503,7 @@ init();
// Event handlers // Event handlers
self.HandleSave = async (event: Event, data: EventData) => { self.HandleSave = async (event: Event, data: EventData) => {
try { try {
await $Backend().Call("ApiSave", data); await $Backend().Call("Save", data);
sendAction("notify.success", { message: "Saved successfully!" }); sendAction("notify.success", { message: "Saved successfully!" });
} catch (error: any) { } catch (error: any) {
sendAction("notify.error", { message: error.message }); sendAction("notify.error", { message: error.message });

View file

@ -112,18 +112,18 @@ Named keys are used internally for translation lookup. The `keys` section in loc
### Scripts ### Scripts
```typescript ```typescript
function Page(component: HTMLElement) { import { Component } from "@yao/sui";
this.root = component;
this.showMessage = () => { const self = this as Component;
const message = __m("Operation completed");
alert(message);
};
this.confirm = () => { self.ShowMessage = () => {
return confirm(__m("Are you sure you want to delete?")); const message = __m("Operation completed");
}; alert(message);
} };
self.Confirm = () => {
return confirm(__m("Are you sure you want to delete?"));
};
``` ```
## Locale Detection ## Locale Detection
@ -223,17 +223,17 @@ This command:
<a href="/contact" s:trans>Contact</a> <a href="/contact" s:trans>Contact</a>
</nav> </nav>
<button s:on-click="showWelcome" s:trans>Show Welcome</button> <button s:on-click="ShowWelcome" s:trans>Show Welcome</button>
</div> </div>
<script> <script>
function home(component) { import { Component } from "@yao/sui";
this.root = component;
this.showWelcome = () => { const self = this as Component;
alert(__m("Welcome to our site!"));
}; self.ShowWelcome = () => {
} alert(__m("Welcome to our site!"));
};
</script> </script>
``` ```