Skip to content
UniBox

The runtime

A plugin’s create returns a runtime: the live connection to one configured source. The app creates one runtime per source and calls these methods. Every failure must reject with an IntegrationError; never let a raw exception out.

@unibox-cam/integration-sdk · plugin.ts
import type {
IntegrationConnectionStatus,
IntegrationInstance,
InventoryContainer,
InventoryItem,
InventorySearchQuery,
InventorySearchResult,
RemoteId
} from './domain'
import type { IntegrationContext } from './services'
import type { IntegrationManifest } from './manifest'
export interface IntegrationConnectionResult {
status: IntegrationConnectionStatus
serverVersion?: string
apiVersion?: string
}
export interface GetContainersOptions {
parentRemoteId?: RemoteId
signal?: AbortSignal
}
export interface GetContainerContentsOptions {
limit?: number
offset?: number
signal?: AbortSignal
}
export interface SyncOptions {
since?: string
signal?: AbortSignal
}
export interface SyncResult {
containers: InventoryContainer[]
items: InventoryItem[]
syncedAt: string
}
/** Active connection and provider behavior. All failures must reject with an IntegrationError. */
export interface InventoryIntegrationRuntime<TCredentials = unknown> {
connect(credentials: TCredentials): Promise<IntegrationConnectionResult>
disconnect(): Promise<void>
testConnection(): Promise<IntegrationConnectionStatus>
getContainers(options?: GetContainersOptions): Promise<InventoryContainer[]>
getContainer(remoteId: RemoteId): Promise<InventoryContainer>
getContainerContents(remoteContainerId: RemoteId, options?: GetContainerContentsOptions): Promise<InventoryItem[]>
getItem(remoteId: RemoteId): Promise<InventoryItem>
search?(query: InventorySearchQuery): Promise<InventorySearchResult[]>
getDeepLink?(resource: InventoryContainer | InventoryItem): string | null
sync?(options?: SyncOptions): Promise<SyncResult>
}
export interface InventoryIntegrationPlugin<TConfig = unknown, TCredentials = unknown> {
readonly manifest: IntegrationManifest
create(
context: IntegrationContext,
instance: IntegrationInstance,
config: TConfig
): InventoryIntegrationRuntime<TCredentials>
}
Method Rules
connect(credentials) Sign in. Wrong credentials reject with authentication-failed; an unreachable server with network-unreachable. Resolve with a connected status and, if known, the server version.
disconnect() Forget the session. Idempotent: calling it twice must not fail.
testConnection() Report, don’t throw: resolve with { state: 'connected' } or { state: 'error', error }. Used for “is this source still working?”.
getContainers() Every container, each with a root-to-leaf path whose last element is its own name. Use parentRemoteId to list one level if the source supports it.
getContainer(remoteId) One container. An unknown id rejects with not-found.
getContainerContents(remoteContainerId) The items directly in that container, each pointing back at it through containerId. An unknown container rejects with not-found; it must not return an empty list. Honour limit and offset.
getItem(remoteId) One item. An unknown id rejects with not-found.

Calling anything before connect must also reject with a normalized error (for example authentication-expired).

Method When to implement it
search(query) Declare the search capability and implement it. Honour limit, containerIds, includeItems and includeContainers: if the remote API cannot filter, filter in the plugin. Each result says whether it canLocatePhysically: true when you know which container the item is in.
getDeepLink(resource) Return the URL of that container or item in the source’s own web interface, or null.
sync(options) Only if the source can return changes since a time (incrementalSync). The app falls back to listing containers and contents otherwise.
  • Use the context for I/O. HTTP, storage, cache, logging and the clock all come from the context, which is also how tests replace the network.
  • One request path. Funnel every call through one method that maps transport failures and HTTP statuses to errors.
  • Cancel when asked. Methods that take a signal should pass it to the request.
  • Don’t cache in the plugin what the app already caches. The app mirrors containers and items locally; the plugin’s job is to answer fresh questions.