Download packages/app/src/utils/draft-store.ts from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 6.87 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/src/utils/draft-store.ts
- Command line
-
hf download hf://SaylorTwift/opencode/packages/app/src/utils/draft-store.ts
-
curl -L -o draft-store.ts https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/src/utils/draft-store.ts
6.87 kB
| import type { AsyncStorage } from "@solid-primitives/storage" | |
| export type BlobReference = { id: string; url: string } | |
| type Driver = { | |
| get(key: string): Promise<string | null> | |
| set(key: string, value: string): Promise<void> | |
| remove(key: string): Promise<void> | |
| putBlob(blob: Blob): Promise<string> | |
| getBlob(id: string): Promise<Blob | null> | |
| } | |
| export type DraftStore = AsyncStorage & { putBlob(blob: Blob): Promise<BlobReference> } | |
| const urls = new Map<string, string>() | |
| function blobUrl(id: string, blob: Blob) { | |
| const existing = urls.get(id) | |
| if (existing) return existing | |
| const url = URL.createObjectURL(blob) | |
| urls.set(id, url) | |
| return url | |
| } | |
| async function blobID(blob: Blob) { | |
| const id = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", await blob.arrayBuffer()))) | |
| .map((byte) => byte.toString(16).padStart(2, "0")) | |
| .join("") | |
| return id | |
| } | |
| export async function createBlobReference(blob: Blob): Promise<BlobReference> { | |
| const id = await blobID(blob) | |
| return { id, url: blobUrl(id, blob) } | |
| } | |
| export function createDraftStore(driver: Driver): DraftStore { | |
| const versions = new Map<string, number>() | |
| const putBlob = async (blob: Blob) => { | |
| const id = await driver.putBlob(blob) | |
| return { id, url: blobUrl(id, blob) } | |
| } | |
| const encode = async (value: unknown): Promise<unknown> => { | |
| if (Array.isArray(value)) return Promise.all(value.map(encode)) | |
| if (!value || typeof value !== "object") return value | |
| const item = value as Record<string, unknown> | |
| if (item.type === "image" && typeof item.dataUrl === "string") { | |
| const blob = await fetch(item.dataUrl).then((response) => response.blob()) | |
| const { dataUrl: _, ...rest } = item | |
| return { ...rest, blob: { id: await driver.putBlob(blob) } } | |
| } | |
| if ("blob" in item && item.blob && typeof item.blob === "object") { | |
| const blob = item.blob as Record<string, unknown> | |
| if (typeof blob.id === "string" && blob.id.startsWith("data:")) { | |
| const data = await fetch(blob.id).then((response) => response.blob()) | |
| return { ...item, blob: { id: await driver.putBlob(data) } } | |
| } | |
| return { ...item, blob: { id: blob.id } } | |
| } | |
| return Object.fromEntries( | |
| await Promise.all(Object.entries(item).map(async ([key, entry]) => [key, await encode(entry)])), | |
| ) | |
| } | |
| const decode = async (value: unknown): Promise<unknown> => { | |
| if (Array.isArray(value)) return Promise.all(value.map(decode)) | |
| if (!value || typeof value !== "object") return value | |
| const item = value as Record<string, unknown> | |
| if (item.blob && typeof item.blob === "object") { | |
| const ref = item.blob as Record<string, unknown> | |
| if (typeof ref.id === "string") { | |
| const blob = await driver.getBlob(ref.id) | |
| if (blob) return { ...item, blob: { id: ref.id, url: blobUrl(ref.id, blob) } } | |
| } | |
| } | |
| return Object.fromEntries( | |
| await Promise.all(Object.entries(item).map(async ([key, entry]) => [key, await decode(entry)])), | |
| ) | |
| } | |
| return { | |
| getItem: async (key) => { | |
| const value = await driver.get(key) | |
| return value === null ? null : JSON.stringify(await decode(JSON.parse(value))) | |
| }, | |
| setItem: async (key, value) => { | |
| const version = (versions.get(key) ?? 0) + 1 | |
| versions.set(key, version) | |
| const encoded = JSON.stringify(await encode(JSON.parse(value))) | |
| if (versions.get(key) === version) await driver.set(key, encoded) | |
| }, | |
| removeItem: async (key) => { | |
| versions.set(key, (versions.get(key) ?? 0) + 1) | |
| await driver.remove(key) | |
| }, | |
| putBlob, | |
| } | |
| } | |
| export function createBrowserDraftStore(): DraftStore { | |
| const request = indexedDB.open("opencode-drafts", 1) | |
| request.addEventListener("upgradeneeded", () => { | |
| request.result.createObjectStore("documents") | |
| request.result.createObjectStore("blobs") | |
| }) | |
| const db = new Promise<IDBDatabase>((resolve, reject) => { | |
| request.addEventListener("success", () => { | |
| const database = request.result | |
| const transaction = database.transaction(["documents", "blobs"], "readwrite") | |
| const documents = transaction.objectStore("documents").getAll() | |
| documents.addEventListener("success", () => { | |
| const used = new Set<string>() | |
| JSON.parse(`[${documents.result.join(",")}]`, (_key, item) => { | |
| if (item?.blob && typeof item.blob.id === "string") used.add(item.blob.id) | |
| return item | |
| }) | |
| const blobs = transaction.objectStore("blobs").openKeyCursor() | |
| blobs.addEventListener("success", () => { | |
| const cursor = blobs.result | |
| if (!cursor) return | |
| if (!used.has(String(cursor.key))) cursor.delete() | |
| cursor.continue() | |
| }) | |
| }) | |
| transaction.addEventListener("complete", () => resolve(database)) | |
| transaction.addEventListener("abort", () => resolve(database)) | |
| }) | |
| request.addEventListener("error", () => reject(request.error)) | |
| }) | |
| const get = async (store: string, key: string) => { | |
| const result = (await db).transaction(store).objectStore(store).get(key) | |
| return new Promise<unknown>((resolve, reject) => { | |
| result.addEventListener("success", () => resolve(result.result)) | |
| result.addEventListener("error", () => reject(result.error)) | |
| }) | |
| } | |
| const write = async (store: string, key: string, value?: unknown) => { | |
| const transaction = (await db).transaction(store, "readwrite") | |
| if (value === undefined) transaction.objectStore(store).delete(key) | |
| else transaction.objectStore(store).put(value, key) | |
| return new Promise<void>((resolve, reject) => { | |
| transaction.addEventListener("complete", () => resolve()) | |
| transaction.addEventListener("error", () => reject(transaction.error)) | |
| }) | |
| } | |
| return createDraftStore({ | |
| get: async (key) => ((await get("documents", key)) as string | undefined) ?? null, | |
| set: (key, value) => write("documents", key, value), | |
| remove: (key) => write("documents", key), | |
| putBlob: async (blob) => { | |
| const id = await blobID(blob) | |
| await write("blobs", id, blob) | |
| return id | |
| }, | |
| getBlob: async (id) => ((await get("blobs", id)) as Blob | undefined) ?? null, | |
| }) | |
| } | |
| export async function blobDataUrl(blob: BlobReference, mime: string) { | |
| const data = await fetch(blob.url).then((response) => response.blob()) | |
| return new Promise<string>((resolve, reject) => { | |
| const reader = new FileReader() | |
| reader.addEventListener("error", () => reject(reader.error)) | |
| reader.addEventListener("load", () => { | |
| const value = typeof reader.result === "string" ? reader.result : "" | |
| resolve(`data:${mime};base64,${value.slice(value.indexOf(",") + 1)}`) | |
| }) | |
| reader.readAsDataURL(data) | |
| }) | |
| } | |
| export function createLegacyBlobReference(dataUrl: string): BlobReference { | |
| return { id: dataUrl, url: dataUrl } | |
| } | |