Esposter

Platform — Cross-Product Layer Model

How Esposter's products link together. Five layers; the Resources layer carries the products, and the others are capabilities or infrastructure it plugs into.

Principle

Everything is a resource; capabilities are opt-in. A resource exposes and consumes datasets if it declares so. Anything publishable gets a public versioned read. Every mutation is already an achievement trigger.

New products join the platform by adding one ResourceType and one ResourceDefinitionMap entry, not by adding services or bespoke pages. Games, anime, and the fluid simulator deliberately stay outside (achievements only) — a game save has nothing to gain from naming, sharing, or dataset semantics.

Layer model

LayerContract
Identityusers.id (Better-Auth) keys every row, blob path, and session — shared by all products
ResourcesPostgres identity row + content blob + capability declaration → /docs/architecture/resources
DatasetsColumns + rows served by DatasetProvider types → /docs/architecture/datasets
PublishingVersioned publish copy + public rate-limited read at /view/[type]/[id]/docs/architecture/publishing
EventstRPC mutation path = achievement trigger key (achievementPlugin) — every new procedure is automatically triggerable
flowchart TB
  subgraph explorer [Resource Explorer — /resources]
    SHEET[Sheet<br/>datasetProvider · portable]
    SURVEY[Survey<br/>publishable · datasetProvider · fileAssets]
    TODO[TodoList]
    DASH[Dashboard<br/>publishable]
    EMAIL[Email<br/>publishable · fileAssets · portable]
    WEB[Webpage<br/>publishable · fileAssets]
    FLOW[Flowchart<br/>publishable]
    NOTE[Note<br/>publishable]
    PROG[Program<br/>datasetProvider]
    BP[Blueprint]
  end

  subgraph platform [Platform layers]
    RES["Resources<br/>identity row + content blob + ResourceDefinitionMap"]
    DS["Datasets<br/>readDataset(reference) + provider map"]
    PUB["Publishing<br/>snapshot + public /view/[type]/[id]"]
  end

  explorer --- RES
  SHEET -- Sheet provider --> DS
  SURVEY -- SurveyResponses provider --> DS
  PROG -- ProgramStatus provider --> DS
  DS -- bind / import / merge fields --> DASH
  DS --> SHEET
  DS --> EMAIL
  SURVEY --> PUB
  DASH --> PUB
  WEB --> PUB
  EMAIL --> PUB
  FLOW --> PUB
  NOTE --> PUB
  PUB -- "public /view URLs (shareable in esbabbler)" --> WORLD((Viewers))

  ACH[Achievements<br/>tRPC path middleware]
  ID[(Identity<br/>Better-Auth users.id)]
  platform --- ID
  explorer -. every mutation .-> ACH

Business-logic user journey

The headline cross-product flow — create a survey → collect responses → extract/transform → visualise → publish — runs entirely through resources and their capabilities:

sequenceDiagram
  actor Creator
  actor Respondent
  participant SV as Survey resource<br/>(Editor blade)
  participant AT as Azure Table<br/>(SurveyResponses)
  participant SH as Sheet resource<br/>(Data blade)
  participant DB as Dashboard resource
  participant PUB as Public /view/[type]/[id]

  Creator->>SV: 1. Author survey (SurveyJS autosave → saveResourceContent)
  Creator->>SV: 2. Publish — snapshot model + assets to {id}/published/{n}
  PUB-->>Respondent: 3. Share /view/survey/{id} (esbabbler, email block, anywhere)
  Respondent->>AT: 4. Respond → rows (partitionKey = survey resource id)
  Note over SV,AT: Respondents are served the published snapshot — unpublished 404s
  Creator->>SH: 5. Import responses (dataset.readDataset → one-time copy into a Sheet resource)
  SH->>SH: 6. Computed columns (Aggregation, Math, RegexMatch, …)
  Creator->>DB: 7. Bind visual to a DatasetReference (live re-resolve on load)
  DB->>PUB: 8. Publish dashboard — bakes dataset snapshots → shareable /view/dashboard/{id}

Capability matrix

ResourceDefinitionMap (/docs/architecture/resources) is the authoritative declaration; this is the summary:

ResourceTypePublishableDatasetProviderFileAssetsPortableBlades beyond Overview/Editor
Blueprint
Dashboard
Email✅ export
Flowchart
Note
Program✅ statusSetup, Status
SheetData, Settings
Survey✅ responsesResponses
TodoListItems, Calendar
Webpage

Outside the resource model: Posts (relational Postgres, social feed semantics), Esbabbler (distribution channel for published links), Achievements (the events layer itself), and games/anime/fluid (blob save state or none; achievements only). Single-blob-per-user save state (useSave, blob ${userId}/save) remains only for genuinely one-per-user saves: clicker and dungeons.