Version control

In this example we will be modeling a version control system similar to Github

Table Definition

Setup

This file is referenced by multiple entities below

./types.ts

/* istanbul ignore file */
import {IssueCommentIds, PullRequestCommentIds} from "./index";

export const StatusTypes = ["Open", "Closed"] as const;

export type Status = typeof StatusTypes[number];

export function toStatusCode(status: unknown) {
    for (let index in StatusTypes) {
        if (StatusTypes[index] === status) {
            return `${index}`;
        }
    }
}

export function toStatusString(code: unknown) {
    for (let index in StatusTypes) {
        if (`${index}` === code) {
            return StatusTypes[index];
        }
    }
}

export const PullRequestTicket = "PullRequest";
export const IssueTicket = "Issue";
export const IsNotTicket = "";
export const TicketTypes = [IssueTicket, PullRequestTicket, IsNotTicket] as const;

export const NotYetViewed = "#";

export type SubscriptionTypes = typeof TicketTypes[number];

export function isIssueCommentIds(comment: any): comment is IssueCommentIds {
    return comment.issueNumber !== undefined && comment.username !== undefined;
}

export function isPullRequestCommentIds(comment: any): comment is PullRequestCommentIds {
    return comment.pullRequestNumber !== undefined && comment.username !== undefined;
}

Entities

Issues

import { Entity } from 'electrodb';
import moment from "moment";
import { TicketTypes, IssueTicket, StatusTypes, toStatusString, toStatusCode } from "./types";

export const issues = new Entity({
  model: {
    entity: "issues",
    service: "versioncontrol",
    version: "1"
  },
  attributes: {
    issueNumber: {
      type: "string",
    },
    repoName: {
      type: "string"
    },
    repoOwner: {
      type: "string"
    },
    username: {
      type: "string",
    },
    ticketType: {
      type: TicketTypes,
      set: () => IssueTicket,
      readOnly: true
    },
    ticketNumber: {
      type: "string",
      readOnly: true,
      watch: ["issueNumber"],
      set: (_, {issueNumber}) => issueNumber
    },
    status: {
      type: StatusTypes,
      default: "Open",
      set: (val) => toStatusCode(val),
      get: (val) => toStatusString(val),
    },
    subject: {
      type: "string"
    },
    body: {
      type: "string"
    },
    createdAt: {
      type: "string",
      set: () => moment.utc().format(),
      readOnly: true,
    },
    updatedAt: {
      type: "string",
      watch: "*",
      set: () => moment.utc().format(),
      readOnly: true,
    },
  },
  indexes: {
    issue: {
      collection: "issueReview",
      pk: {
        composite: ["repoOwner", "repoName", "issueNumber"],
        field: "pk"
      },
      sk: {
        composite: [],
        field: "sk"
      }
    },
    created: {
      collection: ["owned", "managed"],
      index: "gsi1pk-gsi1sk-index",
      pk: {
        field: "gsi1pk",
        composite: ["username"]
      },
      sk: {
        field: "gsi1sk",
        composite: ["status", "createdAt"]
      }
    },
    todos: {
      collection: "activity",
      index: "gsi2pk-gsi2sk-index",
      pk: {
        composite: ["repoOwner", "repoName"],
        field: "gsi2pk"
      },
      sk: {
        composite: ["status", "createdAt"],
        field: "gsi2sk"
      }
    },
    _: {
      collection: "subscribers",
      index: "gsi4pk-gsi4sk-index",
      pk: {
        composite: ["repoOwner", "repoName", "ticketNumber"],
        field: "gsi4pk"
      },
      sk: {
        composite: [],
        field: "gsi4sk"
      }
    }
  }
});

IssueComments

import { Entity } from 'electrodb';
import moment from "moment";
import { TicketTypes, IssueTicket, StatusTypes, toStatusString, toStatusCode } from "./types";

