Users & Profiles

The public identity surface. A profile is the whole of what one user can see about another — email is never exposed through this API. Profiles are platform-wide (cross-app): one profile per user, resolved the same way from every app.

ts
type Profile = {
  id: string | null          // null for an account-deleted user
  displayName: string | null
  avatarUrl: string | null
  handle: string | null
}

SDK#

ts
await tallpond.auth.getUser()            // → { id, profile } | null (null when signed out)
await tallpond.users(id).profile()       // → Profile
await tallpond.users([id1, id2]).profiles() // → Record<id, Profile>
await tallpond.users.byHandle('alice')   // → Profile (with id) — 404 if unclaimed

Profiles are read-only for apps. updateProfile exists on the SDK but the gateway accepts it only from the tallpond dashboard (403 dashboard_only otherwise): the profile is central platform identity rendered by every app, so untrusted apps may never write it — the same rule as wallets and spend caps. Users are never asked to choose a handle: one is generated for them at signup and changed later, in the dashboard, if ever. They manage both fields there.

A user with no profile yet resolves to their id with null fields. An account-deleted user resolves to the reserved deleted-user profile ({ id: null, displayName: "Deleted user" }).

Handles#

Handles are the platform-central username: optional, unique across all of tallpond, 3–30 characters of a–z, 0–9, _. Because they're platform-wide, @alice is the same person in every app — apps should not hand-roll their own handle systems (app-local nicknames layered on top are fine; that's what non-unique displayName and your own tables are for).

The author(...) projection#

On mounted (creator-owned) tables, select accepts the reserved author(...) relation, which resolves the row's creator to their profile:

ts
await tallpond.resource(roomId)
  .table("messages")
  .select("body, author(displayName, avatarUrl)")

Each row gains an author object with the requested fields (any of id, displayName, avatarUrl, handle). The gateway resolves all rows' authors in one batched query. Rows whose creator has deleted their account resolve to the deleted-user profile — so every app renders deletion the same way, with no per-app null-checking. author(...) is rejected on private and resource-owned tables (they have no row creator).

Account deletion#

POST /v1/account/delete (dashboard only) deletes the caller's account across every app at once. For each resource the user contributed to, the mount's onOwnerDelete policy decides what the context sees afterward:

The user's private rows and all their files are then swept, resources they own are deleted (ownership transfer is a future option), and their profile is marked deleted. The append-only wallet ledger is retained for audit; session revocation is handled by the identity provider.