A modular TypeScript toolkit for building resilient services with strong nominal typing, reusable macro-style effects, and HAL/HATEOAS-friendly web surfaces. Everything runs on Deno for a zero-config developer experience.
- Branded (nominal) types keep IDs, units, and headers unambiguous across services.
- MacroFX-style helpers (
withCache,withRetry,withTimeout) wrap side-effecting code with resilience policies. - A lightweight web layer serves HAL and HAL-FORMS documents, HTMX-friendly SSR, and typed middleware composition.
- Overview
- Key Features
- Repository Layout
- Getting Started
- Packages
- Example Workflows
- Architecture Notes
- Testing
- Development Tips
- Examples
- Next Ideas
The repo demonstrates how functional-first patterns can improve day-to-day TypeScript work:
- Branded types model identifiers, headers, and domain-specific numbers without runtime cost.
- Macro-style decorators keep retry, timeout, and caching policies close to the functions they protect.
- HAL/HATEOAS-first HTTP keeps hypermedia contracts explicit, pairing nicely with HTMX-powered SSR.
- Port/adapter layering isolates core use-cases behind small interfaces for portability across runtimes.
The apps/ directory hosts runnable demos (CLI + SSR server) that wire the packages together. Each
package is self-contained and can be reused independently.
- ✅ Nominal typing utilities (
Brand, constructors, validators) with accompanying tests. - ✅ Core service helpers built on declarative dependency injection (
Deps) rather than global state. - ✅ Drop-in resilience macros for caching, retrying, and timing out effectful functions.
- ✅ KV abstractions with interchangeable in-memory, Deno KV, and SQLite-backed implementations.
- ✅ Typed router, middleware registry, and HAL/HAL-FORMS helpers for hypermedia APIs.
- ✅ Out-of-the-box HTMX SSR demo showcasing HAL discovery, forms, auth, rate limiting, and idempotency.
apps/
examples/ # CLI demo (macro composition + branded types)
ssr/ # HTMX SSR server with HAL/HATEOAS endpoints
tests/ # Deno tests (validation constructors, etc.)
examples/ # Standalone example applications with focused READMEs
packages/
core/ # Branded types, ports/adapters, shared services
macrofx/ # Macro-style effect wrappers (cache/retry/timeout)
web/ # Router, middleware, HAL utilities, typed headers
kv-deno/ # Deno KV adapter implementing the core KV port
kv-sqlite/ # SQLite adapter for the KV port
deno.jsonc # Deno tasks, fmt configuration, import map
import_map.json # Aliases for stdlib and 3rd-party deps
- Install Deno (v2.5+ recommended).
- Optional: SQLite (
sqlite3) installed locally when experimenting with thekv-sqlitepackage.
The project avoids external package managers. As long as Deno is installed, the repo is ready to run.
Configured in deno.jsonc:
deno task start:ssr # Launch the HTMX SSR demo on http://localhost:8080
deno task example:cli # Run the CLI example (cache + retry + timeout)
deno task test # Execute the Deno test suiteAll tasks execute with -A (allow-all) permissions to simplify local development. Adjust flags if
you need a tighter sandbox.
src/types/nominal.tsandsrc/lib/brand.ts: strongly-typed brand helpers (UserId,ApiKey,Milliseconds, etc.).src/types/constructors.ts: runtime validators (EmailFrom,PositiveIntFrom,ApiKeyFrom, …).src/lib/pipe.ts&src/lib/result.ts: minimal functional primitives.src/ports/*.ts: tiny interfaces forLogger,Http,Clock, andKV.src/adapters/*.ts: ready-made adapters (ConsoleLogger,FetchHttp,MemoryKV).src/services.ts: reusable business functions composed from aDepsobject (e.g.,timeNow,fetchJson,fetchTodo,bumpCounter).
Use this package as the foundation for shared types and side-effect ports across services.
Macro-like combinators that wrap dependency-injected functions:
withCache(ttl, fn)caches responses (KV-backed by convention).withRetry(retries, delay, fn)retries on failure with a linear backoff.withTimeout(ms, fn)races the inner function against a timeout.
Because they operate on WithDeps, you can stack them compositionally:
const resilientTodo = withTimeout(2000, withRetry(2, 100, withCache(15_000, fetchTodo)))router.ts: declarative route table + middleware composer.middleware.ts: opinionated middlewares (auth,rateLimit).mw_registry.ts: a named middleware registry so routes can opt-in by name.extras.ts: higher-level middleware (role-based access, idempotency).hal_forms.ts: builders for HAL/HAL-FORMS links, templates, and properties.typed_headers.ts: parse and brand important headers (x-api-key,idempotency-key).
This package keeps HTTP concerns modular while retaining strong typing.
Adapters that fulfill the KV port:
kv-deno: wrapsDeno.openKv, supporting TTL and atomic increments.kv-sqlite: file-backed store usingdeno.land/x/sqlite, including TTL eviction.
Swap them in wherever the KV dependency is supplied.
const deps = {
log: ConsoleLogger,
http: FetchHttp,
clock: { now: () => new Date() },
kv: MemoryKV(),
}
const composed = withTimeout(2000, withRetry(2, 100, withCache(10_000, fetchTodo)))
const todo = await composed(deps)(ApiKeyFrom("demo-key-123"), PositiveIntFrom(1))The example showcases:
- Dependency injection via explicit
deps. - Branded validators guarding external input (
ApiKeyFrom,PositiveIntFrom). - Macro composition delivering caching, retry, and timeout in ~3 lines.
- Serves an HTMX-powered dashboard (
GET /). - Streams HAL discovery at
/.well-known/haland HAL-FORMS at/forms. - Protects
/extwithauth+rateLimitmiddlewares and optional API keys. - Adds idempotency guarantees to
/echo, and role-based guard to/admin. - Demonstrates middleware registry composition (
composeNamed) and state injection.
Run deno task start:ssr, then open http://localhost:8080 to explore links, forms, and HTMX
interactions.
- Ports & Adapters: All external effects (logging, HTTP, KV, timing) pass through interfaces, making it straightforward to stub or replace them in tests.
- Nominal Safety: Brands and constructor functions prevent mixing incompatible IDs, counters, or
headers. Use
asTypehelpers sparingly and prefer the runtime constructors. - MacroFX Composition: Effects stay pure and composable; macros return new
WithDepsfunctions without losing type information. - Hypermedia-first APIs: HAL helpers keep link relations branded (
Rel,Href), while HAL-FORMS builders describe actionable forms for clients. - Middleware Registry: Named middleware stacks allow declarative policies per route and clean reuse between SSR handlers and API endpoints.
The repo currently includes targeted tests in apps/tests. Run them with:
deno task testAdd new tests by placing files under apps/tests (or any path matched by Deno’s default glob) and
exporting Deno.test suites. Because the code uses ports and explicit dependencies, you can pass in
fakes or in-memory adapters in tests without additional tooling.
- Prefer composing functions with the
Depspattern rather than importing singletons. - Reach for
withCache/withRetry/withTimeoutearly—tying resilience to the call site keeps intent obvious. - Use branded constructors whenever data crosses trust boundaries (HTTP headers, request bodies, KV payloads).
- The import map (
import_map.json) already maps@std/andsqlite; extend it as more third-party modules are needed. - Run
deno fmtbefore committing to respect the repo’s formatting preferences.
examples/type-safe-onboarding– contrasts naive onboarding flows with branded constructors that prevent identifier/header mix-ups.examples/resilient-data-pipeline– demonstrates how MacroFX turns flaky upstream fetches into a resilient, cached ingestion loop.examples/hal-forms-taskboard– showcases HAL discovery, HAL-FORMS templates, and typed middleware in an HTMX-friendly task board.examples/distributed-quotas– swapsKVadapters (memory, SQLite, Deno KV) to power rate limits without touching route logic.
- Add more branded constructors (
Currency,Locale, etc.) and domain modules underpackages/core. - Build additional middleware (e.g., tracing, structured logging) and register them by name.
- Expand the test suite with integration tests that exercise both KV backends.
- Experiment with deploying the SSR app to Deno Deploy or another edge runtime by swapping adapters.
Happy hacking! Contributions and experiments are welcome—fork, branch, and start composing macros.