Entity: Hosted Instances — data-farm
- Dominant language
- JavaScript
- Stars
- 400
- Forks
- 89
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 146
Description
# Entity: Hosted Instances — data-farm
**Parent:** Data Farm · **Depends on:** Applications pilot (store shape, subscriber file, `notifyEntityLifecycle` helper, Page/Component Loaders).
Hosted instances = "projects" (`p/` namespace). Status already flows into `live-status`; entity data does not. Reuses the Applications pattern — this doc notes where instances differ: three-source reconciliation, the entity↔status join, pagination, and (unlike Applications, which left secondary callers direct) the store owning **all** instance reads.
Four PRs (shape in 00): backend · store+consumer · subscriber · remaining-vue. Backend is independent; the rest depend on the store.
## Current state
- `mixins/Instance.js` fetches `getInstance()` in `created()` + optimistic `InstanceStateMutator`.
- `pages/team/Instances.vue` watches `team`, calls `getInstances()`, fetches statuses separately; polls (`InstanceStatusPolling`) when `!statusChannelLive`.
- Status is live (`p/+/state` → `live-status`); entity CRUD is not.
- `context.setInstance` cascades the owning application/device.
- Three sources of truth: API, `live-status`, optimistic mutator.
## Store: `stores/data-farm-hosted-instances.ts`
Route-lazy + team-scoped like Applications → list load-state drives a non-blocking Page Loader. Normalized (`byId`) — unlike Teams' flat list — because realtime CRUD and detail lookups are by id. **Single source for all instance reads:** every fetch (table, detail, pickers, dialogs) upserts into `instancesById`; views select/filter from it instead of fetching their own copy. Owns the entity↔status join (getters merge `live-status.instanceStatuses[id]`), and folds the optimistic mutator in so all three sources reconcile in one place.
| Kind | Name | Caller | PR |
| --- | --- | --- | --- |
| state | `instancesById` | normalized map | store |
| state | `teamInstanceIds` | loaded team's list (order + pagination) | store |
| state | `loadedTeamId` | ensure-guard | store |
| state | `isLoadingTeamInstances` | Page Loader | store |
| state | `activeInstanceId` | route's instance | remaining-vue |
| getter | `teamInstances` | Instances table | store |
| getter | `teamInstancesWithStatus` / `instanceWithStatus(id)` | joins `live-status` | store |
| getter | `activeInstance` | `context.instance` pass-through | remaining-vue |
| action | `ensureTeamInstancesLoaded(teamId)` → `getInstances` | table; guards nav | store |
| action | `fetchInstance(id)` → `getInstance` | resolve-only; reused by detail + lifecycle | store |
| action | `start`/`suspend`/`restart(id)` → upsert | actions; optimistic folded here | store |
| action | `createInstance` / `deleteInstance` → upsert/remove | **same-session fix** | store |
| action | `setActiveInstance(i)` | `context.setInstance` pass-through | remaining-vue |
| action | `applyRealtimeEvent(e)` → upsert/remove | subscriber | subscriber |
| internal | `upsertInstance` / `removeInstance` | mutations + realtime | store |
- Clear on team switch + logout (`clearOtherStores()` + `$reset`).
- `context.setInstance` keeps its owner cascade (orchestration); only the instance pointer delegates to the store — like Teams left `refreshTeam`'s analytics/router in context.
- Secondary readers (mixin detail, instance pickers, dialogs) go through the store too — varied query shapes read from `instancesById` with client-side filtering, or a scoped fetch that upserts. Bigger store + remaining-vue PRs than a table-only cache, but one source of truth.
Contributor guide
Assessment
This issue has not been assessed yet.