Mediathek docs

Decisions

ADR-0004 — Content lives in S3 behind a proxy Lambda; DynamoDB holds the pointer

Status: Superseded by ADR-0005 and ADR-0006 (Mediathek) · Date: 2026-08-28

Kept as the template's record. The Mediathek has no Workspace and no objectProxy: media is delivered as in ADR-0005 and written as in ADR-0006. The lessons in Consequences (ListBucket for 404s, data-stack placement, routing on arguments) all still apply, and were applied.

Context

A workspace's content can be large and is edited as a whole. Two questions had to be answered together, because the answer to each constrains the other:

Where does it live? DynamoDB has a 400 KB item limit and charges by throughput; modelling a document as rows means a schema migration every time its shape changes. S3 has neither problem.

Who may read it? Access is per-tenant: the members of app-<slug>. Amplify Gen 2's defineStorage rules are baked in at deploy time, so they cannot express "the caller is in group app-<slug>" for a group that will be created by hand next month. Declaring the groups in defineAuth instead would mean a backend deploy per tenant — see ADR-0002.

So the static rules cannot be the boundary, and something at runtime has to be.

Decision

The object in S3 is the source of truth. DynamoDB holds a Workspace metadata row: the name, the group, the object key, an advisory version and an advisory lock. It never holds the content.

All access goes through the objectProxy Lambda. It reads the caller's cognito:groups from the AppSync identity, fetches the Workspace row, and compares. Only then does it act. The browser never talks to S3 directly, and defineStorage's coarse rules exist only so the bucket is not world-open.

Reads hand back a short-lived presigned GET, so the object never travels through AppSync — which has neither the payload limits nor the pricing for it.

Writes go through the function, not a presigned PUT, because the correctness mechanism is an S3 If-Match precondition and putting it inside the function means the caller cannot drop, alter or replay it. Unconditional writes are refused outright.

Consequences