Skip to content

How StayFn works

Client / partner system OPERA Cloud (OHIP)
│ X-Tenant-Api-Key ▲ REST (OAuth, x-app-key, x-hotelid)
▼ │ Streaming API (GraphQL over WebSocket)
┌──────────────────────── StayFn.Host (one container) ─────────────────────────┐
│ /api/functions/{area}/{name}/invoke ──► invocation pipeline │
│ /api/events/webhook/{source} │ 1 tenant + API key scope │
│ /api/admin/* (JWT) /ui /mcp │ 2 quotas │
│ /health /metrics │ 3 registry lookup + JSON schema │
│ │ 4 idempotency │
│ Workers: stream ingest, outbox │ 5 invocation row │
│ dispatch, scheduler, metering, │ 6 OHIP rate-limit budget │
│ retention, stale-invocation reaper │ 7 your function (IFunctionContext) │
│ │ 8 retries 9 completion 10 dead │
│ │ letter │
└───────────────────────────────────────┴──────────────────────────────────────┘
│
PostgreSQL 16 (row-level security per tenant)
  • A function is a class with [OhipFunction("name")] and a Run(input, IFunctionContext ctx) method. It lives in an area (rsv, crm, custom, …), so its full name is <area>/<name>, for example rsv/get-reservation. The area comes from Area = on the attribute, a [FunctionArea] attribute, or the last namespace segment when it names a known OHIP area. Otherwise it is custom.
  • A handler is a method with [EventHandler("reservation.changed")]. It runs as an ordinary invocation for each matching event.
  • Functions reach OHIP only through ctx.Ohip, which adds the token, x-app-key, x-hotelid and correlation id, applies the rate-limit governor, and records every upstream call so the dashboard can show it.
  • Events from the OHIP Streaming API or from signed webhooks land in an inbox (deduplicated), then an outbox row per handler is dispatched in order per hotel and aggregate. This works across any number of replicas.

Design decisions are recorded in docs/adr/0001-architecture.md (decisions D1–D16). Verified OHIP facts, with evidence levels, are in docs/adr/0002-ohip-facts.md.