Skip to content
UniBox

Domain types

These are the only shapes that cross from a plugin into the app. They contain no provider names and no remote DTOs.

@unibox-cam/integration-sdk · domain.ts
// Provider-neutral domain types. No provider names may appear here.
export type IntegrationId = string
export type IntegrationType = string
export type IntegrationInstanceId = string
export type RemoteId = string
export type ContainerId = string
export type InventoryItemId = string
export interface InventoryContainer {
id: ContainerId
integrationInstanceId: IntegrationInstanceId
remoteId: RemoteId
name: string
description?: string
parentId?: ContainerId
/** Names from the root down to (and including) this container. */
path: string[]
itemCount?: number
imageUrl?: string
sourceUrl?: string
metadata: Record<string, unknown>
updatedAt?: string
syncedAt: string
}
export interface InventoryItem {
id: InventoryItemId
integrationInstanceId: IntegrationInstanceId
remoteId: RemoteId
name: string
description?: string
quantity?: number
quantityUnit?: string
containerId?: ContainerId
imageUrl?: string
sourceUrl?: string
sku?: string
serialNumber?: string
barcode?: string
tags?: string[]
metadata: Record<string, unknown>
updatedAt?: string
syncedAt: string
}
export interface InventorySearchQuery {
text: string
integrationInstanceIds?: IntegrationInstanceId[]
containerIds?: ContainerId[]
limit?: number
includeContainers?: boolean
includeItems?: boolean
}
interface SearchResultBase {
id: string
integrationInstanceId: IntegrationInstanceId
score: number
searchableText: string
matchedFields?: string[]
canLocatePhysically: boolean
}
export interface InventoryItemSearchResult extends SearchResultBase {
kind: 'item'
item: InventoryItem
container?: InventoryContainer
}
export interface InventoryContainerSearchResult extends SearchResultBase {
kind: 'container'
container: InventoryContainer
}
export type InventorySearchResult = InventoryItemSearchResult | InventoryContainerSearchResult
export type IntegrationConnectionStatus =
| { state: 'connected'; serverVersion?: string; apiVersion?: string }
| { state: 'disconnected' }
| { state: 'error'; error: import('./errors').IntegrationError }
export interface IntegrationInstance {
id: IntegrationInstanceId
integrationType: IntegrationType
displayName: string
baseUrl?: string
secureCredentialKey?: string
status: IntegrationConnectionStatus
pluginVersion?: string
serverVersion?: string
apiVersion?: string
lastSuccessfulSyncAt?: string
metadata: Record<string, unknown>
}
/** What the local (non-secret) database persists about an instance. */
export type StoredIntegrationInstance = Pick<
IntegrationInstance,
| 'id'
| 'integrationType'
| 'displayName'
| 'secureCredentialKey'
| 'pluginVersion'
| 'serverVersion'
| 'lastSuccessfulSyncAt'
>
  • Ids. id is unique across all sources; remoteId is the source’s own id. A good convention is ${instance.id}:c:${remoteId} for containers and …:i:… for items, which also keeps two sources from colliding.
  • Paths. A container’s path runs from the root down to and including itself, so path.at(-1) === name. If it has a parentId, the parent’s path is the same list without the last element.
  • Items point at containers. Every item returned for a container has containerId equal to that container’s id.
  • Only known fields. A container or item with a field outside the type fails the suite, so provider-specific extras cannot leak.
  • Metadata is plain data. metadata must survive JSON.parse(JSON.stringify(x)) unchanged: no class instances, functions or Date objects. Use it for extra data you want to keep; keep it small.
  • Plain objects. Returned values must be plain objects (Object.getPrototypeOf(x) === Object.prototype).
  • Timestamps. syncedAt is an ISO-8601 string; take it from the context’s clock, not Date.now(), so tests are deterministic.

A search result is either an item or a container. Both carry a score (higher is better), the searchableText that matched, and canLocatePhysically. An item result that can be located must carry its container, because that is what lets the app say where to point the camera.