<commit_before>import {
  AllDocsMap,
  FolderDoc,
  FolderDocEditibleProps,
  TagDocEditibleProps,
  TagDoc,
  NoteDoc,
  NoteDocEditibleProps,
  ExceptRev
} from './types'
import {
  getFolderId,
  createUnprocessableEntityError,
  isFolderPathnameValid,
  getParentFolderPathname,
  getTagId,
  isTagNameValid,
  generateNoteId,
  getNow,
  createNotFoundError,
  getFolderPathname,
  isNoteDoc,
  isFolderDoc,
  isTagDoc
} from './utils'

export default class NoteDb {
  public initialized: boolean

  constructor(
    public pouchDb: PouchDB.Database,
    public id: string,
    public name: string
  ) {}

  async init() {
    await this.upsertNoteListViews()

    const { noteMap, folderMap, tagMap } = await this.getAllDocsMap()
    const { missingPathnameSet, missingTagNameSet } = [
      ...noteMap.values()
    ].reduce<{
      missingPathnameSet: Set<string>
      missingTagNameSet: Set<string>
    }>(
      (obj, noteDoc) => {
        if (!folderMap.has(getFolderId(noteDoc.folderPathname))) {
          obj.missingPathnameSet.add(noteDoc.folderPathname)
        }
        noteDoc.tags.forEach(tagName => {
          if (!tagMap.has(getTagId(tagName))) {
            obj.missingTagNameSet.add(tagName)
          }
        })
        return obj
      },
      { missingPathnameSet: new Set(), missingTagNameSet: new Set() }
    )

    await Promise.all([
      ...[...missingPathnameSet].map(pathname => this.upsertFolder(pathname)),
      ...[...missingTagNameSet].map(tagName => this.upsertTag(tagName))
    ])
  }

  async getFolder(path: string): Promise<FolderDoc | null> {
    return this.getDoc<FolderDoc>(getFolderId(path))
  }

  async upsertFolder(
    pathname: string,
    props?: Partial<FolderDocEditibleProps>
  ): Promise<FolderDoc> {
    if (!isFolderPathnameValid(pathname)) {
      throw createUnprocessableEntityError(
        `pathname is invalid, got \`${pathname}\``
      )
    }
    if (pathname !== '/') {
      await this.doesParentFolderExistOrCreate(pathname)
    }
    const folder = await this.getFolder(pathname)
    if (folder != null && props == null) {
      return folder
    }
    const now = getNow()
    const folderDocProps = {
      ...(folder || {
        _id: getFolderId(pathname),
        createdAt: now,
        data: {}
      }),
      ...props,
      updatedAt: now
    }
    const { rev } = await this.pouchDb.put(folderDocProps)

    return {
      _id: folderDocProps._id,
      createdAt: folderDocProps.createdAt,
      updatedAt: folderDocProps.updatedAt,
      data: folderDocProps.data,
      _rev: rev
    }
  }

  async doesParentFolderExistOrCreate(pathname: string) {
    const parentPathname = getParentFolderPathname(pathname)
    await this.upsertFolder(parentPathname)
  }

  async getAllDocsMap(): Promise<AllDocsMap> {
    const allDocsResponse = await this.pouchDb.allDocs({
      include_docs: true
    })

    const map: AllDocsMap = {
      noteMap: new Map(),
      folderMap: new Map(),
      tagMap: new Map()
    }

    return allDocsResponse.rows.reduce((map, row) => {
      const { doc } = row
      if (isNoteDoc(doc)) {
        map.noteMap.set(doc._id, doc)
      } else if (isFolderDoc(doc)) {
        map.folderMap.set(doc._id, doc)
      } else if (isTagDoc(doc)) {
        map.tagMap.set(doc._id, doc)
      }
      return map
    }, map)
  }

  async getTag(tagName: string): Promise<TagDoc | null> {
    return this.getDoc<TagDoc>(getTagId(tagName))
  }