export const issueComments = new Entity({
  model: {
    entity: "issueComment",
    service: "versioncontrol",
    version: "1"
  },
  attributes: {
    issueNumber: {
      type: "string",
    },
    commentId: {
      type: "string"
    },
    username: {
      type: "string"
    },
    replyTo: {
      type: "string"
    },
    replyViewed: {
      type: "string",
      default: NotYetViewed,
      get: (replyViewed) => {
        if (replyViewed !== NotYetViewed) {
          return replyViewed
        }
      },
      set: (replyViewed) => {
        if (replyViewed === undefined) {
          return NotYetViewed;
        }
        return replyViewed;
      }
    },
    repoName: {
      type: "string"
    },
    repoOwner: {
      type: "string"
    },
    body: {
      type: "string"
    },
    createdAt: {
      type: "string",
      set: () => moment.utc().format(),
      readOnly: true,
    },
    updatedAt: {
      type: "string",
      watch: "*",
      set: () => moment.utc().format(),
      readOnly: true,
    },
  },
  indexes: {
    comments: {
      collection: "issueReview",
      pk: {
        composite: ["repoOwner", "repoName", "issueNumber"],
        field: "pk"
      },
      sk: {
        composite: ["commentId"],
        field: "sk"
      }
    },
    created: {
      collection: "conversations",
      index: "gsi1pk-gsi1sk-index",
      pk: {
        field: "gsi1pk",
        composite: ["username"]
      },
      sk: {
        field: "gsi1sk",
        composite: ["repoOwner", "repoName", "issueNumber"]
      }
    },
    replies: {
      collection: "inbox",
      index: "gsi2pk-gsi2sk-index",
      pk: {
        composite: ["replyTo"],
        field: "gsi2pk"
      },
      sk: {
        composite: ["createdAt", "replyViewed"],
        field: "gsi2sk"
      }
    }
  }
});

PullRequests

import { Entity } from 'electrodb';
import moment from "moment";
import {NotYetViewed, TicketTypes, PullRequestTicket, StatusTypes, toStatusString, toStatusCode} from "./types";

export const pullRequests = new Entity({
  model: {
    entity: "pullRequest",
    service: "versioncontrol",
    version: "1"
  },
  attributes: {
    pullRequestNumber: {
      type: "string",
      required: true,
    },
    repoName: {
      type: "string",
      required: true,
    },
    repoOwner: {
      type: "string",
      required: true,
    },
    username: {
      type: "string",
      required: true,
    },
    ticketType: {
      type: TicketTypes,
      default: () => PullRequestTicket,
      set: () => PullRequestTicket,
      readOnly: true
    },
    ticketNumber: {
      type: "string",
      readOnly: true,
      watch: ["pullRequestNumber"],
      set: (_, {issueNumber}) => issueNumber
    },
    status: {
      type: StatusTypes,
      default: "Open",
      set: (val) => toStatusCode(val),
      get: (val) => toStatusString(val)
    },
    reviewers: {
      type: "list",
      items: {
        type: "map",
        properties: {
          username: {
            type: "string",
            required: true,
          },
          approved: {
            type: "boolean",
            required: true,
          },
          createdAt: {
            type: "string",
            default: () => moment.utc().format(),
            readOnly: true,
          },
        }
      }
    },
    createdAt: {
      type: "string",
      set: () => moment.utc().format(),
      readOnly: true,
    },
    updatedAt: {
      type: "string",
      watch: "*",
      set: () => moment.utc().format(),
      readOnly: true,
    },
  },
  indexes: {
    pullRequest: {
      collection: "PRReview",
      pk: {
        composite: ["repoOwner", "repoName", "pullRequestNumber"],
        field: "pk"
      },
      sk: {
        composite: [],
        field: "sk"
      }
    },
    created: {
      collection: ["owned", "managed"],
      index: "gsi1pk-gsi1sk-index",
      pk: {
        field: "gsi1pk",
        composite: ["username"]
      },
      sk: {
        field: "gsi1sk",
        composite: ["status", "createdAt"]
      }
    },
    enhancements: {
      collection: "activity",
      index: "gsi2pk-gsi2sk-index",
      pk: {
        field: "gsi2pk",
        composite: ["repoOwner", "repoName"],
      },
      sk: {
        field: "gsi2sk",
        composite: ["status", "createdAt"],

      }
    },
    _: {
      collection: "subscribers",
      index: "gsi4pk-gsi4sk-index",
      pk: {
        composite: ["repoOwner", "repoName", "ticketNumber"],
        field: "gsi4pk"
      },
      sk: {
        composite: [],
        field: "gsi4sk"
      }
    }
  }
});

PullRequestComments

import { Entity } from 'electrodb';
import moment from "moment";
import {NotYetViewed, TicketTypes, PullRequestTicket, StatusTypes, toStatusString, toStatusCode} from "./types";

