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();
}