Lightyear LogoLightyear Docs

Step 2 - Understand the project

Check out the folder structure

Open up the lightyear-integrations project with your IDE and familiarize yourself with the key files in the directory structure.

index.ts
index.ts
index.ts
TaskModel.ts
TodoApp.ts

Understand the project structure

Main entry point

src/index.ts

This is the main entry point for the project. You can see that it imports the taskManagement collection and todoApp integration.

import "./taskManagement";
import "./todoApp";

Task management collection

src/taskManagement/index.ts

A collection is where we store the data that we sync between apps. The data in a collection is grouped into different models, each with an optional schema and optional matching rule.

import { defineCollection } from "@runlightyear/lightyear";
 
export const taskManagement = defineCollection({
  name: "taskManagement",
  title: "Task Management",
  models: [
    {
      name: "task",
      title: "Task",
      schema: {
        type: "object",
        properties: {
          title: {
            type: "string",
          },
          description: {
            type: ["string", "null"],
          },
          status: {
            type: "string",
            enum: ["pending", "inProgress", "completed"],
          },
          dueDate: {
            type: ["string", "null"],
          },
          completedAt: {
            type: ["string", "null"],
          },
        },
        required: ["title", "status"],
      },
      matchOn: "title",
    },
  ],
});

This one is pretty simple, so it has only one model. But if you are syncing something like CRM data, you might have a model for contacts, another for companies, and another for deals.

For additional information on collections, see the Collections page.

TodoApp integration

src/todoApp/index.ts

Here we define both the custom app called todoApp and the related sync integration.

import {
  defineCustomApp,
  defineSyncIntegration,
} from "@runlightyear/lightyear";
import { TodoApp } from "./TodoApp";
import { taskManagement } from "../taskManagement";
 
const customApp = defineCustomApp({
  name: "todoApp",
  title: "Todo App",
  connector: TodoApp,
});
 
defineSyncIntegration({
  name: "todoApp",
  title: "Todo App",
  customApp,
  collection: taskManagement,
  connector: (props) =>
    new TodoApp({
      ...props,
      collectionName: "taskManagement",
    }),
  frequency: {
    incremental: 1,
    full: 15,
  },
});

For additional information on custom apps, see the Apps and custom apps page.

For additional information on integrations, see the Integrations page.

TodoApp sync connector

src/todoApp/TodoApp.ts

A sync connector is a TypeScript class that defines how data is synced between the app and the collection. This is where we define things like the base URL, the authentication header, and which models we are syncing.

import { AuthType, SyncConnector } from "@runlightyear/lightyear";
import { TaskModel } from "./TaskModel";
 
export class TodoApp extends SyncConnector {
  static authType: AuthType = "APIKEY";
 
  getBaseUrl(): string {
    return "https://todo-api.lightyear.dev/api";
  }
 
  getDefaultHeaders(): Record<string, string> {
    const { apiKey } = this.getAuthData();
 
    if (!apiKey) {
      throw new Error("API key is not set");
    }
 
    return {
      ...super.getDefaultHeaders(),
      "x-api-key": apiKey,
    };
  }
 
  getModels() {
    return {
      task: new TaskModel({
        todoApp: this,
        connector: this,
        collectionName: this.collectionName,
        modelName: "task",
      }),
    };
  }
}

Task model connector

src/todoApp/TaskModel.ts

A model connector is a TypeScript class that synchronizes data between an app and a specific model within a collection. This is the real meat of the integration and it's where you define what endpoint you are using and how to map the data back and forth from the collection.

import {
  ModelConnector,
  ModelConnectorProps,
  ListProps,
  CreateBatchProps,
  UpdateBatchProps,
  DeleteBatchProps,
} from "@runlightyear/lightyear";
import { TodoApp } from "./TodoApp";
 
export interface TaskModelProps extends ModelConnectorProps {
  todoApp: TodoApp;
}
 
export class TaskModel extends ModelConnector<any, any, any, any> {
  todoApp: TodoApp;
 
  constructor(props: TaskModelProps) {
    super(props);
    this.todoApp = props.todoApp;
  }
 
  getNoun(): string {
    return "task";
  }
 
  validateListResponse(response: any): any {
    return response;
  }
 
  mapExternalToObject(data: any): any {
    return {
      id: data.id,
      updatedAt: data.updatedAt,
      data: {
        title: data.title,
        description: data.description,
        status:
          data.status === "PENDING"
            ? "pending"
            : data.status === "IN_PROGRESS"
              ? "inProgress"
              : "completed",
        dueDate: data.dueDate,
        completedAt: data.completedAt,
      },
    };
  }
 
  mapObjectDataToExternalData(data: any): any {
    return {
      title: data.title,
      description: data.description,
      status:
        data.status === "pending"
          ? "PENDING"
          : data.status === "inProgress"
            ? "IN_PROGRESS"
            : "COMPLETED",
      dueDate: data.dueDate,
      completedAt: data.completedAt,
    };
  }
 
  async list(props: ListProps): Promise<any> {
    const response = await this.todoApp.request({
      method: "GET",
      url: "/todos",
      params: {
        limit: 100,
        orderBy: "updatedAt",
        updatedAt_gt: props.lastExternalUpdatedAt ?? undefined,
        offset: props.cursor ?? undefined,
      },
    });
 
    const validatedResponse = this.validateListResponse(response.data);
 
    return {
      objects: validatedResponse.todos.map((todo: any) =>
        this.mapExternalToObject(todo),
      ),
      cursor: validatedResponse.pagination.hasMore
        ? validatedResponse.pagination.nextOffset
        : undefined,
    };
  }
 
  async createBatch(props: CreateBatchProps<any>): Promise<any> {
    await this.todoApp.requestBatch(
      props.changes.map((change) => ({
        method: "POST",
        url: "/todos",
        data: this.mapObjectDataToExternalData(change.data),
        confirm: {
          changeIds: [change.changeId],
          idPath: "$.todo.id",
          updatedAtPath: "$.todo.updatedAt",
        },
      })),
    );
  }
 
  async updateBatch(props: UpdateBatchProps<any>): Promise<any> {
    await this.todoApp.requestBatch(
      props.changes.map((change) => ({
        method: "PATCH",
        url: `/todos/${change.externalId}`,
        data: this.mapObjectDataToExternalData(change.data),
        confirm: {
          changeIds: [change.changeId],
          idPath: "$.todo.id",
          updatedAtPath: "$.todo.updatedAt",
        },
      })),
    );
  }
 
  async deleteBatch(props: DeleteBatchProps): Promise<any> {
    await this.todoApp.requestBatch(
      props.changes.map((change) => ({
        method: "DELETE",
        url: `/todos/${change.externalId}`,
        confirm: {
          changeIds: [change.changeId],
        },
      })),
    );
  }
}

On this page