Skip to content

Latest commit

 

History

History
103 lines (90 loc) · 6.81 KB

README.md

File metadata and controls

103 lines (90 loc) · 6.81 KB

Examples

Eight applications built on Garuda's public API. Seven are small enough to read in one sitting, each showing one thing; the starter shows the shape of a whole application and is the one to copy when beginning. They build against the Garuda checkout this directory sits in.

Example Run What it shows
Todo swift run todo A JSON CRUD API on SQLite (GarudaSQLite): typed routes, validation answered as 422 and 409, migrations, paging
Auth swift run auth Sign-up, login and logout: PBKDF2 password hashes, random session tokens stored as digests, authenticate(bearer:state:), an extractor of its own that awaits
Streaming swift run streaming Server-sent events, a CSV export written as it is produced, an upload read as it arrives and written to disk with a body limit of its own, and an offloaded route
Chat swift run chat Rooms over WebSockets and server-sent events, heard across every shard through Topic
WebTransport swift run webtransport -- --tls-cert cert.pem --tls-key key.pem A network probe over HTTP/3: datagram round trips, download and upload throughput on streams, a stream the server opens, an extractor that refuses a session before it starts, and a browser page that trusts a self-signed certificate by its hash
Files swift run files A file service: resumable uploads (GarudaUploads) with size limits and SHA-256 digests, the files served back by --static-dir with byte ranges, ETag and a listing
Uploads swift run uploads Photo uploads per user: resumable uploads inside group("/users/:user") behind bearer tokens, onCreate recording whose upload it is, the bytes checked for the image type they claim, and an answer with a Location replayed to a client that lost it
Starter swift run starter A whole application on PostgreSQL (GarudaPostgres): accounts with JWT access and refresh tokens, roles and admin-only routes, input rules answered as 422, migrations, configuration from the environment, OpenAPI, health and readiness, and a deployment recipe
cd Examples
swift run chat -- --port 8080 --shards 4      # then open http://localhost:8080
swift test                                    # every example, through app.test

createdb starter
DATABASE_URL='postgres://you@127.0.0.1:5432/starter?sslmode=disable' \
    swift run starter serve -- --port 8080    # then open http://localhost:8080/docs

The starter has its own guide, STARTER.md: its layout, configuration, migrations, the per-shard model, and how to deploy it. Its database tests run against the PostgreSQL in GARUDA_POSTGRES, as Garuda's own do, and are skipped without it.

Everything after -- is Garuda's own flags (CONFIG.md): TLS, HTTP/3, shards and threads, compression, rate limits. Each main.swift has curl commands to try.

How each one is laid out

An example is a library target with one function that builds its Application, and an executable whose main.swift calls it and runs it:

// Sources/todo/main.swift
todoApp(databasePath: path).run()

The function is what the tests call, so the tests exercise exactly the routes the executable serves, through the real engine. Configuration an application reads from its environment (a database path, a directory) is read in main.swift and passed in.

The package depends on Garuda's products -- Garuda, and GarudaSQLite, GarudaPostgres, GarudaUploads and GarudaTesting where an example needs them -- and on Vega, the runtime Garuda is built on, for what Garuda does not export: SHA-256 (VegaCrypto, in the webtransport example), a runtime for work outside the server (Vega, in the starter's withServices), and file system calls that run on the blocking pool (VegaFS, in the streaming, files and uploads examples).

Notes for copying

  • State is per shard. Garuda runs a process per shard, and each shard serves on several threads. app.state builds a database handle in each shard as it starts; nothing made before run() is shared between them, and what a shard's threads share is Sendable. What every shard must see lives outside the process: the SQLite file, a Topic.
  • SQLite with several shards works: each shard has its own connections to the same file, in write-ahead-log mode, and writes wait for each other. For more write traffic than one file takes, use PostgreSQL; the handler code looks the same.
  • Passwords cost CPU on purpose. The auth example hashes with 600,000 iterations on Vega's blocking pool. Run it with --rate-limit, and behind TLS.
  • Blocking work goes off the shard's threads. A handler that blocks, as the streaming example's stat does, is an app.offload route and runs on the blocking pool. VegaFS's calls, which the uploads use, run there by themselves.
  • Uploads in the streaming example are read as they arrive, with onStreamingBody, and go to a partial file that is renamed when complete. Every other route is given its body whole, so for those the route's maxBodySize is also what a request may hold in memory; a streaming route's limit is only how much it takes. Serve the directory with a separate route or --static-dir if they should be downloadable.
  • Resumable uploads in the files example are app.resumableUploads: the body goes to disk as it arrives, a client that speaks the draft protocol can ask how much arrived and send the rest after a dropped connection, and the handler is called once, with the whole file on disk, to move it into place. The files go back out through --static-dir, which main.swift adds to the command line, so the application writes no download code at all.
  • Whose upload it is is decided once, on the request that creates it. The request that completes an upload is a later one, perhaps on another connection; the uploads example's onCreate records the owner from the group's authentication, and the completion handler reads it from upload.metadata, which no client can write. Content-Type and the file name are the client's word, so the example checks the first bytes before it keeps a photo.
  • WebTransport needs HTTP/3, and so TLS. A browser trusts a self-signed certificate over WebTransport only through serverCertificateHashes, and only an ECDSA one valid for at most 14 days; the webtransport example's main.swift has the openssl line. A session lives on one shard, so sessions that must hear each other need something outside the shard to carry it, as the chat example does with Topic.