# Aurora BackendHandler — Developer Reference

This document is the authoritative developer guide for the `event-handler` library. It is written for a developer with zero prior context on this codebase. After reading it you will understand the architecture, every major module, and exactly how data flows from a UI action to a backend response and back.

---

## Table of Contents

1. [What this library does](#1-what-this-library-does)
2. [High-level architecture](#2-high-level-architecture)
3. [Entry point — `BackendHandler`](#3-entry-point--backendhandler)
4. [Event bus — `EventHelper`](#4-event-bus--eventhelper)
5. [WebSocket layer — `websocket-manager.ts`](#5-websocket-layer--websocket-managerts) ← main focus
6. [API service modules](#6-api-service-modules)
7. [Helper modules](#7-helper-modules)
8. [Utilities](#8-utilities)
9. [Configuration](#9-configuration)
10. [End-to-end data flow examples](#10-end-to-end-data-flow-examples)
11. [Adding a new feature — checklist](#11-adding-a-new-feature--checklist)

---

## 1. What this library does

The Aurora WUI (web frontend) never calls backend HTTP or WebSocket endpoints directly. Instead it fires **named events** into a global event bus. This library sits between that event bus and the backend, listening for those events, calling the correct API, and publishing the result back onto the bus so the UI can react.

```
┌──────────────────────────────────────────────┐
│                  WUI Frontend                │
│  publishes "create-tenant", listens for      │
│  "tenant-created" / "tenant-creation-error"  │
└────────────────────┬─────────────────────────┘
                     │ global event bus
┌────────────────────▼─────────────────────────┐
│             event-handler library            │
│  BackendHandler → EventHelper → API Services │
│                ↕                             │
│          websocket-manager.ts                │
│  (single WebSocket connection to backend)    │
└────────────────────┬─────────────────────────┘
                     │ WebSocket / HTTP
┌────────────────────▼─────────────────────────┐
│               Aurora Backend API             │
└──────────────────────────────────────────────┘
```

Key design decisions:
- **WebSocket-first** — almost all state mutations go through the WebSocket. HTTP is only used for the initial user load and a handful of multipart (file upload) operations.
- **Single connection** — one shared `WebSocket` instance for the whole application lifetime.
- **Request/response correlation** — every outgoing WS message gets a UUID (`messageId`). Incoming responses are matched by that ID to resolve the correct `Promise`.
- **Event-driven state** — the backend also pushes unsolicited "event" messages when the state of a resource changes (another user creates something, a deployment finishes, etc.). These are handled and merged into the local in-memory caches.

---

## 2. High-level architecture

```
backend-handler.ts          ← instantiated once by the consumer app
    │
    ├── EventHelper          ← wraps the global event bus
    │       └── createEvent  ← factory: returns { publish, subscribe, unsubscribe }
    │
    ├── API Services (api/)
    │       ├── user-api-service.ts
    │       ├── tenant-api-service.ts
    │       ├── account-api-service.ts
    │       ├── environment-api-service.ts
    │       ├── service-api-service.ts
    │       ├── marketplace-api-service.ts
    │       ├── resources-api-service.ts
    │       ├── planProvider-api-service.ts
    │       ├── reporting-api-service.ts
    │       └── organizations-api-service.ts
    │
    ├── websocket-manager.ts  ← owns the WebSocket connection + all in-memory maps
    │
    ├── helpers/              ← one file per entity domain
    │       ├── user-helper.ts
    │       ├── tenant-helper.ts
    │       ├── service-helper.ts
    │       ├── account-helper.ts
    │       ├── environment-helper.ts
    │       ├── resource-helper.ts
    │       ├── revision-helper.ts
    │       ├── plan-helper.ts
    │       ├── registry-helper.ts
    │       ├── token-helper.ts
    │       ├── link-helper.ts
    │       └── marketplace-helper.ts
    │
    └── utils/utils.ts        ← pure utility functions
```

**Module responsibility summary**

| Module | Responsibility |
|--------|---------------|
| `backend-handler.ts` | Wires subscriptions, owns subscription lifecycle, exports `eventHelper` singleton |
| `event-helper.ts` | Type-safe façade over the raw global event bus |
| `websocket-manager.ts` | WebSocket connection, pending-request registry, incoming message routing, in-memory entity caches |
| `api/*.ts` | Translate domain operations into WS or HTTP calls |
| `helpers/*.ts` | Process raw WS response payloads into domain objects |
| `utils/utils.ts` | Shared pure functions |

---

## 3. Entry point — `BackendHandler`

**File:** [backend-handler.ts](backend-handler.ts)

### Construction

```typescript
new BackendHandler(route, globalEventHandler, baseUrl, apiVersion)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `route` | `string` | Current UI route (used to subscribe the right events) |
| `globalEventHandler` | `any` | Must expose `.publish()`, `.subscribe()`, `.unsubscribe()` |
| `baseUrl` | `string` | Backend base URL, e.g. `https://api.example.com` |
| `apiVersion` | `string` | API version string, e.g. `v1` |

**What happens in the constructor:**

1. Validates that `globalEventHandler` has a `.subscribe` method — throws if not.
2. Creates the `EventHelper` singleton (exported as `eventHelper`).
3. Writes `baseUrl` and `apiVersion` into the shared `environment` object so all API services can read them.
4. Calls `getUserHTTP()` — an HTTP GET that loads basic user info without a token.
5. Inside the `.then()` callback it:
   a. Subscribes to `user.load` → calls `initializeGlobalWebSocketClient` (opens the WS).
   b. Subscribes to `user.loaded` → calls `updateUserComplete` (syncs in-memory state).
   c. Calls `changeRoute(route)` → subscribes all entity event listeners.
   d. Publishes `user.load` to kick off the WS init immediately.

### Route management

```typescript
handler.changeRoute(newRoute: string): void
```

Unsubscribes all current listeners, then re-subscribes them. In the current implementation all entity groups are always subscribed (the commented-out route-filtering code shows the intended future behaviour). This is safe because the underlying `createEvent` factory creates fresh `publish/subscribe/unsubscribe` closures each time.

```typescript
handler.subscribeForRoute(route: string): void
```

Calls the nine private `subscribe*Events()` methods:
- `subscribeTenantEvents()`
- `subscribeUserEvents()`
- `subscribeAccountEvents()`
- `subscribePlanEvents()`
- `subscribeEnvironmentEvents()`
- `subscribeServiceEvents()`
- `subscribeMarketplaceEvents()`
- `subscribeResourceEvents()`
- `subscribeOrganizationEvents()`

Each method registers a callback on the event bus for each action event (e.g. `tenant.creation`, `service.deploy`, …) that calls the matching API service function, and pushes an unsubscribe handle into `this.unsubscribeFunctions` for clean teardown.

```typescript
handler.isUserLoggedIn(): Promise<boolean>
```

Quick auth check — calls `isUserLogged()` over HTTP.

### Exported singleton

```typescript
export let eventHelper: EventHelper;
```

This is the single `EventHelper` instance used by the whole library. API services and `websocket-manager` import it from `backend-handler.ts` to publish results back to the UI.

---

## 4. Event bus — `EventHelper`

**File:** [event-helper.ts](event-helper.ts)

`EventHelper` is a typed façade over whatever global event bus the consumer application provides. It does **not** implement pub/sub itself — it delegates to `globalEventHandler.publish / subscribe / unsubscribe`.

### `createEvent<T>(eventName: string)`

Private factory method. Returns:

```typescript
{
  publish:     (data: T) => void,
  subscribe:   (callback: (payload: T) => void) => void,
  unsubscribe: (callback: (payload: T) => void) => void,
}
```

All event names come from the `EventNames` enum in `event-names.ts`, ensuring there are no typos or name clashes.

### Entity groups

Each entity domain is exposed as a getter that returns `{ publish, subscribe, unsubscribe }` objects with type-safe methods:

| Getter | Entity type | Example events |
|--------|-------------|---------------|
| `eventHelper.tenant` | `Tenant` | `creation`, `created`, `creationError`, `update`, `updated`, `delete`, `deleted`, `createRegistry`, `inviteUser`, `createToken`, … |
| `eventHelper.user` | `UserData` | `creation`, `load`, `loaded`, `update`, `delete`, `authError` |
| `eventHelper.account` | `Account` | `creation`, `update`, `delete`, `clean` + result variants |
| `eventHelper.environment` | `Environment` | `creation`, `update`, `delete`, `clean`, `scale` + result variants |
| `eventHelper.service` | `Service` | `deploy`, `deployed`, `update`, `delete`, `restart`, `changeRevision`, `restartInstance`, `updateServiceLinks`, `requestRevisionData` + error variants |
| `eventHelper.marketplace` | `MarketplaceItem` | `deployItem`, `itemDeployed`, `loadItems`, `itemsLoaded`, `loadSchema`, `schemaLoaded`, `schemaLoadError` |
| `eventHelper.resource` | `Resource` | `creation`, `update`, `delete` + result variants |
| `eventHelper.plan` | `string` | `upgrade`, `upgraded`, `upgradeError`, `downgrade`, `downgraded`, `downgradeError` |
| `eventHelper.organization` | `Organization` | `creation`, `update`, `delete` + result variants |
| `eventHelper.notification` | `Notification` | `creation`, `deletion`, `read` |
| `eventHelper.planProviders` | `PlanProvider[]` | `loadPlans`, `plansLoaded`, `loadError` |

**Convention:** every action has three events:
- `<action>` — intent (UI fires this to trigger an operation)
- `<action>d` / `<entity>Created` / `<entity>Updated` … — success (library fires this after backend confirms)
- `<action>Error` — failure (library fires this if backend returns an error)

---

## 5. WebSocket layer — `websocket-manager.ts`

**File:** [websocket-manager.ts](websocket-manager.ts)

This is the most complex and important file in the library. It owns:
- The single WebSocket connection
- The pending-request registry (request/response correlation)
- All in-memory entity caches (Maps)
- Routing of incoming server messages to the correct handler
- Publishing results back through `eventHelper`

### 5.1 Global state variables

```typescript
let wsConnection: WebSocket | null               // the live socket
let pendingRequests: Map<string, PendingOperation> // messageId → pending promise
let currentToken: string | null                  // token used to open current connection
let connectionPromise: Promise<WebSocket> | null // in-flight connect promise (prevents double-connect)
let userData: UserData                           // canonical user state
let user: User                                   // User domain object rebuilt from userData

// Entity caches — keyed by entity ID
let tenantsMap:       Map<string, Tenant>
let accountsMap:      Map<string, Account>
let environmentsMap:  Map<string, Environment>
let servicesMap:      Map<string, Service>
let revisionsMap:     Map<string, Revision>
let organizationsMap: Map<string, Organization>
let clusterTokensMap: Map<string, ClusterToken>
let tokenMap:         Map<string, Token>
let roleMap:          Map<string, Role[]>
let plansMap:         Map<string, Plan>
let secretsMap:       Map<string, any>

// Platform capability info (loaded once, cached)
let platformInfo: Platform | null
let isPlatformInfoLoading: boolean
let isPlatformInfoLoaded:  boolean

// Misc deferred/pending state
let pendingDomains:             Resource[]
let pendingEnvironments:        Array<{ tenant: string; env: Environment }>
let pendingRevisionErrors:      Array<{ service: string; revision: Revision }>
let pendingCloudProviderUpdates: Array<{ environmentId: string; accountId: string }>
let pendingRegistries:          Array<{ tenant: string; registry: Registry }>
let pendingProjects:            Array<{ tenant: string; project: string }>
let pendingTenantStatus:        Map<string, string>

// Reporting (usage metrics)
let isLoadingReporting:     boolean
let hasLoadedReportingOnce: boolean
const REPORTING_ITERATIONS = 5
const REPORTING_INTERVAL   = 1000  // ms

let recentlyUpdatedServices: Map<string, Service>
```

### 5.2 Core interfaces

#### `WSMessage`

Every message sent or received on the WebSocket conforms to this shape:

```typescript
interface WSMessage {
  messageId: string;   // UUID v7 — used to correlate requests to responses
  payload:   any;      // domain data
  topic:     string;   // "group:method", e.g. "tenant:create_tenant"
  type:      "request" | "multipartrequest" | "success" | "error" | "event";
  error?: {
    code?:    string;
    message?: string;
    content?: string;
  };
}
```

#### `PendingOperation`

Stored in `pendingRequests` while waiting for a server response:

```typescript
interface PendingOperation {
  resolve:         Function;  // resolves the Promise returned to the caller
  reject:          Function;  // rejects the Promise on error/timeout
  action:          string;    // e.g. "CREATE", "UPDATE", "DELETE", "GET_CHANNELS"
  entityType:      string;    // e.g. "tenant", "service", "account"
  entityName:      string;    // entity identifier
  originalData?:   any;       // the data sent in the request (for error context)
  responsePayload?: any;      // set when success response arrives
}
```

### 5.3 Exported functions

---

#### `initializeGlobalWebSocketClient`

```typescript
export const initializeGlobalWebSocketClient = async (
  token: string,
  type?: string,
  name?: string,
  previousData?: UserData,
): Promise<WebSocket>
```

Opens (or reuses) the global WebSocket connection.

**Logic:**

1. Copies `previousData.notifications` into local `userData` (preserves notification list across reconnects).
2. Stores `name` in `globalEntityName` (used later when processing the first data load).
3. **If already connected with the same token** — returns the existing socket immediately. No duplicate connections.
4. **If a connection is already in progress** (`connectionPromise` is set) — awaits it. This prevents a race condition when multiple callers try to connect simultaneously.
5. **If connected with a different token** — closes the old connection and resets `platformInfo` flags.
6. Constructs the WebSocket URL by replacing `http` with `ws` in `baseUrl`: `ws://host/api/<version>/ws`.
7. **Browser vs. Node.js** — uses the native `WebSocket` in browsers; dynamically `require('ws')` in Node.js (detected via `typeof window === "undefined"`).
8. Sets up three event listeners:

**`open`** — sets `currentToken`, clears `connectionPromise`, resolves the outer Promise.

**`message`** — the main message dispatcher:
```
incoming message
    │
    ├── type === "success" or "error"
    │       look up pendingRequests by messageId
    │           → handleOperationSuccess(pending, message)  OR
    │           → handleOperationError(pending, message.error)
    │           then delete from pendingRequests
    │
    └── type === "event"
            → handleEvent(message)
```

**`close`** — logs the close event, does not auto-reconnect (reconnection is left to the caller).

**`error`** — rejects `connectionPromise`, rejects all pending requests, cleans up state.

---

#### `makeGlobalWebSocketRequest`

```typescript
export const makeGlobalWebSocketRequest = async (
  topic: string,
  payload: any,
  timeout?: number,        // default: 30 000 ms
  petitionAction?: string, // stored in PendingOperation.action
  petitionInfo?: string,   // stored in PendingOperation.entityName
  entityType?: string,     // stored in PendingOperation.entityType
  originalData?: any,      // stored in PendingOperation.originalData
): Promise<any>
```

Sends a request on the WebSocket and returns a `Promise` that resolves when the server responds with a matching `messageId`.

**Mechanics:**

1. Generates a UUID v7 `messageId`.
2. Builds a `WSMessage` of type `"request"`.
3. Registers a `PendingOperation` in `pendingRequests` keyed by `messageId`.
4. Sets a timeout — on expiry, rejects the promise with a timeout error and removes the entry from `pendingRequests`.
5. Sends the JSON-serialised message through `wsConnection.send()`.
6. When the `"message"` listener receives a `success`/`error` with the matching `messageId`, it calls `resolve`/`reject` and removes the entry.

**Topic format:** `"group:method"` — for example:
- `"tenant:create_tenant"`
- `"service:delete_service"`
- `"account:create_account"`

---

#### `makeGlobalWebSocketRequestWithRetry`

```typescript
export const makeGlobalWebSocketRequestWithRetry = async (
  topic: string,
  payload: any,
  maxRetries?: number,  // default: 3
  timeout?: number,
  ...same as makeGlobalWebSocketRequest
): Promise<any>
```

Wraps `makeGlobalWebSocketRequest` with **exponential backoff**:
- Attempt 1: immediate
- Attempt 2: 1 000 ms delay
- Attempt 3: 2 000 ms delay
- Attempt 4: 4 000 ms delay (capped)

Used for operations that may transiently fail due to network conditions.

---

#### `getWebSocketStatus`

```typescript
export const getWebSocketStatus = (): {
  connected: boolean;
  readyState?: number;
  pendingRequests: number;
  currentToken: string | null;
}
```

Diagnostic snapshot of the connection. Useful for debugging and health checks.

---

#### `closeGlobalWebSocketConnection`

```typescript
export const closeGlobalWebSocketConnection = (): void
```

Explicitly closes the socket. Rejects all pending requests with `"Connection closed"` before closing.

---

#### `updateUserComplete`

```typescript
export const updateUserComplete = (updatedUserData: UserData): User
```

Called every time the backend sends a fresh full-user payload (after WS connect, after a reload). Performs:

1. **Token merge** — `updatedUserData.tokens` are merged with any tokens already cached in `userData.tokens` so nothing is lost across partial updates.
2. Overwrites `userData` with the new data.
3. Rebuilds the `User` domain object.
4. Rebuilds tenant/account/environment/service hierarchy via `rebuildHierarchy()`.
5. Loads platform capabilities via `loadPlatformInfo()`.
6. Starts background reporting via `loadAllEnvironmentsReporting()`.
7. Publishes the updated `User` through `eventHelper.user.publish.loaded(user)`.

---

#### `fetchAndStoreMarketplaceSchema`

```typescript
export const fetchAndStoreMarketplaceSchema = async (
  item: MarketplaceItem,
): Promise<void>
```

Fetches the deployment schema for a marketplace item, stores the result in `tenantsMap` (inside the corresponding tenant's marketplace items list), and calls `rebuildHierarchy()` so the UI sees the updated item immediately.

---

### 5.4 Internal routing functions

These are not exported but are the core of the event-processing engine.

---

#### `handleEvent(message: WSMessage)`

Handles messages of `type === "event"` — unsolicited push notifications from the server.

**Routing logic:**

```
message.topic
    │
    ├── contains "deleted" or key-path ends with entity
    │       → handleDeleteEvent(message)
    │
    ├── "user:*"         → handleUserEvent(userData, message, ...)
    ├── "tenant:*"       → handleTenantEvent(tenantsMap, message, ...)
    ├── "service:*"      → handleServiceEvent(servicesMap, message, ...)
    ├── "account:*"      → handleAccountEvent(accountsMap, message, ...)
    ├── "environment:*"  → handleEnvironmentEvent(environmentsMap, message, ...)
    ├── "revision:*"     → handleRevisionEvent(revisionsMap, message, ...)
    ├── "domain:*"       → handleDomainEvent(...)
    ├── "port:*"         → handlePortEvent(...)
    ├── "secret:*"       → handleSecretEvent(...)
    ├── "volume:*"       → handleVolumeEvent(...)
    ├── "certificate:*"  → handleCertificateEvent(...)
    ├── "ca:*"           → handleCAEvent(...)
    ├── "plan:*"         → handlePlanInstanceEvent(...)
    ├── "registry:*"     → handleRegistryEvent(...)
    ├── "token:*"        → handleTokenEvent(...)
    └── "link:*"         → handleLinkEvent(...)
```

After every event that modifies entity state, `handleEvent` calls:
- `rebuildHierarchy()` — re-links all entities into the User hierarchy.
- `loadPlatformInfo()` — ensures platform capabilities are current (cached; loads only once).

---

#### `handleOperationSuccess(operation: PendingOperation, response: WSMessage)`

Called when an outgoing request receives a `success` response. Routes by `operation.entityType`:

| `entityType` | Handler |
|--------------|---------|
| `"tenant"` | `handleTenantOperationSuccess(...)` |
| `"service"` | `handleServiceOperationSuccess(...)` |
| `"account"` | `handleAccountOperationSuccess(...)` |
| `"environment"` | `handleEnvironmentOperationSuccess(...)` |
| `"resource"` | `processResourceResult(...)` |
| `"token"` | `handleTokenOperationSuccess(...)` |
| `"user"` | Calls `updateUserComplete(response.payload)` |

Special-cased actions that bypass the entity-type routing:
- `"GET_CHANNELS"` — parses channel data, maps it onto the service, resolves the promise with channel info.
- `"GET_REVISION"` — processes revision schema via `processRevisionData(...)`.
- `"UPDATE_CONFIG"` — triggers a service config refresh.

After every success: calls `rebuildHierarchy()` to propagate the change to the full User object.

---

#### `handleOperationError(operation: PendingOperation, error: WSMessage["error"])`

Called when an outgoing request receives an `error` response. Routes by `operation.entityType` to the matching `handle*OperationError(...)` function in the helpers. Each helper publishes the appropriate error event through `eventHelper` and creates an error `Notification`.

---

#### `handleDeleteEvent(message: WSMessage)`

Processes deletion events pushed from the server. The message payload contains a hierarchical key path such as `/tenant/mytenant/service/myservice`.

1. Parses the key path via `parseKeyPath()` to extract entity types and names.
2. Deletes the entity from the appropriate Map (`tenantsMap`, `servicesMap`, etc.).
3. For services: also deletes all related revisions from `revisionsMap`.
4. Publishes the appropriate `deleted` event through `eventHelper`.
5. Creates a deletion `Notification`.
6. Calls `rebuildHierarchy()`.

---

### 5.5 State management helpers

#### `rebuildHierarchy()`

Reconstructs the full nested object tree from the flat Maps:

```
tenantsMap
  └── for each Tenant
        └── accountsMap (filter by tenant)
              └── environmentsMap (filter by account)
                    └── servicesMap (filter by environment)
                          └── revisionsMap (filter by service)
```

Produces a new `User` object and publishes it via `eventHelper.user.publish.loaded(user)`. This is how the UI always has a consistent view of the full state.

Called after every mutation — both from operation results and from push events.

---

#### `loadPlatformInfo()`

Fetches the platform's capabilities (supported cloud providers, features, limits) from the backend. Results are stored in `platformInfo`. Uses `isPlatformInfoLoading` / `isPlatformInfoLoaded` flags to ensure it is fetched only once per connection, regardless of how many concurrent callers invoke it.

---

#### `updateUserWithPlatformInfo()`

Merges `platformInfo` into the `User` object and re-publishes. Called after both `rebuildHierarchy()` and `loadPlatformInfo()` complete.

---

#### `loadAllEnvironmentsReporting()`

Background polling loop. Runs `REPORTING_ITERATIONS` (5) times with `REPORTING_INTERVAL` (1 000 ms) between iterations. For each environment in `environmentsMap`, calls `getReporting(env)` to fetch CPU/memory usage metrics and merges results back into the environment objects. Uses `isLoadingReporting` to prevent overlapping runs.

---

#### `resolveServiceStatus(service: Service): string`

Determines the effective status of a service by inspecting its current revision state (running, deploying, failed, etc.). Used after revision events to keep service status consistent.

---

#### `getChannelsInfo(service: Service, token: string): Promise<Channel[]>`

Sends a `GET_CHANNELS` WS request for a service. Returns channel details (protocol, port, host). Called after a service is deployed or updated, so the UI immediately has connectivity info.

---

#### `updateEnvironmentsCloudProvider(accountId: string, provider: string)`

When a cloud-provider update event arrives for an account, propagates the new provider value to all environments that belong to that account. Then calls `rebuildHierarchy()`.

---

#### `safeStringifyError(error: any): string`

Pure helper that extracts a readable string from any error value — handles strings, Error objects, objects with `message` / `content` / `description` / `error` fields, and falls back to `JSON.stringify`. Used everywhere an error needs to go into a `Notification`.

---

### 5.6 Message flow diagram

```
Consumer UI
    │  publishes service.deploy(serviceData)
    ▼
BackendHandler.subscribeServiceEvents()
    │  calls deployService(serviceData, token)
    ▼
service-api-service.ts
    │  calls makeGlobalWebSocketRequest("service:deploy_service", payload, ...)
    ▼
websocket-manager.ts
    │  generates messageId = uuid-v7
    │  stores PendingOperation in pendingRequests
    │  sends JSON over wsConnection
    │  sets timeout timer
    ▼
Aurora Backend
    │  processes request
    │  sends back { messageId, type: "success", payload: {...} }
    ▼
wsConnection "message" event
    │  parses JSON → WSMessage
    │  finds PendingOperation by messageId
    │  deletes entry from pendingRequests
    │  calls handleOperationSuccess(pending, message)
    ▼
handleOperationSuccess
    │  routes to handleServiceOperationSuccess(...)
    │  updates servicesMap
    │  calls rebuildHierarchy() → publishes updated User
    │  calls eventHelper.service.publish.deployed(service)
    ▼
Consumer UI
    receives service.deployed event + updated User
```

---

## 6. API service modules

These modules translate domain operations into WebSocket or HTTP calls. They are called exclusively by `BackendHandler`'s subscription callbacks.

### `api/user-api-service.ts`

| Function | Transport | Description |
|----------|-----------|-------------|
| `getUserHTTP()` | HTTP GET | Loads minimal user info to bootstrap the app before WS is open |
| `loadUser(token, previousData)` | WS | Opens WS connection, then calls `loadUserData()` |
| `loadUserData()` | WS (many requests) | Fetches the full user graph: tenants, accounts, environments, services, revisions, resources, marketplace items, organizations, platform info — builds `UserData` and calls `updateUserComplete()` |
| `isUserLogged(token)` | HTTP | Auth check |
| `createUser(user)` | HTTP POST | Creates new user account |
| `updateUser(user)` | WS | Updates user profile |
| `deleteUSer(user, token)` | WS | Deletes user account |

`loadUserData()` is the most important function here. It makes a sequence of WS requests to assemble a complete `UserData` object. The order matters — tenants first, then accounts per tenant, then environments per account, etc. This is the "initial hydration" that populates all the Maps in `websocket-manager.ts`.

### `api/tenant-api-service.ts`

All operations use `makeGlobalWebSocketRequest` with topic `"tenant:<action>"`.

| Function | Action |
|----------|--------|
| `createTenant(tenant, security)` | `tenant:create_tenant` |
| `updateTenant(tenant, security)` | `tenant:update_tenant` |
| `deleteTenant(tenant, security)` | `tenant:delete_tenant` |
| `createRegistry(tenant, security, registry)` | `tenant:create_registry` |
| `updateRegistry(tenant, registry, security, force?)` | `tenant:update_registry` |
| `deleteRegistry(tenant, registry, security, force?)` | `tenant:delete_registry` |
| `inviteUser(tenant, email, role, security)` | `tenant:invite_user` |
| `removeUser(tenant, userId, security)` | `tenant:remove_user` |
| `updateUserRole(tenant, userId, role, security)` | `tenant:update_user_role` |
| `acceptInvite(tenant, security)` | `tenant:accept_invite` |
| `rejectInvite(tenant, security, leave)` | `tenant:reject_invite` |
| `createToken(tenantName, description, expiration, security)` | `tenant:create_token` |
| `deleteToken(tenantName, tokenId, security)` | `tenant:delete_token` |

HTTP variants (`createTenantHTTP`, `updateTenantHTTP`, `deleteTenantHTTP`) exist for cases where the WS is not yet available.

### `api/account-api-service.ts`

Operations use topic `"account:<action>"`. The `createAccount` payload includes provider-specific credentials and resource marks (CPU/memory/storage limits) and IaaS flavors. Supported providers: AWS, Azure, OVH, OASix, CloudOS, OpenNebula.

| Function | Description |
|----------|-------------|
| `createAccount(account, security)` | Creates cloud account with all provider credentials |
| `updateAccount(account, security)` | Modifies account config |
| `deleteAccount(account, security)` | Removes account |
| `clearAccount(account, security)` | Frees resources without deleting the account |

### `api/environment-api-service.ts`

Operations use topic `"environment:<action>"`.

| Function | Description |
|----------|-------------|
| `createEnvironment(env, security)` | Creates deployment environment (primary or secondary type, HA config) |
| `updateEnvironment(env, security)` | Modifies environment |
| `deleteEnvironment(env, security)` | Deletes environment |
| `clearEnvironment(env, security)` | Clears resources |
| `scaleEnvironment(env, security)` | Adjusts node/replica counts |

### `api/service-api-service.ts`

The most complex API service. Service deployments use **HTTP multipart** (to upload DSL/bundle files); all other operations use WebSocket.

| Function | Transport | Description |
|----------|-----------|-------------|
| `deployService(data, token)` | HTTP multipart POST | Deploys a service from a DSL or bundle file. Optionally performs a dry-run or validation pass first. Links services post-deployment. |
| `redeployService(data)` | HTTP POST | Re-deploys from an existing revision |
| `deleteService(data, security)` | WS | Deletes service + removes its links |
| `restartService(data, security)` | WS | Restarts a service |
| `requestRevisionData(service, token)` | WS (`GET_REVISION`) | Fetches revision schema and config |
| `updateService(data, token, scale?)` | WS | Updates config: parameters, resources, resilience, auto-scaling per role |
| `updateServiceLinks(link, token)` | WS | Creates or removes inter-service link |
| `changeRevision(data, token)` | WS | Rolls back to a previous revision |
| `restartInstance(service, roleId, instanceId, token)` | WS | Restarts a specific service instance |
| `linkPendingServices(service, token)` | WS | Called automatically after deploy if the service has pending link targets |
| `unlinkServices(service, token)` | WS | Removes links between services |

### `api/marketplace-api-service.ts`

| Function | Transport | Description |
|----------|-----------|-------------|
| `deployMarketplaceItem(item)` | HTTP multipart | Deploys a catalog item. Handles complex deployments with public channels, variants (Inbound with HTTP/HTTPS/TCP), parameter mapping, resource binding, scaling rules. |
| `getMarketplaceItems(tenants, security)` | WS | Fetches available catalog items per tenant. Parses CPU/memory requirements from tags. |
| `getMarketplaceSchema(tenant, item, security)` | WS | Gets the full deployment schema for an item (parameters, resources, channels, variants, role definitions). |
| `loadMarketplaceItemsForTenant(tenant, security)` | WS | Variant used during WS-driven initial load. |

### `api/resources-api-service.ts`

Handles all resource types: `domain`, `port`, `ca`, `certificate`, `secret`, `volume`. Each `create*` function sends a typed WS payload with resource-specific fields.

| Function | Description |
|----------|-------------|
| `createResource(tenant, resource, security)` | Routes to the type-specific create function |
| `createDomain(tenant, domain, security)` | Creates a DNS domain resource |
| `createPort(tenant, port, security)` | Creates an exposed port resource |
| `createCA(tenant, ca, security)` | Creates a Certificate Authority |
| `createCertificate(tenant, cert, security)` | Creates a TLS certificate (requires cert + key + domain) |
| `createSecret(tenant, secret, security)` | Creates an opaque secret |
| `createVolume(tenant, volume, security)` | Creates a volume (persistent / nonReplicated / volatile) |
| `deleteResource(tenant, resource, security)` | Deletes any resource type |
| `updateResource(tenant, resource, security)` | Updates any resource type (validates type before sending) |

### `api/planProvider-api-service.ts`

| Function | Description |
|----------|-------------|
| `getPlanProviders(token)` | Fetches available subscription plans. Publishes result via `eventHelper.planProviders.publish.plansLoaded(plans)`. |

### `api/reporting-api-service.ts`

| Function | Description |
|----------|-------------|
| `getReporting(environment)` | HTTP GET for CPU/memory usage metrics for one environment |

---

## 7. Helper modules

Located in [helpers/](helpers/). Each file processes raw WS response payloads for one entity domain. They are called from `websocket-manager.ts` and never called directly by API services or `BackendHandler`.

| File | Responsibility |
|------|---------------|
| `user-helper.ts` | `handleUserEvent`, `handleUserOperationError` — processes user push events and user operation errors |
| `tenant-helper.ts` | `handleTenantEvent`, `handleTenantOperationSuccess` — processes tenant push events and maps creation/update results into `tenantsMap` |
| `service-helper.ts` | `handleServiceEvent`, `handleServiceOperationSuccess`, `handleServiceOperationError`, `mapChannelsFromApiData` — the most complex helper; handles deployment results, status resolution, channel mapping |
| `account-helper.ts` | `handleAccountEvent`, `handleAccountOperationSuccess`, `handleAccountOperationError`, `syncAccountStatusFromNotifications` |
| `environment-helper.ts` | `handleEnvironmentEvent`, `handleEnvironmentOperationSuccess`, `handleEnvironmentOperationError` |
| `resource-helper.ts` | `handleDomainEvent`, `handlePortEvent`, `handleSecretEvent`, `handleVolumeEvent`, `handleCertificateEvent`, `handleCAEvent`, `processResourceResult` — one handler per resource type |
| `revision-helper.ts` | `handleRevisionEvent`, `processRevisionData` — maps raw revision API data into `Revision` domain objects |
| `plan-helper.ts` | `handlePlanInstanceEvent`, `updateUserPlansAfterPlanEvent` — handles plan subscription changes |
| `registry-helper.ts` | `handleRegistryEvent` — handles Docker registry push events |
| `token-helper.ts` | `handleTokenEvent`, `handleTokenOperationSuccess` — handles API token create/delete operations |
| `link-helper.ts` | `handleLinkEvent` — updates service link state in `servicesMap` |
| `marketplace-helper.ts` | `processMarketplaceSchemaResponse` — parses raw marketplace schema API responses into typed structures |

**Pattern common to all helpers:**

```typescript
export function handleXxxOperationSuccess(
  map: Map<string, Xxx>,
  operation: PendingOperation,
  response: WSMessage,
  eventHelper: EventHelper,
) {
  // 1. Parse response.payload into a domain object
  // 2. Update the Map
  // 3. Publish the result event: eventHelper.xxx.publish.created(domainObj)
  // 4. Create a Notification: eventHelper.notification.publish.creation(notification)
}
```

---

## 8. Utilities

**File:** [utils/utils.ts](utils/utils.ts)

| Function | Signature | Description |
|----------|-----------|-------------|
| `getTimestamp` | `(val: any) => number` | Parses any timestamp representation (epoch ms, epoch s, ISO string, Date) to milliseconds |
| `parseKeyPath` | `(key: string) => Record<string, string>` | Converts a hierarchical key path like `/tenant/mytenant/service/myservice` into `{ tenant: "mytenant", service: "myservice" }`. Used in `handleDeleteEvent` to determine which entity was deleted. |
| `decodeRegistryAuth` | `(base64Secret: string) => { username: string; password: string }` | Decodes a base-64 Docker registry auth secret into credentials |
| `convertToGigabytes` | `(size: number, unit: string) => number` | Converts storage sizes in any SI/IEC unit (k, M, G, T, P, E, Ki, Mi, Gi, Ti, Pi, Ei) to gigabytes |

---

## 9. Configuration

**File:** [environment.ts](environment.ts)

```typescript
export const environment = {
  apiServer: {
    baseUrl:    '',   // set by BackendHandler constructor
    apiVersion: '',   // set by BackendHandler constructor
  },
};
```

This is a module-level singleton. `BackendHandler` writes to it once during construction, and all API services and `websocket-manager.ts` read from it. This is the only configuration surface — there are no `.env` files consumed by this library.

**tsconfig.json** — key settings:
- `target: "ES2020"` — uses native async/await and Map/Set
- `module: "ESNext"` — ESM output
- `moduleResolution: "bundler"` — expects a bundler (Vite, webpack, etc.)
- `strict: true` — full TypeScript strict mode
- External types come from `@kumori/aurora-interfaces` (not part of this repo)

---

## 10. End-to-end data flow examples

### Example A — Create a Tenant

```
1. UI: eventHelper.tenant.publish.creation(tenantData)

2. BackendHandler.subscribeTenantEvents:
       creationCb receives tenantData
       calls createTenant(tenantData, token)

3. tenant-api-service.createTenant:
       calls makeGlobalWebSocketRequest(
         "tenant:create_tenant",
         { name: tenantData.name, plan: tenantData.plan, ... },
         30000,
         "CREATE",
         tenantData.name,
         "tenant",
         tenantData
       )

4. websocket-manager:
       messageId = uuidv7()
       pendingRequests.set(messageId, {
         resolve, reject, action: "CREATE",
         entityType: "tenant", entityName: tenantData.name
       })
       wsConnection.send(JSON.stringify(message))
       setTimeout(reject, 30000)

5. Backend sends: { messageId, type: "success", payload: { name: "...", ... } }

6. websocket-manager "message" handler:
       pending = pendingRequests.get(messageId)
       pendingRequests.delete(messageId)
       handleOperationSuccess(pending, message)

7. handleOperationSuccess → handleTenantOperationSuccess:
       creates Tenant domain object from payload
       tenantsMap.set(tenant.name, tenant)
       eventHelper.tenant.publish.created(tenant)
       eventHelper.notification.publish.creation({ type: "success", subtype: "tenant-created", ... })
       rebuildHierarchy() → eventHelper.user.publish.loaded(updatedUser)

8. UI receives:
       tenant.created event with new Tenant
       notification.creation event
       user.loaded event with updated User tree
```

---

### Example B — Server pushes a deletion event

```
1. Backend sends: {
     type: "event",
     topic: "service:deleted",
     payload: { key: "/tenant/acme/account/prod/environment/env1/service/api" }
   }

2. wsConnection "message" handler:
       message.type === "event"
       handleEvent(message)

3. handleEvent → handleDeleteEvent(message):
       parseKeyPath("/tenant/acme/account/prod/environment/env1/service/api")
       → { tenant: "acme", account: "prod", environment: "env1", service: "api" }
       servicesMap.delete("api")
       revisionsMap: delete all entries belonging to "api"
       eventHelper.service.publish.deleted({ name: "api", tenant: "acme" })
       eventHelper.notification.publish.creation({ type: "success", subtype: "deployment-deleted", ... })
       rebuildHierarchy()
```

---

### Example C — Initial app load

```
1. new BackendHandler(route, globalEventHandler, baseUrl, apiVersion)
       → getUserHTTP() [HTTP GET /user]

2. HTTP response arrives:
       → eventHelper.user.publish.load({} as UserData)

3. user.load subscriber (backend-handler.ts):
       → loadUser(token, data)
           → initializeGlobalWebSocketClient(token)  [opens WS]
           → loadUserData()

4. loadUserData() sends sequential WS requests:
       user:get_user
       tenant:get_tenants
       for each tenant:
           account:get_accounts
           for each account:
               environment:get_environments
               for each environment:
                   service:get_services
                   ...
       marketplace:get_items
       organization:get_organizations
       platform:get_info

5. After all requests complete:
       assembles UserData
       calls updateUserComplete(userData)

6. updateUserComplete:
       merges tokens
       populates all Maps (tenantsMap, accountsMap, ...)
       rebuildHierarchy()
       loadPlatformInfo()
       loadAllEnvironmentsReporting()
       eventHelper.user.publish.loaded(user)

7. UI receives user.loaded with the full populated User object
```

---

## 11. Adding a new feature — checklist

When adding support for a new entity type or operation, follow these steps:

1. **Add event names** to [event-names.ts](event-names.ts) — add an entry to the `EventNames` enum for each event (`CreateFoo`, `FooCreated`, `FooCreationError`, …).

2. **Extend EventHelper** in [event-helper.ts](event-helper.ts) — add a new getter that exposes `publish`, `subscribe`, and `unsubscribe` objects using the new event names.

3. **Create an API service** in `api/foo-api-service.ts` — implement functions that call `makeGlobalWebSocketRequest` with the correct topic and payload shape.

4. **Create a helper** in `helpers/foo-helper.ts` — implement `handleFooOperationSuccess`, `handleFooOperationError`, and `handleFooEvent`. These functions update the relevant Map and publish events.

5. **Extend `websocket-manager.ts`** — add a new Map for the entity, import and call the new helper from `handleOperationSuccess`, `handleOperationError`, and `handleEvent`. Update `rebuildHierarchy()` if the new entity participates in the hierarchy.

6. **Extend `BackendHandler`** in [backend-handler.ts](backend-handler.ts) — add a `subscribeFooEvents()` method that wires the UI action events to the API service functions. Call it from `subscribeForRoute()`.

7. **Add to `@kumori/aurora-interfaces`** if you need new domain types — that external package defines `User`, `Tenant`, `Service`, etc.

---

*Last updated: 2026-05-08*
