Task manager

In this example we will be modeling a task manager app. The app will associate employees to tasks, projects, and teams

Table Definition

Entities

Employee

import moment from "moment";
import { v4 as uuid } from "uuid";
import { Entity, EntityItem, QueryResponse, CreateEntityItem } from "electrodb";
import { table, client } from "../config";

export const employee = new Entity(
  {
    model: {
      entity: "employee",
      version: "1",
      service: "taskmanager",
    },
    attributes: {
      employee: {
        type: "string",
        default: () => uuid(),
      },
      firstName: {
        type: "string",
        required: true,
      },
      lastName: {
        type: "string",
        required: true,
      },
      office: {
        type: "string",
        required: true,
      },
      title: {
        type: "string",
        required: true,
      },
      team: {
        type: [
          "development",
          "marketing",
          "finance",
          "product",
          "cool cats and kittens",
        ] as const,
        required: true,
      },
      salary: {
        type: "string",
        required: true,
      },
      manager: {
        type: "string",
      },
      dateHired: {
        type: "string",
        validate: (date: string) => {
          if (!moment(date).isValid) {
            throw new Error("Invalid date format");
          }
        },
      },
      birthday: {
        type: "string",
        validate: (date: string) => {
          if (!moment(date).isValid) {
            throw new Error("Invalid date format");
          }
        },
      },
    },
    indexes: {
      employee: {
        pk: {
          field: "pk",
          composite: ["employee"],
        },
        sk: {
          field: "sk",
          composite: [],
        },
      },
      coworkers: {
        index: "gsi1pk-gsi1sk-index",
        collection: "workplaces",
        pk: {
          field: "gsi1pk",
          composite: ["office"],
        },
        sk: {
          field: "gsi1sk",
          composite: ["team", "title", "employee"],
        },
      },
      teams: {
        index: "gsi2pk-gsi2sk-index",
        pk: {
          field: "gsi2pk",
          composite: ["team"],
        },
        sk: {
          field: "gsi2sk",
          composite: ["dateHired", "title"],
        },
      },
      employeeLookup: {
        collection: "assignments",
        index: "gsi3pk-gsi3sk-index",
        pk: {
          field: "gsi3pk",
          composite: ["employee"],
        },
        sk: {
          field: "gsi3sk",
          composite: [],
        },
      },
      roles: {
        index: "gsi4pk-gsi4sk-index",
        pk: {
          field: "gsi4pk",
          composite: ["title"],
        },
        sk: {
          field: "gsi4sk",
          composite: ["salary"],
        },
      },
      directReports: {
        index: "gsi5pk-gsi5sk-index",
        pk: {
          field: "gsi5pk",
          composite: ["manager"],
        },
        sk: {
          field: "gsi5sk",
          composite: ["team", "office"],
        },
      },
    },
  },
  { table, client },
);

export type EmployeeItem = EntityItem<typeof employee>;
export type CreateEmployeeItem = CreateEntityItem<typeof employee>;
export type EmployeeQueryResponse = QueryResponse<typeof employee>;

Office

import { Entity, EntityItem, QueryResponse, CreateEntityItem } from "electrodb";
import { table, client } from "../config";

export const office = new Entity(
  {
    model: {
      entity: "office",
      version: "1",
      service: "taskmanager",
    },
    attributes: {
      office: {
        type: "string",
      },
      country: {
        type: "string",
      },
      state: {
        type: "string",
      },
      city: {
        type: "string",
      },
      zip: {
        type: "string",
      },
      address: {
        type: "string",
      },
    },
    indexes: {
      locations: {
        pk: {
          field: "pk",
          composite: ["country", "state"],
        },
        sk: {
          field: "sk",
          composite: ["city", "zip", "office"],
        },
      },
      office: {
        index: "gsi1pk-gsi1sk-index",
        collection: "workplaces",
        pk: {
          field: "gsi1pk",
          composite: ["office"],
        },
        sk: {
          field: "gsi1sk",
          composite: [],
        },
      },
    },
  },
  { table, client },
);

export type OfficeItem = EntityItem<typeof office>;
export type CreateOfficeItem = CreateEntityItem<typeof office>;
export type OfficeQueryResponse = QueryResponse<typeof office>;

Task

import { Entity, EntityItem, QueryResponse, CreateEntityItem } from "electrodb";
import { table, client } from "../config";

export const task = new Entity(
  {
    model: {
      entity: "task",
      version: "1",
      service: "taskmanager",
    },
    attributes: {
      task: {
        type: "string",
        required: true,
      },
      project: {
        type: "string",
        required: true,
      },
      employee: {
        type: "string",
        required: true,
      },
      description: {
        type: "string",
      },
      status: {
        type: ["open", "in-progress", "closed"] as const,
        default: "open",
      },
      points: {
        type: "number",
        required: true,
      },
      comments: {
        type: "any",
      },
    },
    indexes: {
      task: {
        pk: {
          field: "pk",
          composite: ["task"],
        },
        sk: {
          field: "sk",
          composite: ["project", "employee"],
        },
      },
      project: {
        index: "gsi1pk-gsi1sk-index",
        pk: {
          field: "gsi1pk",
          composite: ["project"],
        },
        sk: {
          field: "gsi1sk",
          composite: ["employee", "status"],
        },
      },
      assigned: {
        collection: "assignments",
        index: "gsi3pk-gsi3sk-index",
        pk: {
          field: "gsi3pk",
          composite: ["employee"],
        },
        sk: {
          field: "gsi3sk",
          composite: ["project", "status"],
        },
      },
      statuses: {
        index: "gsi4pk-gsi4sk-index",
        pk: {
          field: "gsi4pk",
          composite: ["status"],
        },
        sk: {
          field: "gsi4sk",
          composite: ["project", "employee"],
        },
      },
    },
  },
  { table, client },
);

export type TaskItem = EntityItem<typeof task>;
export type CreateTaskItem = CreateEntityItem<typeof task>;
export type TaskQueryResponse = QueryResponse<typeof task>;

Service

export const taskManager = new Service({
  employee,
  task,
  office,
});

Access Patterns

Find office and staff information an office

Use Collections to query across entities.

const workplace = await taskManager.collections
  .workplaces({ office: "portland" })
  .go();

Get employee details and all assigned

const { data, cursor } = await taskManager.collections
  .assignments({ employee: "tyler.walch" })
  .go();

Find Junior Developers making more than 100,000

Use Entity queries to drill into specific entities.

const title = "Junior Software Engineer";
const salary = "100000";

const developers = await taskManager.entities.employee.query
  .roles({ title })
  .gt({ salary })
  .go();

Find all open tasks for a given project less than or equal to 13 points

const status = "open";
const project = "135-53";

const tasks = await taskManager.entities.task.query
  .statuses({ status, project })
  .where(({ points }, { lte }) => lte(points, 13))
  .go();

Find marketing team members who were hired in between two and five years ago:

const team = "marketing";
const twoYearsAgo = moment.utc().subtract(2, "years").format("YYYY-MM-DD");
const fiveYearsAgo = moment.utc().subtract(5, "years").format("YYYY-MM-DD");

const recentHires = await taskManager.entities.employee.query
  .teams({ team })
  .between({ dateHired: fiveYearsAgo }, { dateHired: twoYearsAgo })
  .go();

Create New Task

The type CreateEntityItem<typeof YOUR_ENTITY_INSTANCE> is exported to help you type items as they are used for creation

type CreateTaskItem = CreateEntityItem<typeof task>;

function createNewTask(item: CreateTaskItem) {
  return taskManager.entities.task.put(item).go();
}