or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

http-client.mdindex.mdlogger.mdmappers.mdrepository-management.mdstate-management.mdtypes-extraction.mdtypes-loading.mdworker-management.md
tile.json

mappers.mddocs/

Sync Mappers

Sync mappers manage the mappings between external system entities and DevRev entities during loading operations. The Mappers class provides methods to create, read, and update these mappings, enabling bidirectional data synchronization.

Capabilities

Mappers Class

The Mappers class is accessible via adapter.mappers within worker code and provides methods for managing sync mapper records.

/**
 * Mappers manages sync mapper records linking external system entities to DevRev entities
 * Access via adapter.mappers in processTask
 */
class Mappers {
  /**
   * Get sync mapper record by DevRev target ID
   * Use when you know the DevRev ID and want the corresponding mapping
   */
  getByTargetId(
    params: MappersGetByTargetIdParams
  ): Promise<AxiosResponse<MappersGetByTargetIdResponse>>;

  /**
   * Get sync mapper record by external system ID
   * Use when you know an external ID and need the DevRev mapping
   */
  getByExternalId(
    params: MappersGetByExternalIdParams
  ): Promise<AxiosResponse<MappersGetByExternalIdResponse>>;

  /**
   * Create a new sync mapper record
   * Call after creating an item in the external system to persist the mapping
   */
  create(params: MappersCreateParams): Promise<AxiosResponse<MappersCreateResponse>>;

  /**
   * Update an existing sync mapper record
   * Call after updating an item in the external system to add IDs, targets, or version markers
   */
  update(params: MappersUpdateParams): Promise<AxiosResponse<MappersUpdateResponse>>;
}

Usage Examples:

import { processTask, LoaderEventType } from '@devrev/ts-adaas';

