import _ from 'lodash'; import * as Y from 'yjs'; import { openDB, type DBSchema } from 'idb'; import { assert, generateId } from './util'; import { getDocsMap, getVaultMap, type DocMap } from './vault'; import { openDoc } from './persistence'; const DB_NAME = 'synced-docs'; export const LOCAL_DELTA_STORE = 'local-updates'; export const VAULT_STORE = 'vaults'; type LocalDeltaKey = [string, number]; type VaultRecord = { id: string }; interface DocsDatabase extends DBSchema { [LOCAL_DELTA_STORE]: { key: LocalDeltaKey; value: Uint8Array; }; [VAULT_STORE]: { key: string; value: VaultRecord; }; } export const docsDb = await openDB(DB_NAME, 3, { upgrade(db, oldVersion, newVersion) { console.log(`Running upgrade from ${oldVersion} to ${newVersion}`); if (oldVersion < 2) { db.createObjectStore(LOCAL_DELTA_STORE); } if (oldVersion < 3) { db.createObjectStore(VAULT_STORE); } }, }); async function getOrCreateVaultDoc() { const records = await docsDb.getAll(VAULT_STORE); if (records.length > 1) { throw new Error(`Expected 1 vault record, got ${records.length}`); } else if (records.length === 0) { const key = generateId(); const value = { id: key }; await docsDb.add(VAULT_STORE, value, key); records.push(value); } const id = records[0].id as string; if (!id) { throw new Error("Vault record didn't have id"); } const vault = await openDoc(id); const vaultMap = getVaultMap(vault); if (!vaultMap.has('id')) { vaultMap.set('id', id); } if (!vaultMap.has('docs')) { vaultMap.set('docs', new Y.Map()); } // Make sure each doc has tags const docsMap = vaultMap.get('docs'); for (const docMap of docsMap.values()) { if (!docMap.has('tags')) { docMap.set('tags', new Y.Map()); } } return vault; } export const defaultVault = await getOrCreateVaultDoc(); const docsMap = getDocsMap(defaultVault); if (docsMap.size === 0) { console.log('Running vault migration...'); for (const oldKey of ['foo', 'bar', 'baz']) { const newId = generateId(); const docInfo = new Y.Map([['title', oldKey]]) as DocMap; docsMap.set(newId, docInfo); const tx = docsDb.transaction(LOCAL_DELTA_STORE, 'readwrite'); const recordKeys = (await tx.store.getAllKeys( IDBKeyRange.bound([oldKey, -Infinity], [oldKey, Infinity]) )) as LocalDeltaKey[]; console.log( `Found ${recordKeys.length} records for old document key ${oldKey}` ); for (const oldRecordKey of recordKeys) { const record = await tx.store.get(oldRecordKey); assert(record, `Couldn't find old record ${oldRecordKey} to convert it`); const newRecordKey: LocalDeltaKey = [newId, oldRecordKey[1]]; await tx.store.put(record, newRecordKey); } await tx.store.delete( IDBKeyRange.bound([oldKey, -Infinity], [oldKey, Infinity]) ); tx.commit(); } } (window as any).Y = Y;