From 100470b8e2379bd227add5583cf6452d98ffc098 Mon Sep 17 00:00:00 2001 From: Chris Mounce Date: Wed, 3 Sep 2025 01:18:52 -0700 Subject: [PATCH] Migrate to using random IDs for documents --- app.html | 2 +- src/components/App.tsx | 15 +++++++- src/components/Chooser.tsx | 16 ++++++--- src/sync.ts | 74 ++++++++++++++++++++++++++++++-------- 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/app.html b/app.html index 67ec011..1bfd51f 100644 --- a/app.html +++ b/app.html @@ -8,6 +8,6 @@
- + diff --git a/src/components/App.tsx b/src/components/App.tsx index 886b280..a788c71 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -9,6 +9,8 @@ import { import Chooser from './Chooser'; import Editor from './Editor'; import { HashRouter, Route, useNavigate } from '@solidjs/router'; +import { defaultVault } from '../sync'; +import * as Y from 'yjs'; interface NavbarProps { fileId: Accessor; @@ -28,6 +30,17 @@ export function useNavbar(): NavbarProps { function Layout(props: any) { const navigate = useNavigate(); const [fileId, setFileId] = createSignal(null); + const title = () => { + const id = fileId(); + if (id === null) { + return null; + } else { + return (defaultVault.doc.getMap() as Y.Map) + .get('docs') + .get(id) + .get('title') as string; + } + }; return ( @@ -36,7 +49,7 @@ function Layout(props: any) { -
{fileId() ?? 'Choose a file'}
+
{title() ?? 'Choose a file'}
{props.children} diff --git a/src/components/Chooser.tsx b/src/components/Chooser.tsx index 8f3ebc2..31865d9 100644 --- a/src/components/Chooser.tsx +++ b/src/components/Chooser.tsx @@ -2,20 +2,26 @@ import { For, onMount } from 'solid-js'; import './Chooser.css'; import { useNavbar } from './App'; import { useNavigate } from '@solidjs/router'; +import { defaultVault } from '../sync'; +import * as Y from 'yjs'; function Chooser() { const navigate = useNavigate(); const { setFileId } = useNavbar(); onMount(() => setFileId(null)); - const files = ['foo', 'bar', 'baz']; + const docsMap = defaultVault.doc.getMap().get('docs') as Y.Map; + const titles: Record = {}; + for (const [id, docInfo] of docsMap.entries()) { + titles[id] = docInfo.get('title'); + } return ( <> - - {(availableFile) => ( - )} diff --git a/src/sync.ts b/src/sync.ts index 6e35e73..89e3ab6 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -19,21 +19,6 @@ const db = await openDB(DB_NAME, 3, { }, }); -async function getOrCreateVault() { - const records = await db.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 db.add(VAULT_STORE, value, key); - records.push(value); - } - return records[0]; -} - -await getOrCreateVault(); - export class LocalDocument { readonly id: string; readonly doc: Y.Doc; @@ -46,6 +31,7 @@ export class LocalDocument { const flush = _.debounce(() => this.flush(), 1000); this.doc.on('update', (update: Uint8Array) => { + // TODO: Is this generating update events on load? this.updates.push(update); flush(); }); @@ -56,6 +42,7 @@ export class LocalDocument { LOCAL_UPDATE_STORE, IDBKeyRange.bound([id, -Infinity], [id, Infinity]) ); + // console.log(`load(${id}): got ${updates.length} updates`); const result = new LocalDocument(id); Y.applyUpdate(result.doc, Y.mergeUpdates(updates)); return result; @@ -76,3 +63,60 @@ export class LocalDocument { this.doc.destroy(); } } + +async function getOrCreateVaultDoc() { + const records = await db.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 db.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 LocalDocument.load(id); + const vaultMap = vault.doc.getMap(); + if (!vaultMap.has('id')) { + vaultMap.set('id', id); + } + if (!vaultMap.has('docs')) { + vaultMap.set('docs', new Y.Map()); + } + return vault; +} + +export const defaultVault = await getOrCreateVaultDoc(); + +const docsMap = defaultVault.doc.getMap().get('docs') as Y.Map; +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]]); + docsMap.set(newId, docInfo); + + const tx = db.transaction(LOCAL_UPDATE_STORE, 'readwrite'); + const recordKeys = (await tx.store.getAllKeys( + IDBKeyRange.bound([oldKey, -Infinity], [oldKey, Infinity]) + )) as [string, number][]; + console.log( + `Found ${recordKeys.length} records for old document key ${oldKey}` + ); + for (const oldRecordKey of recordKeys) { + const record = await tx.store.get(oldRecordKey); + const newRecordKey = [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;