export const pullRequestComments = new Entity({
  model: {
    entity: "pullRequestComment",
    service: "versioncontrol",
    version: "1"
  },
  attributes: {
    repoName: {
      type: "string"
    },
    username: {
      type: "string"
    },
    repoOwner: {
      type: "string"
    },
    pullRequestNumber: {
      type: "string"
    },
    commentId: {
      type: "string"
    },
    replyTo: {
      type: "string"
    },
    replyViewed: {
      type: "string",
      default: NotYetViewed,
      get: (replyViewed) => {
        if (replyViewed !== NotYetViewed) {
          return replyViewed
        }
      },
      set: (replyViewed) => {
        if (replyViewed === undefined) {
          return NotYetViewed;
        }
        return replyViewed;
      }
    },
    createdAt: {
      type: "string",
      set: () => moment.utc().format(),
      readOnly: true,
    },
    updatedAt: {
      type: "string",
      watch: "*",
      set: () => moment.utc().format(),
      readOnly: true,
    },
  },
  indexes: {
    comments: {
      collection: "PRReview",
      pk: {
        composite: ["repoOwner", "repoName", "pullRequestNumber"],
        field: "pk"
      },
      sk: {
        composite: ["commentId"],
        field: "sk"
      }
    },
    created: {
      collection: "conversations",
      index: "gsi1pk-gsi1sk-index",
      pk: {
        field: "gsi1pk",
        composite: ["username"]
      },
      sk: {
        field: "gsi1sk",
        composite: ["repoOwner", "repoName"]
      }
    },
    replies: {
      collection: "inbox",
      index: "gsi2pk-gsi2sk-index",
      pk: {
        composite: ["replyTo"],
        field: "gsi2pk"
      },
      sk: {
        composite: ["createdAt", "replyViewed"],
        field: "gsi2sk"
      }
    },
  }
});

Repositories

import { Entity } from 'electrodb';
import moment from "moment";

export const licenses = [
  "afl-3.0",
  "apache-2.0",
  "artistic-2.0",
  "bsl-1.0",
  "bsd-2-clause",
  "bsd-3-clause",
  "bsd-3-clause-clear",
  "cc",
  "cc0-1.0",
  "cc-by-4.0",
  "cc-by-sa-4.0",
  "wtfpl",
  "ecl-2.0",
  "epl-1.0",
  "epl-2.0",
  "eupl-1.1",
  "agpl-3.0",
  "gpl",
  "gpl-2.0",
  "gpl-3.0",
  "lgpl",
  "lgpl-2.1",
  "lgpl-3.0",
  "isc",
  "lppl-1.3c",
  "ms-pl",
  "mit",
  "mpl-2.0",
  "osl-3.0",
  "postgresql",
  "ofl-1.1",
  "ncsa",
  "unlicense",
  "zlib"
] as const;

export const repositories = new Entity({
  model: {
    entity: "repositories",
    service: "versioncontrol",
    version: "1"
  },
  attributes: {
    repoName: {
      type: "string"
    },
    repoOwner: {
      type: "string"
    },
    about: {
      type: "string"
    },
    username: {
      type: "string",
      readOnly: true,
      watch: ["repoOwner"],
      set: (_, {repoOwner}) => repoOwner
    },
    description: {
      type: "string"
    },
    isPrivate: {
      type: "boolean"
    },
    license: {
      type: licenses
    },
    defaultBranch: {
      type: "string",
      default: "main"
    },
    topics: {
      type: "set",
      items: "string"
    },
    followers: {
      type: "set",
      items: "string"
    },
    stars: {
      type: "set",
      items: "string"
    },
    createdAt: {
      type: "string",
      set: () => moment.utc().format(),
      readOnly: true,
    },
    updatedAt: {
      type: "string",
      watch: "*",
      set: () => moment.utc().format(),
      readOnly: true,
    },
  },
  indexes: {
    repositories: {
      collection: "alerts",
      pk: {
        composite: ["repoOwner"],
        field: "pk"
      },
      sk: {
        composite: ["repoName"],
        field: "sk"
      }
    },
    created: {
      collection: "owned",
      index: "gsi1pk-gsi1sk-index",
      pk: {
        composite: ["username"],
        field: "gsi1pk"
      },
      sk: {
        composite: ["isPrivate", "createdAt"],
        field: "gsi1sk"
      }
    },
  }
});

Subscriptions

import { Entity } from 'electrodb';
import moment from "moment";
import {TicketTypes, IsNotTicket} from "./types";

const RepositorySubscription = "#";

