aRustyDev / aRustyDev/mdbook-htmx
docs(adr): ADR-0010: Server-Side Reference Implementation Scripts
- Dominant language
- Rust
- Stars
- 0
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
# ADR-0010: Server-Side Reference Implementation Scripts
## Status
Accepted
## Context
mdbook-htmx generates static files (HTML, JSON manifests, search indexes) but requires server-side logic to:
- Serve appropriate content based on `HX-Request` header
- Implement search endpoints
- Handle authentication/authorization
- Manage scope switching
Users need guidance on implementing these server-side behaviors. The question: should the backend generate reference implementation scripts?
## Decision Drivers
1. **Developer Experience** - Reduce time-to-working-deployment
2. **HTMX Philosophy** - Server-side code is expected and encouraged
3. **Portability** - Scripts should work across runtimes (Workers, Node, Deno)
4. **Maintainability** - Reference code needs ongoing updates
5. **Flexibility** - Users may have different server frameworks
## Decision
**Generate TypeScript reference implementation scripts in `book/htmx/scripts/`.**
These scripts are:
- **Reference implementations**, not required
- **TypeScript** for type safety and self-documentation
- **Runtime-agnostic** (work with Workers, Node, Deno, Bun)
- **Framework-agnostic** (use Web APIs, adapt to Hono/Express/etc.)
## HTMX Alignment
This decision is **fully aligned** with HTMX philosophy:
| Principle | How Scripts Align |
|-----------|-------------------|
| Server returns HTML | Scripts render HTML responses |
| Minimal client-side JS | Zero client-side code in scripts |
| Server is source of truth | Scripts manage state, auth, scope |
| Progressive enhancement | Scripts serve full pages and fragments |
HTMX's "minimal JS" applies to the **client**. Server-side code in any language is expected.
## Output Structure
```
book/htmx/scripts/
├── types.ts # Shared type definitions
├── manifest-loader.ts # Load and cache manifests
├── search.ts # Search endpoint handler
├── authn.ts # Authentication middleware
├── authz.ts # Authorization middleware
├── scope.ts # Scope switching logic
├── htmx-utils.ts # HX-Request detection, OOB helpers
├── meili-proxy.ts # Meilisearch proxy (if external search enabled)
└── README.md # Usage documentation
```
## Deployment Patterns
These scripts support multiple deployment architectures:
### In-Memory Search (Simple Sites)
Worker bundles search index directly:
```typescript
import searchIndex from "./search-index.json";
// Use Fuse.js/MiniSearch with bundled index
```
### External Search via Cloudflare Tunnel (Recommended for Production)
Worker acts as **policy enforcement proxy** to self-hosted Meilisearch:
```
Worker → Cloudflare Tunnel → Private Meilisearch
```
**Key security properties**:
- Meilisearch never internet-facing
- Worker validates auth before searching
- Query inputs sanitized (length limits, filter whitelists)
- API keys server-side only (never sent to browser)
- Results authorization-filtered at runtime
See [`examples/meilisearch-cf-tunnel.md`](../examples/meilisearch-cf-tunnel.md) for complete implementation.
## Script Contents
### types.ts
```typescript
// Generated from manifest schema
export interface Page {
path: string;
title: string;
file: string;
fragment: string;
auth: {
access: 'public' | 'authenticated' | 'roles';
roles?: string[];
fallback?: string;
};
scopes: string[];
}
export interface Manifest {
version: string;
generated: string;
scopes: {
available: string[];
default: string;
};
pages: Page[];
navigation: NavigationItem[];
}
export interface User {
id: string;
roles: string[];
preferences?: {
scope?: string;
};
}
```
### htmx-utils.ts
```typescript
export function isHtmxRequest(request: Request): boolean {
return request.headers.get('HX-Request') === 'true';
}
export function oobSwap(id: string, content: string): string {
return `
}
export function htmxRedirect(url: string): Response {
return new Response(null, {
status: 200,
headers: { 'HX-Redirect': url }
});
}
```
### authz.ts
```typescript
import type { Page, User } from './types';
export function isAuthorized(user: User | null, page: Page): boolean {
switch (page.auth.access) {
case 'public':
return true;
case 'authenticated':
return user !== null;
case 'roles':
return page.auth.roles?.some(role => user?.roles?.includes(role)) ?? false;
default:
return false;
}
}
```
## Configuration
```toml
[output.htmx.scripts]
enabled = true # Generate reference scripts
language = "typescript" # typescript | javascript
runtime = "workers" # workers | node | deno | bun
framework = "hono" # hono | express | none
```
## Consequences
### Positive
- Faster time-to-deployment
- Types document the manifest schema
- Consistent patterns across deployments
- Testable reference implementations
### Negative
- Maintenance burden (scripts need updates)
- May not fit all server architectures
- Users might copy without understanding
### Mitigation
- Clear "reference implementation" labeling
- Extensive inline documentation
- Integration tests in mdbook-htmx repo
- Version scripts with manifest schema
## Alternatives Considered
### No Scripts (Documentation Only)
Provide patterns in docs, no generated code.
**Rejected** because:
- Higher barrier to entry
- Documentation drifts from actual implementation
- Users reinvent the wheel
### Generate Full Server Application
Generate complete deployable server.
**Rejected** because:
- Too opinionated
- Hard to customize
- Framework lock-in
## References
- [HTMX Server Examples](https://htmx.org/examples/)
- [Cloudflare Workers TypeScript](https://developers.cloudflare.com/workers/languages/typescript/)
- [Hono - Ultrafast Web Framework](https://hono.dev/)
Contributor guide
Assessment
This issue has not been assessed yet.