Skip to content
This repository was archived by the owner on Jun 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ Details in [`docs/stability/`](./docs/stability) — semver, IR stability, LTS.

The official adapter set lives inside [`causeway.contrib`](./packages/causeway/src/causeway/contrib) and installs through extras like `causeway[dramatiq]`, `causeway[s3]`, `causeway[jwt]`, and `causeway[sqlmodel]`. Full inventory and roadmap in [`ROADMAP.md`](./ROADMAP.md#plugin-ecosystem).

> The React story is what I use day to day, so React and Next.js are the bindings I maintain. Solid, Svelte, or anything else can ride on the same `@causewayjs/ts` runtime — if you build one, I'd love to point people at it.
> React and Next.js are the bindings I maintain. Any other framework can ride on the same `@causewayjs/ts` runtime — if you build a binding for one, I'd love to point people at it.

## Contributing

Expand Down
3 changes: 0 additions & 3 deletions commitlint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ export default {
"runtime",
"codegen",
"ir",
"polyglot",
"openapi",
"streaming",
"react",
"solid",
"svelte",
"ts",
"routing",
"config",
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The longer design story is [Why Causeway](./why-causeway.md).
| Add auth, idempotency, or tenancy | [Permissions](./backend/permissions.md), [idempotency keys](./backend/idempotency.md), [multi-tenancy](./backend/multi-tenant.md) |
| Add tasks, events, or webhooks | [Background tasks](./app/tasks.md), [events](./app/events.md), [webhooks](./app/webhooks.md) |
| Test routes | [Testing](./app/testing.md), [inline scenarios](./app/inline-scenarios.md) |
| Deploy | [Deploying overview](./deploy/index.md), [Docker](./deploy/docker.md), [Fly.io](./deploy/fly.md), [Modal](./deploy/modal.md) |
| Deploy | [Deploying overview](./deploy/index.md), [Docker](./deploy/docker.md) |

## Core Concepts

Expand Down
2 changes: 0 additions & 2 deletions docs/app/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ For request-scoped values that depend on settings (a DB pool, a Stripe client),
```python
class Settings(BaseSettings):
env: str = "dev"
sentry_dsn: SecretStr | None = None
s3_bucket: str = "uploads-local"

@property
Expand All @@ -62,7 +61,6 @@ Per-env activation in `plugins.py`:
from causeway import register, env

if env() == "prod":
register(SentryObserver(dsn=settings.sentry_dsn.get_secret_value()))
register(S3Storage(bucket=settings.s3_bucket))
else:
register(LocalStorage(path="./tmp/uploads"))
Expand Down
13 changes: 1 addition & 12 deletions docs/app/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Disable in production via `create_app(..., diagnostics=False)`.
## What Causeway doesn't do

- **Metrics export.** Use the `MetricsSink` contract with a plugin (`causeway-metrics-statsd`, `causeway-metrics-prometheus`).
- **Log shipping.** The `LogSink` contract or just point your container runtime at stdout.
- **Log shipping.** Point your container runtime at stdout.
- **APM dashboards.** SigNoz, Honeycomb, Datadog, Tempo — pick one, point OTel at it.

The framework wires correlation. The transport and storage are the user's choice.
Expand All @@ -120,17 +120,6 @@ async def time_request(req):

For app-wide timing, install a class `Middleware` at the root.

**Sentry:**

```python
# src/app/plugins.py
from causeway.contrib.sentry import SentryObserver
from causeway import register, env

if env() == "prod":
register(SentryObserver(dsn=settings.sentry_dsn.get_secret_value()))
```

**OTel with auto-instrumentation:**

```python
Expand Down
16 changes: 3 additions & 13 deletions docs/app/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,11 @@ def plugin(settings):
from causeway import register, env
from causeway.contrib.dramatiq import DramatiqAdapter
from causeway.contrib.s3 import S3Storage
from causeway.contrib.sentry import SentryObserver
from app.config import settings


if env() == "prod":
register(SentryObserver(dsn=settings.sentry_dsn.get_secret_value())) # first → wraps everything
register(DramatiqAdapter(broker_url=settings.redis_url.get_secret_value()))
register(DramatiqAdapter(broker_url=settings.redis_url.get_secret_value())) # first → others can depend on it
register(S3Storage(bucket="uploads"))
```

Expand All @@ -89,14 +87,10 @@ swap.
| `TaskAdapter` | `enqueue`, `schedule`, `cron`, `eager`, `status`, `result` | `causeway.tasks.InMemoryAdapter` |
| `Storage` | `put`, `get`, `delete`, `signed_url`, `list` | `causeway.adapters.LocalStorage` |
| `KV` | `get`, `set` (TTL), `delete`, `incr`, `expire` | `causeway.adapters.MemoryKV` |
| `SessionStore` | `read`, `write`, `destroy`, `rotate` | `causeway.adapters.CookieStore` |
| `Mailer` | `send`, `send_template`, `verify_address` | bring your own |
| `Searchable` | `index`, `search`, `delete`, `bulk_index` | bring your own |
| `RateLimiter` | `acquire`, `peek`, `reset` | `causeway.adapters.MemoryLimiter` |
| `FeatureFlags` | `is_on`, `variant`, `refresh` | `causeway.adapters.StaticFlags` |
| `MetricsSink` | `counter`, `gauge`, `histogram`, `timer` | none |
| `LogSink` | `emit(record)` | stdout via `structlog` |
| `PubSub` | `publish`, `subscribe` | none |
| `AuthProvider` | `current_user`, `login`, `logout`, `verify` | bring your own |
| `DBSession` | `session`, `transaction`, `health` | provided by ORM adapters |
| `BlobScanner` | `scan(stream)` — virus / type checks | none |
Expand Down Expand Up @@ -150,7 +144,6 @@ from causeway import register, env
from app.config import settings

if env() == "prod":
register(SentryObserver(dsn=settings.sentry_dsn.get_secret_value()))
register(S3Storage(bucket=settings.s3_bucket))
else:
register(LocalStorage(path="./tmp/uploads"))
Expand All @@ -169,7 +162,7 @@ $ causeway plugins
│ DramatiqAdapter │ v1.0 │ causeway.contrib.dramatiq │
│ S3Storage │ v1.0 │ causeway.contrib.s3 │
│ RedisCache │ v1.0 │ causeway.contrib.redis │
SmtpMailer │ v1.0 │ causeway.contrib.smtp
JwtAuth │ v1.0 │ causeway.contrib.jwt
└──────────────────┴───────────────────┴──────────────────────┘
```

Expand All @@ -183,11 +176,8 @@ Official adapters use short extras and matching `causeway.contrib` modules:
- **storage**: `causeway[fs]`, `causeway[s3]` -> `causeway.contrib.fs`, `causeway.contrib.s3`
- **cache**: `causeway[redis]` -> `causeway.contrib.redis`
- **auth**: `causeway[jwt]` -> `causeway.contrib.jwt`
- **mailer**: `causeway[smtp]` -> `causeway.contrib.smtp`
- **observe**: `causeway[sentry]` -> `causeway.contrib.sentry`
- **flags**: `causeway[growthbook]` -> `causeway.contrib.growthbook`
- **db**: `causeway[sqlmodel]` -> `causeway.contrib.sqlmodel`
- **deploy**: `causeway[docker]`, `causeway[fly]`, `causeway[modal]` -> `causeway.contrib.docker`, `causeway.contrib.fly`, `causeway.contrib.modal`
- **deploy**: `causeway[docker]` -> `causeway.contrib.docker`

Third-party plugins should use `causeway-contrib-<thing>` to avoid implying official status.

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/ir-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Each change is classified per [IR stability](../stability/ir-stability.md):

## Why an IR layer at all

So one source of truth feeds more than one tool. Today the primary generator is the TypeScript route-key client. The same IR also feeds compatibility/export surfaces such as OpenAPI 3.1, Swift, and Kotlin. Each generator consumes the same contract instead of re-parsing Python.
So one source of truth feeds more than one tool. Today the primary generator is the TypeScript route-key client. The same IR also feeds compatibility/export surfaces such as OpenAPI 3.1. Each generator consumes the same contract instead of re-parsing Python.

It also makes contract-stability tooling tractable. `causeway diff` walks the IR rather than parsing Python — that's the only way you get fast, reliable breaking-change detection in CI.

Expand Down
5 changes: 2 additions & 3 deletions docs/architecture/runtime-substrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ phases — is your framework's call.

The IR is the contract. `causeway._runtime.ir.AppIR` is a pure
data structure. Walk it however you want. The shipping renderers
(`causeway._runtime.codegen` for TypeScript,
`causeway._runtime.polyglot` for Swift / Kotlin, and
`causeway._runtime.openapi` for OpenAPI 3.1) are all built on the same
(`causeway._runtime.codegen` for TypeScript and
`causeway._runtime.openapi` for OpenAPI 3.1) are both built on the same
public IR — your renderer joins the same line.

## The substrate's public surface
Expand Down
3 changes: 1 addition & 2 deletions docs/deploy/binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ distroless attack surface gets you ahead of the typical
`--assume-yes-for-downloads` (already in the default command).
- **Cross-compilation isn't supported.** Build on the same OS/arch you
ship to. Use a matrix in CI.
- **Plugins that do runtime importlib magic** (rare, but
`causeway[growthbook]` does some) may need explicit
- **Plugins that do runtime importlib magic** (rare) may need explicit
`--include-package` hints. Pass them via the `extra_packages` argument
to `build_binary()` or open an issue with the plugin.
68 changes: 0 additions & 68 deletions docs/deploy/fly.md

This file was deleted.

10 changes: 3 additions & 7 deletions docs/deploy/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

A Causeway app is **an ASGI app + a manifest**. It runs anywhere ASGI runs — Docker, Fly, Modal, Lambda (via Mangum), bare uvicorn behind nginx, your own Kubernetes cluster.

The framework doesn't own deploys. Three official adapters wrap the common targets so you don't have to write Dockerfiles by hand:
The framework doesn't own deploys. The official adapter wraps the common case so you don't have to write Dockerfiles by hand:

- **[Docker](./docker.md)** — `causeway[docker]`. Builds an image from your project.
- **[Fly.io](./fly.md)** — `causeway[fly]`. Wraps `flyctl` deploy.
- **[Modal](./modal.md)** — `causeway[modal]`. Wraps the Modal SDK for ephemeral function-as-a-service.
- **[Binary export](./binary.md)** — `causeway build --binary`. Single AOT-compiled executable for self-hosting; pairs with `FROM scratch` containers.

Every deploy adapter implements the [`DeployTarget`](../reference/classes/contracts.md#deploytarget) contract — `manifest()`, `package()`, `push(target)`.
Expand All @@ -20,7 +18,7 @@ causeway build # emit dist/ir.json + client/ + wheel
causeway deploy <target> # plugin reads dist/ and pushes
```

The adapter produces the target-specific artifact (Dockerfile + image, Fly machine spec, Modal stub) from the project shape. You don't write target-specific glue.
The adapter produces the target-specific artifact (Dockerfile + image) from the project shape. You don't write target-specific glue.

## What gets deployed

Expand All @@ -47,7 +45,7 @@ Run with:
uvicorn app:app --host 0.0.0.0 --port 8000
```

You're done. Causeway emits an ASGI app — it's not Docker-aware, Fly-aware, or Modal-aware in any way.
You're done. Causeway emits an ASGI app — it's not Docker-aware in any way.

## Health checks

Expand All @@ -69,5 +67,3 @@ You're done. Causeway emits an ASGI app — it's not Docker-aware, Fly-aware, or
## Per-target guides

- **[Docker](./docker.md)**
- **[Fly.io](./fly.md)**
- **[Modal](./modal.md)**
72 changes: 0 additions & 72 deletions docs/deploy/modal.md

This file was deleted.

5 changes: 2 additions & 3 deletions docs/internals/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,8 @@ Two discovery paths:
credentials).

A plugin implements one or more **contracts**: `TaskAdapter`,
`Storage`, `KV`, `SessionStore`, `Mailer`, `Searchable`, `RateLimiter`,
`FeatureFlags`, `MetricsSink`, `LogSink`, `PubSub`, `AuthProvider`,
`DBSession`, … Each contract ships with a reference adapter in core (or
`Storage`, `KV`, `Mailer`, `RateLimiter`, `FeatureFlags`, `MetricsSink`,
`AuthProvider`, `DBSession`, … Each contract ships with a reference adapter in core (or
in a sibling repo for plugins that need a real dependency). Picking a
real backend is a one-line swap. Full mechanics in
[`plugins.md`](../app/plugins.md).
Expand Down
12 changes: 6 additions & 6 deletions docs/internals/code-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ Builds the App Graph from the runtime app: routes, route keys, HTTP paths, sourc

## Plugins

### `contracts.py` — 363 lines
### `contracts.py` — 296 lines

Every official contract as a `typing.Protocol`. `Plugin`, `TaskAdapter`, `EventBus`, `Storage`, `KV`, `Mailer`, `AuthProvider`, `DBSession`, `BlobScanner`, `FeatureFlags`, `MetricsSink`, `LogSink`, `PubSub`, `RateLimiter`, `SessionStore`, `DeployTarget`. Each contract declares `contract_version: ClassVar[str] = "v1.0"`.
Every official contract as a `typing.Protocol`. `Plugin`, `TaskAdapter`, `EventBus`, `Storage`, `KV`, `Mailer`, `AuthProvider`, `DBSession`, `BlobScanner`, `FeatureFlags`, `MetricsSink`, `RateLimiter`, `DeployTarget`. Each contract declares `contract_version: ClassVar[str] = "v1.0"`.

If you're adding a new official contract, **start here**. Then add a reference
adapter in `adapters.py` or an optional adapter under `contrib/`, plus a test
Expand All @@ -100,9 +100,9 @@ The registry. Two discovery paths feed one ordered dict:

Lifecycle: `startup_all(settings)` fires every plugin's `startup` in registration order; `shutdown_all()` fires `shutdown` in reverse. `check_required_contracts()` walks `requires` lists and refuses to boot if a dependency is missing. `merge_settings_fragments(settings)` applies each plugin's optional `settings_fragment()` to the `Settings` instance.

### `adapters.py` — 305 lines
### `adapters.py` — 190 lines

Reference adapters shipped in core: `MemoryKV`, `LocalStorage`, `MemoryLimiter`, `StaticFlags`, `NullSink`, `MemoryBus`, `NullScanner`. These exist so the framework boots out of the box; production users swap them via a sibling `causeway-*` package.
Reference adapters shipped in core: `MemoryKV`, `LocalStorage`, `MemoryLimiter`, `StaticFlags`, `NullSink`, `NullScanner`. These exist so the framework boots out of the box; production users swap them via a sibling `causeway-*` package.

---

Expand Down Expand Up @@ -186,9 +186,9 @@ The inline-scenario runtime. Lives in a private subpackage so the implementation

Registered via the `pytest11` entry point. Adds `--causeway-routes`, `--update-snapshots`, `--causeway-no-inline`; matches route files via `pytest_collect_file`; yields one `ScenarioItem` per `scenario(...)` block; applies snapshot rewrites in `pytest_sessionfinish`.

### `cli.py` — 543 lines
### `cli.py` — 501 lines

The `causeway` CLI built on Typer. Commands: `new` (scaffold via `_scaffold.py`), `dev` (owned uvicorn server + smart route hot-swap), `build` (codegen + wheel), `codegen`, `ir`, `inspect` (App Graph), `freeze`, auxiliary generators (`openapi`, `swift`, `kotlin`), `plugins` (list registered adapters), `diff` (IR breaking-change detection), `deploy <target>` (dispatch to a registered `DeployTarget`), and `plugin new <name>` (scaffold a new plugin package).
The `causeway` CLI built on Typer. Commands: `new` (scaffold via `_scaffold.py`), `dev` (owned uvicorn server + smart route hot-swap), `build` (codegen + wheel), `codegen`, `ir`, `inspect` (App Graph), `freeze`, `openapi` (auxiliary generator), `plugins` (list registered adapters), `diff` (IR breaking-change detection), `deploy <target>` (dispatch to a registered `DeployTarget`), and `plugin new <name>` (scaffold a new plugin package).

### `_scaffold.py` — 290 lines

Expand Down
Loading
Loading