Yichus/Service
Builtin package (resource service.bosatsu).
This is the complete package source used by this build. The export
list names its public API; definitions below give the types and behavior.
New to the API? Start with Make a calculator or Build an API, then use this page to look up a definition.
package Yichus/Service
from Bosatsu/Predef import Int, List, String
export (
Permission(),
Request(),
Response(),
Reply(),
OneOf(),
Handler(),
RouteAuthority(),
Limit(),
Route(),
ServiceDef(),
ok,
handler,
route,
public_route,
publishable_route,
role_route,
limited,
limited_by,
service_def
)
enum Permission:
ReadPerm(resource: String)
WritePerm(resource: String)
CreatePerm(resource: String)
DeletePerm(resource: String)
QueryPerm(resource: String)
struct Request(path: String, body: String)
struct Response(status: Int, body: String)
# What a handler answers: its value (`Done`), or a refusal the engine sends
# with the HTTP status its constructor names and a JSON body of
# {"message", "status", "code"}. `e` says why a handler refuses: an enum
# the program declares with two or more constructors, none spelled like
# one of the engine's own codes (invalid, not_found, try_again, ...). A
# NotFound or Refused carries one of its values, whose constructor, in
# snake_case, is the body's `code`, and whose fields join the body beside
# it (KeyReused(key, requested) answers
# {"message", "status": 422, "code": "key_reused", "key", "requested"}).
# The generated OpenAPI document lists each status and each code, so a
# generated client raises the matching error and can branch on the code.
# Done(value) 200, the value as JSON
# Invalid(message) 400, code "invalid": the request is
# malformed or out of bounds
# NotFound(message, reason) 404: what the request names does not exist
# for the caller
# Refused(message, reason) 422: a valid request the current state
# refuses (a name already taken, a key reused
# for another request, a count that does not
# fit a saved row)
# TryAgain(message) 503, code "try_again", with Retry-After:
# nothing was saved, or sending the same
# request again is safe
# A repeat with the same Idempotency-Key replays every answer but TryAgain.
# There is no 409: generated clients retry a 409 as if it were busy.
enum Reply[e, a]:
Done(value: a)
Invalid(message: String)
NotFound(message: String, reason: e)
Refused(message: String, reason: e)
TryAgain(message: String)
# A value of one of two types. In a route's request or result it is
# written as the value alone, with no `type` property naming the
# constructor: a String or a List, say, as a Chat Completions message's
# `content` is. The engine refuses to generate a route whose OneOf has two
# types with overlapping JSON, or JSON that can be null, because JSON could
# not tell which one a value is. A OneOf stored in a table is tagged like
# any other union, so that check does not apply to it there.
enum OneOf[a, b]:
First(first: a)
Second(second: b)
# The handler's function type is existentially erased so one service can
# register routes with differently-typed handlers (a rails-like routing
# table). Static analysis never needs the type parameter: route-to-handler
# attribution reads the typed Global reference from the Matchless IR.
struct Handler(name: String, run: exists a. a)
# The authority the generated server must establish before invoking a route.
# `RequiresRoles` means every listed role. Roles come from the server's
# verified identity boundary; request bodies never construct a Principal.
# `Publishable` is `Authenticated`, and an account's publishable key may
# call it too: the key an API's customer puts in pages their own users
# load, as a payment provider's publishable key is. Under
# YICHUS_AUTH=clerk-api-key a publishable key is one whose scopes are
# exactly ["publishable"]; it acts as its account, and every other route
# that reads a credential refuses it (403), the engine's key-making route
# included. A public route reads no credential, so it answers anyone.
# Under every other mode a `Publishable` route is an ordinary
# authenticated one.
enum RouteAuthority:
Authenticated
RequiresRoles(roles: List[String])
Anonymous
Publishable
# A limit on how often one account may call a route: at most `requests`
# calls in each window of `seconds` seconds, the windows starting at
# multiples of `seconds` since 1970. Every key of one account, publishable
# ones included, counts against the same windows. A call over the limit
# answers 429 with Retry-After, and every answer to a call the engine
# counted says how much is left in RateLimit and RateLimit-Policy headers. The engine's
# HTTP listener counts every call with an account, a malformed one too,
# against the shortest window first; a call past one window is not counted
# against the longer ones. Counts live in
# the store when it keeps them (the Postgres adapter does), so every
# instance of the engine shares them, and in the engine's memory
# otherwise. A public route has no account to count, so it takes no
# limit. A limit `by` request fields counts each account's calls apart for
# each value of those top-level fields of the request body (a pull's
# token, say), so one learner's guesses at one pull run out without
# holding up the account's other pulls.
struct Limit(requests: Int, seconds: Int, by: List[String])
struct Route(
path: String,
handler: Handler,
permissions: List[Permission],
authority: RouteAuthority,
limits: List[Limit]
)
struct ServiceDef(name: String, routes: List[Route])
def ok(body: String) -> Response:
Response(200, body)
def handler[a](name: String, run: a) -> Handler:
Handler(name, run)
def route(path: String, h: Handler, permissions: List[Permission]) -> Route:
Route(path, h, permissions, Authenticated, [])
def public_route(path: String, h: Handler, permissions: List[Permission]) -> Route:
Route(path, h, permissions, Anonymous, [])
def publishable_route(path: String, h: Handler, permissions: List[Permission]) -> Route:
Route(path, h, permissions, Publishable, [])
def role_route(path: String, h: Handler, roles: List[String], permissions: List[Permission]) -> Route:
Route(path, h, permissions, RequiresRoles(roles), [])
# `r` with one more limit, counted apart for each value of the named
# top-level request fields, which the route's body must have. Every limit
# on a route holds at once, so a short window can cap bursts while a long
# one caps the total.
def limited_by(r: Route, by: List[String], requests: Int, seconds: Int) -> Route:
Route(path, h, permissions, authority, limits) = r
Route(path, h, permissions, authority, [Limit(requests, seconds, by), *limits])
# `r` with one more limit, counted per account alone.
def limited(r: Route, requests: Int, seconds: Int) -> Route:
limited_by(r, [], requests, seconds)
def service_def(name: String, routes: List[Route]) -> ServiceDef:
ServiceDef(name, routes)