@twinfinity/twin
    Preparing search index...

    @twinfinity/twin

    @twinfinity/twin

    TypeScript client for the Twinfinity Twin Service API. Provides typed access to twins, assets, and twin files with optimistic concurrency control and structured error handling.

    bun add @twinfinity/twin
    
    • @twinfinity/authentication - provides TwinfinityHttpClient for authenticated HTTP requests
    • @twinfinity/t8-client-model - provides T8 client model types used by loadTwin / loadGeometryInfo
    import { TwinClient } from '@twinfinity/twin';

    const client = new TwinClient(apiUrl, httpClient);

    The baseUrl is the API root (e.g. https://api.twinfinity.com). The client appends /_ps/twins internally.

    // List twins (paginated)
    const page = await client.listTwins();

    // List all twins (auto-paginates)
    const allTwins = await client.listAllTwins();

    // Get a specific twin
    const twin = await client.getTwin('twin-id');

    // Get a specific version
    const twinV2 = await client.getTwin({ id: 'twin-id', version: 2 });

    // Create or update
    const updated = await client.createOrUpdateTwin('twin-id', {
    name: 'My Twin',
    relatedContainerIds: ['container-1'],
    etag: twin.etag // required for updates
    });

    // Soft-delete and restore
    const deleted = await client.deleteTwin('twin-id', twin.etag);
    const restored = await client.restoreTwin('twin-id', deleted.etag);

    The client can download and deserialize processed twin content (T8CM/T8GI formats):

    // Load client model with default types
    const { id, version, model } = await client.loadTwin('twin-id');

    // Load with custom factory functions for domain-specific types
    const custom = await client.loadTwinWithFactories(
    'twin-id',
    myProjectFactory,
    myProductFactory,
    myEntityTypeFactory,
    myUnitFactory,
    myConnectProject,
    myConnectProduct
    );

    // Load geometry info
    const geometry = await client.loadGeometryInfo('twin-id');

    // Load client model with geometry combined
    const withGeometry = await client.loadTwinWithGeometry('twin-id');

    When no version is specified, the client searches versions (newest first) for one that has the requested content format.

    // List assets in a twin
    const assets = await client.listAssets('twin-id');

    // Include soft-deleted assets
    const allAssets = await client.listAssets('twin-id', 0, 100, { includeDeleted: true });

    // Create assets
    const created = await client.createAssets('twin-id', [{ name: 'Pump A', category: 'HVAC', type: 'Pump' }]);

    // Update with optimistic concurrency
    const updated = await client.updateAsset('twin-id', 'asset-id', {
    etag: asset.etag,
    name: 'Pump A (Updated)'
    });

    // Batch operations (atomic - all succeed or all roll back)
    const result = await client.batchAssets('twin-id', [
    { operation: 'upsert', id: 'asset-1', item: { name: 'New Asset' } },
    { operation: 'delete', id: 'asset-2', item: { etag: 'etag-2' } },
    { operation: 'restore', id: 'asset-3', item: { etag: 'etag-3' } }
    ]);

    // Batch get multiple assets by ID
    const fetched = await client.batchGetAssets('twin-id', ['id-1', 'id-2']);
    // List files (with filtering)
    const files = await client.listTwinFiles('twin-id', { checkedIn: true });

    // Create or update a file
    const file = await client.createOrUpdateTwinFile('twin-id', 'file-id', {
    source: { changeId: 'doc-id', changeVersion: 1, sourceType: 'DocumentChange' },
    name: 'model.ifc',
    quality: 100
    });

    // Download file content
    const blob = await client.getTwinFileContent('twin-id', 'file-id');

    All methods throw typed errors for specific HTTP failure codes. Unexpected status codes throw a generic Error.

    Error HTTP Status Context Attached
    TwinValidationError 400 errorBody?: TwinErrorBody
    TwinNotFoundError 404 -
    TwinConflictError 409 currentTwin: TwinGet
    AssetValidationError 400 errorBody?: TwinErrorBody
    AssetForbiddenError 403 -
    AssetNotFoundError 404 -
    AssetConflictError 409 currentAsset: AssetGet
    AssetBatchError 404, 409 response: AssetBatchResponse
    TwinFileValidationError 400 errorBody?: TwinErrorBody
    TwinFileNotFoundError 404 -
    TwinFileConflictError 409 currentTwinFile: TwinFileGet
    ProcessorNotFoundError 404 -

    Conflict errors (409) carry the current server-side state, enabling retry logic:

    import { TwinConflictError } from '@twinfinity/twin';

    try {
    await client.createOrUpdateTwin('twin-id', twinData);
    } catch (e) {
    if (e instanceof TwinConflictError) {
    // e.currentTwin contains the server's current version
    // Use it to merge changes and retry
    const merged = mergeChanges(twinData, e.currentTwin);
    await client.createOrUpdateTwin('twin-id', {
    ...merged,
    etag: e.currentTwin.etag
    });
    }
    }

    TwinValidationError, TwinFileValidationError, and AssetValidationError may include a structured error body:

    interface TwinErrorBody {
    detail: string;
    cause: string;
    item?: {
    kind: string;
    id: string;
    version?: number;
    field?: string;
    };
    }

    AssetBatchError carries the full batch response with per-item results, including which operations were reverted:

    import { AssetBatchError } from '@twinfinity/twin';

    try {
    await client.batchAssets('twin-id', operations);
    } catch (e) {
    if (e instanceof AssetBatchError) {
    for (const result of e.response.results) {
    if (result.status === 'conflicted') {
    console.log(`Item ${result.index} conflicted: ${result.error?.detail}`);
    }
    }
    }
    }

    The package has two export paths:

    • @twinfinity/twin - Public API: TwinClient, ITwinClient, all types, all error classes
    • @twinfinity/twin/internal/* - Internal modules (no stability guarantees)