  async getDoc<T extends PouchDB.Core.GetMeta & PouchDB.Core.IdMeta>(
    docId: string
  ): Promise<T | null> {
    try {
      return await this.pouchDb.get<T>(docId)<commit_msg>Use pathnamd and tag name rather than document id<commit_after>import {
  AllDocsMap,
  FolderDoc,
  FolderDocEditibleProps,
  TagDocEditibleProps,
  TagDoc,
  NoteDoc,
  NoteDocEditibleProps,
  ExceptRev
} from './types'
import {
  getFolderId,
  createUnprocessableEntityError,
  isFolderPathnameValid,
  getParentFolderPathname,
  getTagId,
  isTagNameValid,
  generateNoteId,
  getNow,
  createNotFoundError,
  getFolderPathname,
  isNoteDoc,
  isFolderDoc,
  isTagDoc,
  getTagName
} from './utils'

export default class NoteDb {
  public initialized: boolean

  constructor(
    public pouchDb: PouchDB.Database,
    public id: string,
    public name: string
  ) {}

  async init() {
    await this.upsertNoteListViews()

    const { noteMap, folderMap, tagMap } = await this.getAllDocsMap()
    const { missingPathnameSet, missingTagNameSet } = [
      ...noteMap.values()
    ].reduce<{
      missingPathnameSet: Set<string>
      missingTagNameSet: Set<string>
    }>(
      (obj, noteDoc) => {
        if (!folderMap.has(noteDoc.folderPathname)) {
          obj.missingPathnameSet.add(noteDoc.folderPathname)
        }
        noteDoc.tags.forEach(tagName => {
          if (!tagMap.has(tagName)) {
            obj.missingTagNameSet.add(tagName)
          }
        })
        return obj
      },
      { missingPathnameSet: new Set(), missingTagNameSet: new Set() }
    )

    await Promise.all([
      ...[...missingPathnameSet].map(pathname => this.upsertFolder(pathname)),
      ...[...missingTagNameSet].map(tagName => this.upsertTag(tagName))
    ])
  }

  async getFolder(path: string): Promise<FolderDoc | null> {
    return this.getDoc<FolderDoc>(getFolderId(path))
  }

  async upsertFolder(
    pathname: string,
    props?: Partial<FolderDocEditibleProps>
  ): Promise<FolderDoc> {
    if (!isFolderPathnameValid(pathname)) {
      throw createUnprocessableEntityError(
        `pathname is invalid, got \`${pathname}\``
      )
    }
    if (pathname !== '/') {
      await this.doesParentFolderExistOrCreate(pathname)
    }
    const folder = await this.getFolder(pathname)
    if (folder != null && props == null) {
      return folder
    }
    const now = getNow()
    const folderDocProps = {
      ...(folder || {
        _id: getFolderId(pathname),
        createdAt: now,
        data: {}
      }),
      ...props,
      updatedAt: now
    }
    const { rev } = await this.pouchDb.put(folderDocProps)

    return {
      _id: folderDocProps._id,
      createdAt: folderDocProps.createdAt,
      updatedAt: folderDocProps.updatedAt,
      data: folderDocProps.data,
      _rev: rev
    }
  }

  async doesParentFolderExistOrCreate(pathname: string) {
    const parentPathname = getParentFolderPathname(pathname)
    await this.upsertFolder(parentPathname)
  }

  async getAllDocsMap(): Promise<AllDocsMap> {
    const allDocsResponse = await this.pouchDb.allDocs({
      include_docs: true
    })

    const map: AllDocsMap = {
      noteMap: new Map(),
      folderMap: new Map(),
      tagMap: new Map()
    }

    return allDocsResponse.rows.reduce((map, row) => {
      const { doc } = row
      if (isNoteDoc(doc)) {
        map.noteMap.set(doc._id, doc)
      } else if (isFolderDoc(doc)) {
        map.folderMap.set(getFolderPathname(doc._id), doc)
      } else if (isTagDoc(doc)) {
        map.tagMap.set(getTagName(doc._id), doc)
      }
      return map
    }, map)
  }

  async getTag(tagName: string): Promise<TagDoc | null> {
    return this.getDoc<TagDoc>(getTagId(tagName))
  }

  async getDoc<T extends PouchDB.Core.GetMeta & PouchDB.Core.IdMeta>(
    docId: string
  ): Promise<T | null> {
    try {
      return await this.pouchDb.get<T>(docId)
{'fraction_non_alphanumeric': 0.11913209606986899, 'fraction_numerical': 0.0, 'mean_word_length': 2.750767656090072, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4585611', 'n_tokens_mistral': 2414, 'n_tokens_neox': 2388, 'n_words': 458}