Server Integration
Type-safe server/client boundaries, RPC, form actions, and SSR.
Compile-Time Server/Client Boundary
Using cfg() and @cfgAttr, the same source file can contain both server and client code. The compiler strips the irrelevant parts from each bundle.
// This function exists ONLY in the server bundle
@cfgAttr("server")
async function getUser(id: string): Promise<User> {
return db.query(sql`SELECT * FROM users WHERE id = ${id}`);
}
// The macro generates an RPC stub for the client bundle automatically
const user = resource(() => getUser(props.id));
// Server build: calls getUser directly
// Client build: calls fetch("/api/__rpc/getUser", { body: { id: props.id } })Type-Safe API Routes
Route handlers are Fx computations. The error types flow through to the client, so the resource that consumes the API knows every possible failure.
// server/routes/users.ts
export const getUser = route("GET", "/users/:id", (params) =>
fx(function* () {
const user = yield* db.query(sql`SELECT * FROM users WHERE id = ${params.id}`);
return match(user, {
Some: (u) => Response.json(u),
None: () => Response.error(404, "User not found"),
});
})
);
// client/pages/user.ts — errors are typed!
const user = resource(() => api.getUser({ id: props.id }));
// typeof user: Resource<User, HttpError | NotFoundError>Form Actions (Remix-Style, But Typed)
Forms work without JavaScript (progressive enhancement) and with full type safety:
export const createPost = formAction(PostSchema, (data) =>
fx(function* () {
const post = yield* db.insert(sql`INSERT INTO posts ${values(data)}`);
return redirect(`/posts/${post.id}`);
})
);
// In the component — works without JS
const NewPost = component(() => {
return html`
<form action=${createPost}>
<input name="title" required />
<textarea name="body" />
<button type="submit">Publish</button>
</form>
`;
});Server-Side Rendering
SSR Compilation
Templates compile to string concatenation on the server for maximum performance:
// On client, compiles to DOM operations:
const _div = document.createElement("div");
_div.textContent = name;
// On server, compiles to string concat:
const html = `<div>${escapeHtml(name)}</div>`;Streaming SSR with Effects
Effects that fetch data can stream as they resolve:
const UserPage = component(($) => {
$.props<{ userId: string }>();
$.view = ({ userId }) => {
const user = resource(() => fetchUser(userId));
return html`
<Suspense fallback=${html`<Skeleton />`}>
${match(user) {
Ok: (u) => html`<UserCard user=${u} />`
}}
</Suspense>
`;
};
});The server streams the fallback immediately, then streams the resolved content when the effect completes.
Automatic RPC Generation
For @cfgAttr("server") functions called from client code:
- Server build: function runs directly
- Client build: function is replaced with RPC stub
// Source
@cfgAttr("server")
export async function createUser(data: CreateUserInput): Promise<User> {
return db.insert(users).values(data).returning();
}
// Client build receives:
export async function createUser(data: CreateUserInput): Promise<User> {
return fetch("/api/__rpc/createUser", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}).then(r => r.json());
}
// Server build also generates the route handler automaticallyDatabase Integration
The ConnectionIO typeclass from @typesugar/sql provides typed database operations:
@typeclass
interface ConnectionIO {
query<T>(sql: SqlFragment): Fx<T[], DatabaseError>;
execute(sql: SqlFragment): Fx<void, DatabaseError>;
transaction<T>(fx: Fx<T, E, R>): Fx<T, E | TransactionError, R>;
}
// Usage in route handler
const getUsers = route("GET", "/users", () =>
fx(function* () {
const users = yield* db.query<User>(
sql`SELECT id, name, email FROM users ORDER BY name`
);
return Response.json(users);
})
);The sql tagged template compiles to parameterized queries (preventing SQL injection) and validates the query against your schema at compile time.
Implementation Roadmap
- Code Splitting:
@cfgAttr("server")/@cfgAttr("client") - RPC Stubs: Auto-generated for server functions called from client
- Route Macros: Type-safe API routes with
route() - Form Actions:
formAction()with validation and progressive enhancement - SSR: Template compilation to string concatenation
- Streaming: Suspense boundaries with effect resolution
See also:
- Fx — the effect system powering server code
- Components — building server-rendered components
