todo/data/database.js (104 lines of code) (raw):

// @flow export class Todo { +id: string; +text: string; +complete: boolean; constructor(id: string, text: string, complete: boolean) { this.id = id; this.text = text; this.complete = complete; } } export class User { +id: string; constructor(id: string) { this.id = id; } } // Mock authenticated ID export const USER_ID = 'me'; // Mock user database table const usersById: Map<string, User> = new Map([[USER_ID, new User(USER_ID)]]); // Mock todo database table const todosById: Map<string, Todo> = new Map(); const todoIdsByUser: Map<string, $ReadOnlyArray<string>> = new Map([ [USER_ID, []], ]); // Seed initial data let nextTodoId: number = 0; addTodo('Taste JavaScript', true); addTodo('Buy a unicorn', false); function getTodoIdsForUser(id: string): $ReadOnlyArray<string> { return todoIdsByUser.get(id) || []; } export function addTodo(text: string, complete: boolean): string { const todo = new Todo(`${nextTodoId++}`, text, complete); todosById.set(todo.id, todo); const todoIdsForUser = getTodoIdsForUser(USER_ID); todoIdsByUser.set(USER_ID, todoIdsForUser.concat(todo.id)); return todo.id; } export function changeTodoStatus(id: string, complete: boolean) { const todo = getTodoOrThrow(id); // Replace with the modified complete value todosById.set(id, new Todo(id, todo.text, complete)); } // Private, for strongest typing, only export `getTodoOrThrow` function getTodo(id: string): ?Todo { return todosById.get(id); } export function getTodoOrThrow(id: string): Todo { const todo = getTodo(id); if (!todo) { throw new Error(`Invariant exception, Todo ${id} not found`); } return todo; } export function getTodos(status: string = 'any'): $ReadOnlyArray<Todo> { const todoIdsForUser = getTodoIdsForUser(USER_ID); const todosForUser = todoIdsForUser.map(getTodoOrThrow); if (status === 'any') { return todosForUser; } return todosForUser.filter( (todo: Todo): boolean => todo.complete === (status === 'completed'), ); } // Private, for strongest typing, only export `getUserOrThrow` function getUser(id: string): ?User { return usersById.get(id); } export function getUserOrThrow(id: string): User { const user = getUser(id); if (!user) { throw new Error(`Invariant exception, User ${id} not found`); } return user; } export function markAllTodos(complete: boolean): $ReadOnlyArray<string> { const todosToChange = getTodos().filter( (todo: Todo): boolean => todo.complete !== complete, ); todosToChange.forEach((todo: Todo): void => changeTodoStatus(todo.id, complete), ); return todosToChange.map((todo: Todo): string => todo.id); } export function removeTodo(id: string) { const todoIdsForUser = getTodoIdsForUser(USER_ID); // Remove from the users list todoIdsByUser.set( USER_ID, todoIdsForUser.filter((todoId: string): boolean => todoId !== id), ); // And also from the total list of Todos todosById.delete(id); } export function removeCompletedTodos(): $ReadOnlyArray<string> { const todoIdsForUser = getTodoIdsForUser(USER_ID); const todoIdsToRemove = getTodos() .filter((todo: Todo): boolean => todo.complete) .map((todo: Todo): string => todo.id); // Remove from the users list todoIdsByUser.set( USER_ID, todoIdsForUser.filter( (todoId: string): boolean => !todoIdsToRemove.includes(todoId), ), ); // And also from the total list of Todos todoIdsToRemove.forEach((id: string): boolean => todosById.delete(id)); return todoIdsToRemove; } export function renameTodo(id: string, text: string) { const todo = getTodoOrThrow(id); // Replace with the modified text value todosById.set(id, new Todo(id, text, todo.complete)); }