export const subscriptions = new Entity({
    model: {
        entity: "subscription",
        service: "versioncontrol",
        version: "1"
    },
    attributes: {
        repoName: {
            type: "string",
            required: true,
        },
        repoOwner: {
            type: "string",
            required: true,
        },
        username: {
            type: "string",
            required: true,
        },
        ticketNumber: {
            type: "string",
            default: () => IsNotTicket,
            set: (ticketNumber) => {
                if (ticketNumber === IsNotTicket) {
                    return RepositorySubscription;
                } else {
                    return ticketNumber;
                }
            },
            get: (ticketNumber) => {
                if (ticketNumber === RepositorySubscription) {
                    return IsNotTicket;
                } else {
                    return ticketNumber;
                }
            }
        },
        ticketType: {
            type: TicketTypes,
            default: () => IsNotTicket,
            set: (ticketType) => {
                if (ticketType === IsNotTicket) {
                    return RepositorySubscription;
                } else {
                    return ticketType;
                }
            },
            get: (ticketType) => {
                if (ticketType === RepositorySubscription) {
                    return IsNotTicket;
                } else {
                    return ticketType;
                }
            }
        },
        createdAt: {
            type: "string",
            set: () => moment.utc().format(),
            readOnly: true,
        },
        updatedAt: {
            type: "string",
            watch: "*",
            set: () => moment.utc().format(),
            readOnly: true,
        },
    },
    indexes: {
        repository: {
            pk: {
                composite: ["repoOwner", "repoName"],
                field: "pk"
            },
            sk: {
                composite: ["username", "ticketType", "ticketNumber"],
                field: "sk"
            }
        },
        user: {
            collection: "watching",
            index: "gsi3pk-gsi3sk-index",
            pk: {
                composite: ["username"],
                field: "gsi3pk"
            },
            sk: {
                composite: ["ticketType", "ticketNumber"],
                field: "gsi3sk"
            }
        },
        tickets: {
            collection: "subscribers",
            index: "gsi4pk-gsi4sk-index",
            pk: {
                composite: ["repoOwner", "repoName", "ticketNumber"],
                field: "gsi4pk"
            },
            sk: {
                composite: ["ticketType", "username"],
                field: "gsi4sk"
            }
        }
    }
});

Users

import { Entity } from 'electrodb';
import moment from "moment";

export const users = new Entity({
  model: {
    entity: "user",
    service: "versioncontrol",
    version: "1"
  },
  attributes: {
    username: {
      type: "string"
    },
    fullName: {
      type: "string"
    },
    photo: {
      type: "string"
    },
    bio: {
      type: "string"
    },
    location: {
      type: "string"
    },
    pinned: {
      type: "any"
    },
    following: {
      type: "set",
      items: "string"
    },
    followers: {
      type: "set",
      items: "string"
    },
    createdAt: {
      type: "string",
      set: () => moment.utc().format(),
      readOnly: true,
    },
    updatedAt: {
        type: "string",
        watch: "*",
        set: () => moment.utc().format(),
        readOnly: true,
    },
  },
  indexes: {
    user: {
      collection: "overview",
      pk: {
        composite: ["username"],
        field: "pk"
      },
      sk: {
        composite: [],
        field: "sk"
      }
    },
    _: {
      collection: "owned",
      index: "gsi1pk-gsi1sk-index",
      pk: {
        composite: ["username"],
        field: "gsi1pk"
      },
      sk: {
        field: "gsi1sk",
        composite: []
      }
    },
    subscriptions: {
      collection: "watching",
      index: "gsi3pk-gsi3sk-index",
      pk: {
        composite: ["username"],
        field: "gsi3pk"
      },
      sk: {
        composite: [],
        field: "gsi3sk"
      }
    }
  }
});

Service

import { Service } from 'electrodb'
export const table = "electro";

export const store = new Service({
  users,
  issues,
  repositories,
  pullRequests,
  subscriptions,
  issueComments,
  pullRequestComments,
}, { table });

Access Patterns

Get public repositories by username

export async function getPublicRepository(username: string) {
    return store.entities.repositories
        .query.created({username, isPrivate: false});
}

Get pull request and associated comments

Note that newest comments will be returned first

export async function reviewPullRequest(options: { pr: PullRequestIds, cursor?: string }) {
    const { pr, cursor } = options;
    return store.collections.PRReview(pr)
        .go({ cursor, order: 'desc' });
}

Get issue and associated comments

Note that newest comments will be returned first

