aRustyDev / aRustyDev/mdbook-htmx

docs(adr): ADR-0010: Server-Side Reference Implementation Scripts

Open
#18 0 comments 0 reactions 1 assignee Claimed by @aRustyDev View on GitHub
documentation
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 `

${content}
`;
}

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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.