processTask({
  task: async ({ adapter }) => {
    const mappers = adapter.mappers;
    const syncUnit = adapter.event.payload.event_context.sync_unit;

    // Get mapping by DevRev ID
    const mapping = await mappers.getByTargetId({
      sync_unit: syncUnit,
      target: 'don:integration:dvrv-us-1:devo/abc:ticket/123',
    });

    const externalId = mapping.data.sync_mapper_record.external_ids[0];

    // Update item in external system
    await updateExternalItem(externalId, itemData);

    // Update mapping with new external version
    await mappers.update({
      id: mapping.data.sync_mapper_record.id,
      sync_unit: syncUnit,
      external_ids: { add: [] },
      targets: { add: [] },
      status: 'operational',
      external_versions: {
        add: [
          {
            recipe_version: 1,
            modified_date: new Date().toISOString(),
          },
        ],
      },
    });

    await adapter.emit(LoaderEventType.DataLoadingDone);
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

Get By Target ID

Retrieve a sync mapper record by DevRev entity ID.

/**
 * Parameters for retrieving a sync mapper record by DevRev target ID
 */
interface MappersGetByTargetIdParams {
  /** The sync unit ID that scopes the synchronization context */
  sync_unit: DonV2;
  /** The DevRev entity ID to look up */
  target: DonV2;
}

/**
 * Response containing a sync mapper record retrieved by target ID
 */
interface MappersGetByTargetIdResponse {
  sync_mapper_record: SyncMapperRecord;
}

Usage Examples:

processTask({
  task: async ({ adapter }) => {
    const devrevId = 'don:integration:dvrv-us-1:devo/abc:ticket/123';
    const syncUnit = adapter.event.payload.event_context.sync_unit;

    try {
      // Get existing mapping
      const response = await adapter.mappers.getByTargetId({
        sync_unit: syncUnit,
        target: devrevId,
      });

      const record = response.data.sync_mapper_record;
      const externalId = record.external_ids[0];

      console.log(`DevRev ID ${devrevId} maps to external ID ${externalId}`);

      // Update existing item in external system
      await updateInExternalSystem(externalId, itemData);
    } catch (error) {
      // Mapping not found, create new item
      const externalId = await createInExternalSystem(itemData);

      // Create new mapping
      await adapter.mappers.create({
        sync_unit: syncUnit,
        external_ids: [externalId],
        targets: [devrevId],
        status: 'operational',
      });
    }

    await adapter.emit(LoaderEventType.DataLoadingDone);
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

Get By External ID

Retrieve a sync mapper record by external system ID.

/**
 * Parameters for retrieving a sync mapper record by external system ID
 */
interface MappersGetByExternalIdParams {
  /** The sync unit ID that scopes the synchronization context */
  sync_unit: DonV2;
  /** The identifier from the external system */
  external_id: string;
  /** The type of DevRev entity to look for */
  target_type: SyncMapperRecordTargetType;
}

/**
 * Response containing a sync mapper record retrieved by external ID
 */
interface MappersGetByExternalIdResponse {
  sync_mapper_record: SyncMapperRecord;
}

/**
 * Types of DevRev entities that can be targets in sync mapper records
 */
enum SyncMapperRecordTargetType {
  ACCESS_CONTROL_ENTRY = 'access_control_entry',
  ACCOUNT = 'account',
  AIRDROP_AUTHORIZATION_POLICY = 'airdrop_authorization_policy',
  AIRDROP_FIELD_AUTHORIZATION_POLICY = 'airdrop_field_authorization_policy',
  AIRDROP_PLATFORM_GROUP = 'airdrop_platform_group',
  ARTICLE = 'article',
  ARTIFACT = 'artifact',
  CHAT = 'chat',
  CONVERSATION = 'conversation',
  CUSTOM_OBJECT = 'custom_object',
  DIRECTORY = 'directory',
  GROUP = 'group',
  INCIDENT = 'incident',
  LINK = 'link',
  MEETING = 'meeting',
  OBJECT_MEMBER = 'object_member',
  PART = 'part',
  REV_ORG = 'rev_org',
  ROLE = 'role',
  ROLE_SET = 'role_set',
  TAG = 'tag',
  TIMELINE_COMMENT = 'timeline_comment',
  USER = 'user',
  WORK = 'work',
}

Usage Examples:

import { SyncMapperRecordTargetType } from '@devrev/ts-adaas';

processTask({
  task: async ({ adapter }) => {
    const externalUserId = 'ext-user-456';
    const syncUnit = adapter.event.payload.event_context.sync_unit;

    // Look up DevRev user by external ID
    const response = await adapter.mappers.getByExternalId({
      sync_unit: syncUnit,
      external_id: externalUserId,
      target_type: SyncMapperRecordTargetType.USER,
    });

    const devrevUserId = response.data.sync_mapper_record.targets[0];
    console.log(`External user ${externalUserId} maps to DevRev user ${devrevUserId}`);

    await adapter.emit(LoaderEventType.DataLoadingDone);
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

Create Mapping

Create a new sync mapper record after creating an item in the external system.

/**
 * Parameters for creating a new sync mapper record
 */
interface MappersCreateParams {
  /** The sync unit ID that scopes the synchronization context */
  sync_unit: DonV2;
  /** Array of external system identifiers */
  external_ids: string[];
  /** Optional map that labels values in external_ids with their usage context */
  secondary_ids?: Record<string, string>;
  /** Array of DevRev entity IDs this mapping points to */
  targets: DonV2[];
  /** Status of the sync mapper record */
  status: SyncMapperRecordStatus;
  /** Input file names where the object was encountered (optional) */
  input_files?: string[];
  /** External version markers to avoid update loops (optional) */
  external_versions?: SyncMapperRecordExternalVersion[];
  /** Opaque data for storing additional client-specific information (optional) */
  extra_data?: string;
}

/**
 * Response containing the newly created sync mapper record
 */
interface MappersCreateResponse {
  sync_mapper_record: SyncMapperRecord;
}

/**
 * Status of a sync mapper record
 */
enum SyncMapperRecordStatus {
  /** The mapping is active and operational (default) */
  OPERATIONAL = 'operational',
  /** The mapping was filtered out by user filter settings */
  FILTERED = 'filtered',
  /** The external object should be ignored in sync operations */
  IGNORED = 'ignored',
}

Usage Examples:

import { SyncMapperRecordStatus } from '@devrev/ts-adaas';

processTask({
  task: async ({ adapter }) => {
    const item: ExternalSystemItem = await getItemToLoad();
    const syncUnit = adapter.event.payload.event_context.sync_unit;

    // Create item in external system
    const externalId = await externalApi.createItem(item.data);

    // Create mapping
    await adapter.mappers.create({
      sync_unit: syncUnit,
      external_ids: [externalId],
      targets: [item.id.devrev],
      status: SyncMapperRecordStatus.OPERATIONAL,
      external_versions: [
        {
          recipe_version: 1,
          modified_date: new Date().toISOString(),
        },
      ],
    });

    await adapter.emit(LoaderEventType.DataLoadingDone);
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

// Create mapping with secondary IDs
await adapter.mappers.create({
  sync_unit: syncUnit,
  external_ids: ['uuid-123', 'john.doe'],
  secondary_ids: {
    username: 'john.doe',
  },
  targets: [devrevUserId],
  status: SyncMapperRecordStatus.OPERATIONAL,
});

Update Mapping

Update an existing sync mapper record after updating an item in the external system.

/**
 * Parameters for updating an existing sync mapper record
 */
interface MappersUpdateParams {
  /** The ID of the existing sync mapper record to update */
  id: DonV2;
  /** The sync unit ID that scopes the synchronization context */
  sync_unit: DonV2;
  /** External system IDs to add to the existing mapping */
  external_ids: { add: string[] };
  /** Optional map that labels values in external_ids with their usage context */
  secondary_ids?: Record<string, string>;
  /** DevRev entity IDs to add to the existing mapping */
  targets: { add: DonV2[] };
  /** Status of the sync mapper record */
  status: SyncMapperRecordStatus;
  /** Input file names (optional) */
  input_files?: { add: string[] };
  /** External version markers to avoid update loops (optional) */
  external_versions?: { add: SyncMapperRecordExternalVersion[] };
  /** Opaque data for storing additional information (optional) */
  extra_data?: string;
}

/**
 * Response containing the updated sync mapper record
 */
interface MappersUpdateResponse {
  sync_mapper_record: SyncMapperRecord;
}

Usage Examples:

processTask({
  task: async ({ adapter }) => {
    const item: ExternalSystemItem = await getItemToLoad();
    const syncUnit = adapter.event.payload.event_context.sync_unit;

    // Get existing mapping
    const mapping = await adapter.mappers.getByTargetId({
      sync_unit: syncUnit,
      target: item.id.devrev,
    });

    const externalId = mapping.data.sync_mapper_record.external_ids[0];

    // Update in external system
    await externalApi.updateItem(externalId, item.data);

    // Update mapping with new external version
    await adapter.mappers.update({
      id: mapping.data.sync_mapper_record.id,
      sync_unit: syncUnit,
      external_ids: { add: [] },
      targets: { add: [] },
      status: SyncMapperRecordStatus.OPERATIONAL,
      external_versions: {
        add: [
          {
            recipe_version: 1,
            modified_date: item.modified_date,
          },
        ],
      },
    });

    await adapter.emit(LoaderEventType.DataLoadingDone);
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

Sync Mapper Record

The structure of a sync mapper record linking external and DevRev entities.

/**
 * SyncMapperRecord represents a mapping between external and DevRev entities
 */
interface SyncMapperRecord {
  /** Unique ID of the sync mapper record */
  id: DonV2;
  /** Array of external system IDs that map to the same DevRev object */
  external_ids: string[];
  /** Optional map that labels values in external_ids with their usage context */
  secondary_ids?: Record<string, string>;
  /** Array of DevRev entity IDs this mapping points to */
  targets: DonV2[];
  /** Status of the mapping */
  status: SyncMapperRecordStatus;
  /** Optional file names where object data was found */
  input_files?: string[];
  /** External version markers to prevent update loops */
  external_versions?: SyncMapperRecordExternalVersion[];
  /** Free-form data storage */
  extra_data?: string;
}

/**
 * External version tracking to prevent update loops
 */
interface SyncMapperRecordExternalVersion {
  /** Sync recipe version at the time the external change was written */
  recipe_version: number;
  /** External system modified timestamp (ISO 8601 string) used for loop detection */
  modified_date: string;
}

Common Patterns

Create or Update Pattern

processTask({
  task: async ({ adapter }) => {
    const items = await getItemsToLoad();
    const syncUnit = adapter.event.payload.event_context.sync_unit;

    for (const item of items) {
      try {
        // Try to get existing mapping
        const mapping = await adapter.mappers.getByTargetId({
          sync_unit: syncUnit,
          target: item.id.devrev,
        });

        // Mapping exists, update external item
        const externalId = mapping.data.sync_mapper_record.external_ids[0];
        await externalApi.updateItem(externalId, item.data);

        // Update mapping
        await adapter.mappers.update({
          id: mapping.data.sync_mapper_record.id,
          sync_unit: syncUnit,
          external_ids: { add: [] },
          targets: { add: [] },
          status: 'operational',
          external_versions: {
            add: [{ recipe_version: 1, modified_date: item.modified_date }],
          },
        });
      } catch (error) {
        // Mapping doesn't exist, create new external item
        const externalId = await externalApi.createItem(item.data);

        // Create mapping
        await adapter.mappers.create({
          sync_unit: syncUnit,
          external_ids: [externalId],
          targets: [item.id.devrev],
          status: 'operational',
          external_versions: [{ recipe_version: 1, modified_date: item.modified_date }],
        });
      }
    }

    await adapter.emit(LoaderEventType.DataLoadingDone);
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

Secondary IDs for Multi-Identifier Systems

// External system uses both UUID and username
const externalUser = await externalApi.getUser();

await adapter.mappers.create({
  sync_unit: syncUnit,
  external_ids: [externalUser.uuid, externalUser.username],
  secondary_ids: {
    username: externalUser.username,
    email: externalUser.email,
  },
  targets: [devrevUserId],
  status: 'operational',
});

// Later, look up by username
const mapping = await adapter.mappers.getByExternalId({
  sync_unit: syncUnit,
  external_id: 'john.doe',
  target_type: SyncMapperRecordTargetType.USER,
});

Loop Prevention with External Versions

processTask({
  task: async ({ adapter }) => {
    // When creating/updating in external system during loading
    const modifiedDate = await updateInExternalSystem(item);

    // Store modified date in mapper to prevent re-applying this change
    await adapter.mappers.update({
      id: mapperId,
      sync_unit: syncUnit,
      external_ids: { add: [] },
      targets: { add: [] },
      status: 'operational',
      external_versions: {
        add: [
          {
            recipe_version: 1,
            modified_date: modifiedDate, // Store this timestamp
          },
        ],
      },
    });

    // During next extraction, if extracted item's modified_date matches
    // one in external_versions, the update is skipped (change originated from DevRev)
  },
  onTimeout: async ({ adapter }) => {
    await adapter.emit(LoaderEventType.DataLoadingError, {
      error: { message: 'Timeout' },
    });
  },
});

Using Extra Data

await adapter.mappers.create({
  sync_unit: syncUnit,
  external_ids: [externalId],
  targets: [devrevId],
  status: 'operational',
  extra_data: JSON.stringify({
    customField1: 'value1',
    customField2: 'value2',
    createdBy: 'snap-in-name',
  }),
});

// Later retrieve and use extra data
const mapping = await adapter.mappers.getByTargetId({
  sync_unit: syncUnit,
  target: devrevId,
});

const extraData = JSON.parse(mapping.data.sync_mapper_record.extra_data || '{}');
console.log('Custom field:', extraData.customField1);