export async function reviewIssue(options: {issue: IssueIds, cursor?: string}) {
    const { issue, cursor } = options;
    return store.collections.issueReview(issue)
        .go({cursor, order: 'desc'});
}

Get pull requests created by user

export async function getUserPullRequests(options: {
    username: string;
    status?: Status;
    cursor?: string;
}) {
    const { username, status, cursor } = options;
    return store.entities.pullRequests
        .query.created({username, status})
        .go({cursor, order: 'desc'});
}

Close pull request

Note that this can only be performed by user who opened PR or the repo owner

export async function closePullRequest(user: string, pr: PullRequestIds) {
    return store.entities.pullRequests
        .update(pr)
        .set({status: "Closed"})
        .where(({username, repoOwner}, {eq}) => `
            ${eq(username, user)} OR ${eq(repoOwner, user)}
        `)
        .go()
}

Get all user info, repos, pull requests, and issues in one query

export async function getFirstPageLoad(username: string) {
    const results: OwnedItems = {
        issues: [],
        pullRequests: [],
        repositories: [],
        users: [],
    };

    let next = null;

    do {
        const {cursor, data} = await store.collections.owned({username}).go();
        results.issues = results.issues.concat(data.issues);
        results.pullRequests = results.pullRequests.concat(data.pullRequests);
        results.repositories = results.repositories.concat(data.repositories);
        results.users = results.users.concat(data.users);
        next = cursor;
    } while (next !== null);

    return results;
}

Get subscriptions for a given repository, pull request, or issue.

export async function getSubscribed(repoOwner: string, repoName: string, ticketNumber: string = IsNotTicket) {
    return store.collections
        .subscribers({repoOwner, repoName, ticketNumber})
        .go();
}

Get unread comment replies

const MinDate = "0000-00-00";
const MaxDate = "9999-99-99";

export async function getUnreadComments(user: string) {
    const start = {
        createdAt: MinDate,
        replyViewed: NotYetViewed
    };
    const end = {
        createdAt: MaxDate,
        replyViewed: NotYetViewed
    };
    let [issues, pullRequests] = await Promise.all([
        store.entities
            .issueComments.query
            .replies({replyTo: user})
            .between(start, end)
            .go(),

        store.entities
            .pullRequestComments.query
            .replies({replyTo: user})
            .between(start, end)
            .go()
    ]);

    return {
        issues,
        pullRequests
    };
}

Mark comment reply as read

Note that this can only be done by the user who was being replied to

export async function readReply(user: string, comment: IssueCommentIds): Promise<boolean>;
export async function readReply(user: string, comment: PullRequestCommentIds): Promise<boolean>;
export async function readReply(user: string, comment: any): Promise<boolean> {
    const replyViewed = moment.utc().format();
    if (isIssueCommentIds(comment)) {
        return await store.entities.issueComments
            .patch(comment)
            .set({replyViewed})
            .where(({replyTo}, {eq}) => eq(replyTo, user))
            .go()
            .then(() => true)
            .catch(() => false);
    } else if (isPullRequestCommentIds(comment)) {
        return await store.entities.pullRequestComments
            .patch(comment)
            .set({replyViewed})
            .where(({replyTo}, {eq}) => eq(replyTo, user))
            .go()
            .then(() => true)
            .catch(() => false);
    } else {
        return false;
    }
}

Approve pull request

export async function approvePullRequest(repoOwner: string, repoName: string, pullRequestNumber: string, username: string) {
    const pullRequest = await store.entities.pullRequests
        .get({repoOwner, repoName, pullRequestNumber})
        .go();

    if (!pullRequest.data || !pullRequest.data.reviewers) {
        return false;
    }

    let index: number = -1;

    for (let i = 0; i < pullRequest.data.reviewers.length; i++) {
        const reviewer = pullRequest.data.reviewers[i];
        if (reviewer.username === username) {
            index = i;
        }
    }

    if (index === -1) {
        return false;
    }

    return store.entities.pullRequests
        .update({repoOwner, repoName, pullRequestNumber})
        .data(({reviewers}, {set}) => {
            set(reviewers[index].approved, true);
        })
        .where(({reviewers}, {eq}) => `
            ${eq(reviewers[index].username, username)};
        `)
        .go()
        .then(() => true)
        .catch(() => false);
}

Follow repository

export async function followRepository(repoOwner: string, repoName: string, follower: string) {
    await store.entities
        .repositories.update({repoOwner, repoName})
        .add({followers: [follower]})
        .go();
}