loro-repo 0.5.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -1
- package/dist/index.cjs +511 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -6
- package/dist/index.d.ts +56 -6
- package/dist/index.js +511 -97
- package/dist/index.js.map +1 -1
- package/dist/storage/filesystem.cjs +17 -0
- package/dist/storage/filesystem.cjs.map +1 -1
- package/dist/storage/filesystem.d.cts +2 -1
- package/dist/storage/filesystem.d.ts +2 -1
- package/dist/storage/filesystem.js +17 -0
- package/dist/storage/filesystem.js.map +1 -1
- package/dist/storage/indexeddb.cjs +4 -0
- package/dist/storage/indexeddb.cjs.map +1 -1
- package/dist/storage/indexeddb.d.cts +2 -1
- package/dist/storage/indexeddb.d.ts +2 -1
- package/dist/storage/indexeddb.js +4 -0
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/transport/broadcast-channel.cjs +131 -1
- package/dist/transport/broadcast-channel.cjs.map +1 -1
- package/dist/transport/broadcast-channel.d.cts +20 -3
- package/dist/transport/broadcast-channel.d.ts +20 -3
- package/dist/transport/broadcast-channel.js +130 -1
- package/dist/transport/broadcast-channel.js.map +1 -1
- package/dist/transport/websocket.cjs +348 -24
- package/dist/transport/websocket.cjs.map +1 -1
- package/dist/transport/websocket.d.cts +47 -5
- package/dist/transport/websocket.d.ts +47 -5
- package/dist/transport/websocket.js +349 -24
- package/dist/transport/websocket.js.map +1 -1
- package/dist/types.d.cts +116 -4
- package/dist/types.d.ts +116 -4
- package/package.json +7 -7
|
@@ -72,6 +72,12 @@ var FileSystemStorageAdaptor = class {
|
|
|
72
72
|
await this.writeDocSnapshot(docId, consolidated);
|
|
73
73
|
return doc;
|
|
74
74
|
}
|
|
75
|
+
async deleteDoc(docId) {
|
|
76
|
+
await this.initPromise;
|
|
77
|
+
await removeIfExists(this.docSnapshotPath(docId));
|
|
78
|
+
await removeDirIfExists(this.docUpdatesDir(docId));
|
|
79
|
+
await removeDirIfExists(this.docDir(docId));
|
|
80
|
+
}
|
|
75
81
|
async loadMeta() {
|
|
76
82
|
await this.initPromise;
|
|
77
83
|
const updatePaths = (await listFiles(this.metaUpdatesDir)).map((file) => node_path.join(this.metaUpdatesDir, file));
|
|
@@ -176,6 +182,17 @@ async function removeIfExists(filePath) {
|
|
|
176
182
|
throw error;
|
|
177
183
|
}
|
|
178
184
|
}
|
|
185
|
+
async function removeDirIfExists(dir) {
|
|
186
|
+
try {
|
|
187
|
+
await node_fs.promises.rm(dir, {
|
|
188
|
+
recursive: true,
|
|
189
|
+
force: true
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error.code === "ENOENT") return;
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
179
196
|
async function listFiles(dir) {
|
|
180
197
|
try {
|
|
181
198
|
return (await node_fs.promises.readdir(dir)).sort();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.cjs","names":["path","LoroDoc","updates: Uint8Array[]","Flock","fs"],"sources":["../../src/storage/filesystem.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport { Flock, type ExportBundle } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type { AssetId, StorageAdapter, StorageSavePayload } from \"../types\";\n\nconst textDecoder = new TextDecoder();\n\nexport interface FileSystemStorageAdaptorOptions {\n /**\n * Base directory where metadata, document snapshots, and assets will be stored.\n * Defaults to `.loro-repo` inside the current working directory.\n */\n readonly baseDir?: string;\n /**\n * Subdirectory dedicated to document persistence. Defaults to `docs`.\n */\n readonly docsDirName?: string;\n /**\n * Subdirectory dedicated to asset blobs. Defaults to `assets`.\n */\n readonly assetsDirName?: string;\n /**\n * File name for the metadata snapshot bundle. Defaults to `meta.json`.\n */\n readonly metaFileName?: string;\n}\n\nexport class FileSystemStorageAdaptor implements StorageAdapter {\n private readonly baseDir: string;\n private readonly docsDir: string;\n private readonly assetsDir: string;\n private readonly metaDir: string;\n private readonly metaUpdatesDir: string;\n private readonly legacyMetaPath: string;\n private readonly initPromise: Promise<void>;\n private updateCounter = 0;\n private metaUpdateCounter = 0;\n\n constructor(options: FileSystemStorageAdaptorOptions = {}) {\n this.baseDir = path.resolve(\n options.baseDir ?? path.join(process.cwd(), \".loro-repo\"),\n );\n this.docsDir = path.join(this.baseDir, options.docsDirName ?? \"docs\");\n this.assetsDir = path.join(this.baseDir, options.assetsDirName ?? \"assets\");\n this.metaDir = path.join(this.baseDir, \"meta\");\n this.metaUpdatesDir = path.join(this.metaDir, \"updates\");\n this.legacyMetaPath = path.join(\n this.baseDir,\n options.metaFileName ?? \"meta.json\",\n );\n this.initPromise = this.ensureLayout();\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n await this.initPromise;\n switch (payload.type) {\n case \"doc-snapshot\":\n await this.writeDocSnapshot(payload.docId, payload.snapshot);\n return;\n case \"doc-update\":\n await this.enqueueDocUpdate(payload.docId, payload.update);\n return;\n case \"asset\":\n await this.writeAsset(payload.assetId, payload.data);\n return;\n case \"meta\":\n await this.enqueueMetaUpdate(payload.update);\n return;\n default:\n throw new Error(`Unsupported payload type: ${(payload as { type: string }).type}`);\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n await this.initPromise;\n const filePath = this.assetPath(assetId);\n await removeIfExists(filePath);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n await this.initPromise;\n const snapshotPath = this.docSnapshotPath(docId);\n const snapshotBytes = await readFileIfExists(snapshotPath);\n const updateDir = this.docUpdatesDir(docId);\n const updateFiles = await listFiles(updateDir);\n\n if (!snapshotBytes && updateFiles.length === 0) {\n return undefined;\n }\n\n const doc = snapshotBytes\n ? LoroDoc.fromSnapshot(snapshotBytes)\n : new LoroDoc();\n\n if (updateFiles.length === 0) {\n return doc;\n }\n\n const updatePaths = updateFiles.map((file) => path.join(updateDir, file));\n for (const updatePath of updatePaths) {\n const update = await readFileIfExists(updatePath);\n if (!update) continue;\n doc.import(update);\n }\n\n await Promise.all(updatePaths.map((filePath) => removeIfExists(filePath)));\n\n const consolidated = doc.export({ mode: \"snapshot\" });\n await this.writeDocSnapshot(docId, consolidated);\n return doc;\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n await this.initPromise;\n const updateFiles = await listFiles(this.metaUpdatesDir);\n const updatePaths = updateFiles.map((file) => path.join(this.metaUpdatesDir, file));\n const updates: Uint8Array[] = [];\n\n for (const updatePath of updatePaths) {\n const bytes = await readFileIfExists(updatePath);\n if (bytes) {\n updates.push(bytes);\n }\n }\n\n const legacy = await readFileIfExists(this.legacyMetaPath);\n const totalUpdates = updates.length + (legacy ? 1 : 0);\n if (totalUpdates === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n flock.importJson(JSON.parse(textDecoder.decode(legacy)) as ExportBundle);\n }\n for (const bytes of updates) {\n flock.importJson(JSON.parse(textDecoder.decode(bytes)) as ExportBundle);\n }\n } catch (error) {\n throw new Error(\"Failed to hydrate metadata snapshot\", { cause: error });\n }\n\n if (totalUpdates > 1 || legacy) {\n const snapshot = flock.exportJson();\n const encoded = new TextEncoder().encode(JSON.stringify(snapshot));\n await this.compactMeta(encoded, updatePaths, Boolean(legacy));\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n await this.initPromise;\n return readFileIfExists(this.assetPath(assetId));\n }\n\n private async ensureLayout(): Promise<void> {\n await Promise.all([\n ensureDir(this.baseDir),\n ensureDir(this.docsDir),\n ensureDir(this.assetsDir),\n ensureDir(this.metaUpdatesDir),\n ]);\n }\n\n private async writeDocSnapshot(\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const targetDir = this.docDir(docId);\n await ensureDir(targetDir);\n await writeFileAtomic(this.docSnapshotPath(docId), snapshot);\n }\n\n private async enqueueDocUpdate(\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n const dir = this.docUpdatesDir(docId);\n await ensureDir(dir);\n const counter = (this.updateCounter = (this.updateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.bin`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async writeAsset(assetId: AssetId, data: Uint8Array): Promise<void> {\n const filePath = this.assetPath(assetId);\n await ensureDir(path.dirname(filePath));\n await writeFileAtomic(filePath, data);\n }\n\n private async enqueueMetaUpdate(update: Uint8Array): Promise<void> {\n const dir = this.metaUpdatesDir;\n await ensureDir(dir);\n const counter = (this.metaUpdateCounter = (this.metaUpdateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.json`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async compactMeta(\n update: Uint8Array,\n previousUpdates: string[],\n hadLegacy: boolean,\n ): Promise<void> {\n const targetPath = path.join(this.metaUpdatesDir, \"000000-full.json\");\n await ensureDir(this.metaUpdatesDir);\n await writeFileAtomic(targetPath, update);\n\n const removals = hadLegacy ? [this.legacyMetaPath] : [];\n removals.push(\n ...previousUpdates.filter((filePath) => path.resolve(filePath) !== path.resolve(targetPath)),\n );\n await Promise.all(removals.map((filePath) => removeIfExists(filePath)));\n }\n\n private docDir(docId: string): string {\n return path.join(this.docsDir, encodeComponent(docId));\n }\n\n private docSnapshotPath(docId: string): string {\n return path.join(this.docDir(docId), \"snapshot.bin\");\n }\n\n private docUpdatesDir(docId: string): string {\n return path.join(this.docDir(docId), \"updates\");\n }\n\n private assetPath(assetId: AssetId): string {\n return path.join(this.assetsDir, encodeComponent(assetId));\n }\n}\n\nfunction encodeComponent(value: string): string {\n return Buffer.from(value, \"utf8\").toString(\"base64url\");\n}\n\nasync function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nasync function readFileIfExists(filePath: string): Promise<Uint8Array | undefined> {\n try {\n const data = await fs.readFile(filePath);\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function removeIfExists(filePath: string): Promise<void> {\n try {\n await fs.rm(filePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir);\n return entries.sort();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [];\n }\n throw error;\n }\n}\n\nasync function writeFileAtomic(\n targetPath: string,\n data: Uint8Array,\n): Promise<void> {\n const dir = path.dirname(targetPath);\n await ensureDir(dir);\n const tempPath = path.join(dir, `.tmp-${randomUUID()}`);\n await fs.writeFile(tempPath, data);\n await fs.rename(tempPath, targetPath);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filesystem.cjs","names":["path","LoroDoc","updates: Uint8Array[]","Flock","fs"],"sources":["../../src/storage/filesystem.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport { Flock, type ExportBundle } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type { AssetId, StorageAdapter, StorageSavePayload } from \"../types\";\n\nconst textDecoder = new TextDecoder();\n\nexport interface FileSystemStorageAdaptorOptions {\n /**\n * Base directory where metadata, document snapshots, and assets will be stored.\n * Defaults to `.loro-repo` inside the current working directory.\n */\n readonly baseDir?: string;\n /**\n * Subdirectory dedicated to document persistence. Defaults to `docs`.\n */\n readonly docsDirName?: string;\n /**\n * Subdirectory dedicated to asset blobs. Defaults to `assets`.\n */\n readonly assetsDirName?: string;\n /**\n * File name for the metadata snapshot bundle. Defaults to `meta.json`.\n */\n readonly metaFileName?: string;\n}\n\nexport class FileSystemStorageAdaptor implements StorageAdapter {\n private readonly baseDir: string;\n private readonly docsDir: string;\n private readonly assetsDir: string;\n private readonly metaDir: string;\n private readonly metaUpdatesDir: string;\n private readonly legacyMetaPath: string;\n private readonly initPromise: Promise<void>;\n private updateCounter = 0;\n private metaUpdateCounter = 0;\n\n constructor(options: FileSystemStorageAdaptorOptions = {}) {\n this.baseDir = path.resolve(\n options.baseDir ?? path.join(process.cwd(), \".loro-repo\"),\n );\n this.docsDir = path.join(this.baseDir, options.docsDirName ?? \"docs\");\n this.assetsDir = path.join(this.baseDir, options.assetsDirName ?? \"assets\");\n this.metaDir = path.join(this.baseDir, \"meta\");\n this.metaUpdatesDir = path.join(this.metaDir, \"updates\");\n this.legacyMetaPath = path.join(\n this.baseDir,\n options.metaFileName ?? \"meta.json\",\n );\n this.initPromise = this.ensureLayout();\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n await this.initPromise;\n switch (payload.type) {\n case \"doc-snapshot\":\n await this.writeDocSnapshot(payload.docId, payload.snapshot);\n return;\n case \"doc-update\":\n await this.enqueueDocUpdate(payload.docId, payload.update);\n return;\n case \"asset\":\n await this.writeAsset(payload.assetId, payload.data);\n return;\n case \"meta\":\n await this.enqueueMetaUpdate(payload.update);\n return;\n default:\n throw new Error(`Unsupported payload type: ${(payload as { type: string }).type}`);\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n await this.initPromise;\n const filePath = this.assetPath(assetId);\n await removeIfExists(filePath);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n await this.initPromise;\n const snapshotPath = this.docSnapshotPath(docId);\n const snapshotBytes = await readFileIfExists(snapshotPath);\n const updateDir = this.docUpdatesDir(docId);\n const updateFiles = await listFiles(updateDir);\n\n if (!snapshotBytes && updateFiles.length === 0) {\n return undefined;\n }\n\n const doc = snapshotBytes\n ? LoroDoc.fromSnapshot(snapshotBytes)\n : new LoroDoc();\n\n if (updateFiles.length === 0) {\n return doc;\n }\n\n const updatePaths = updateFiles.map((file) => path.join(updateDir, file));\n for (const updatePath of updatePaths) {\n const update = await readFileIfExists(updatePath);\n if (!update) continue;\n doc.import(update);\n }\n\n await Promise.all(updatePaths.map((filePath) => removeIfExists(filePath)));\n\n const consolidated = doc.export({ mode: \"snapshot\" });\n await this.writeDocSnapshot(docId, consolidated);\n return doc;\n }\n\n async deleteDoc(docId: string): Promise<void> {\n await this.initPromise;\n await removeIfExists(this.docSnapshotPath(docId));\n await removeDirIfExists(this.docUpdatesDir(docId));\n await removeDirIfExists(this.docDir(docId));\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n await this.initPromise;\n const updateFiles = await listFiles(this.metaUpdatesDir);\n const updatePaths = updateFiles.map((file) => path.join(this.metaUpdatesDir, file));\n const updates: Uint8Array[] = [];\n\n for (const updatePath of updatePaths) {\n const bytes = await readFileIfExists(updatePath);\n if (bytes) {\n updates.push(bytes);\n }\n }\n\n const legacy = await readFileIfExists(this.legacyMetaPath);\n const totalUpdates = updates.length + (legacy ? 1 : 0);\n if (totalUpdates === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n flock.importJson(JSON.parse(textDecoder.decode(legacy)) as ExportBundle);\n }\n for (const bytes of updates) {\n flock.importJson(JSON.parse(textDecoder.decode(bytes)) as ExportBundle);\n }\n } catch (error) {\n throw new Error(\"Failed to hydrate metadata snapshot\", { cause: error });\n }\n\n if (totalUpdates > 1 || legacy) {\n const snapshot = flock.exportJson();\n const encoded = new TextEncoder().encode(JSON.stringify(snapshot));\n await this.compactMeta(encoded, updatePaths, Boolean(legacy));\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n await this.initPromise;\n return readFileIfExists(this.assetPath(assetId));\n }\n\n private async ensureLayout(): Promise<void> {\n await Promise.all([\n ensureDir(this.baseDir),\n ensureDir(this.docsDir),\n ensureDir(this.assetsDir),\n ensureDir(this.metaUpdatesDir),\n ]);\n }\n\n private async writeDocSnapshot(\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const targetDir = this.docDir(docId);\n await ensureDir(targetDir);\n await writeFileAtomic(this.docSnapshotPath(docId), snapshot);\n }\n\n private async enqueueDocUpdate(\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n const dir = this.docUpdatesDir(docId);\n await ensureDir(dir);\n const counter = (this.updateCounter = (this.updateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.bin`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async writeAsset(assetId: AssetId, data: Uint8Array): Promise<void> {\n const filePath = this.assetPath(assetId);\n await ensureDir(path.dirname(filePath));\n await writeFileAtomic(filePath, data);\n }\n\n private async enqueueMetaUpdate(update: Uint8Array): Promise<void> {\n const dir = this.metaUpdatesDir;\n await ensureDir(dir);\n const counter = (this.metaUpdateCounter = (this.metaUpdateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.json`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async compactMeta(\n update: Uint8Array,\n previousUpdates: string[],\n hadLegacy: boolean,\n ): Promise<void> {\n const targetPath = path.join(this.metaUpdatesDir, \"000000-full.json\");\n await ensureDir(this.metaUpdatesDir);\n await writeFileAtomic(targetPath, update);\n\n const removals = hadLegacy ? [this.legacyMetaPath] : [];\n removals.push(\n ...previousUpdates.filter((filePath) => path.resolve(filePath) !== path.resolve(targetPath)),\n );\n await Promise.all(removals.map((filePath) => removeIfExists(filePath)));\n }\n\n private docDir(docId: string): string {\n return path.join(this.docsDir, encodeComponent(docId));\n }\n\n private docSnapshotPath(docId: string): string {\n return path.join(this.docDir(docId), \"snapshot.bin\");\n }\n\n private docUpdatesDir(docId: string): string {\n return path.join(this.docDir(docId), \"updates\");\n }\n\n private assetPath(assetId: AssetId): string {\n return path.join(this.assetsDir, encodeComponent(assetId));\n }\n}\n\nfunction encodeComponent(value: string): string {\n return Buffer.from(value, \"utf8\").toString(\"base64url\");\n}\n\nasync function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nasync function readFileIfExists(filePath: string): Promise<Uint8Array | undefined> {\n try {\n const data = await fs.readFile(filePath);\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function removeIfExists(filePath: string): Promise<void> {\n try {\n await fs.rm(filePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nasync function removeDirIfExists(dir: string): Promise<void> {\n try {\n await fs.rm(dir, { recursive: true, force: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir);\n return entries.sort();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [];\n }\n throw error;\n }\n}\n\nasync function writeFileAtomic(\n targetPath: string,\n data: Uint8Array,\n): Promise<void> {\n const dir = path.dirname(targetPath);\n await ensureDir(dir);\n const tempPath = path.join(dir, `.tmp-${randomUUID()}`);\n await fs.writeFile(tempPath, data);\n await fs.rename(tempPath, targetPath);\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as StorageSavePayload, E as StorageAdapter, r as AssetId } from "../types.cjs";
|
|
2
2
|
import { Flock } from "@loro-dev/flock";
|
|
3
3
|
import { LoroDoc } from "loro-crdt";
|
|
4
4
|
|
|
@@ -36,6 +36,7 @@ declare class FileSystemStorageAdaptor implements StorageAdapter {
|
|
|
36
36
|
save(payload: StorageSavePayload): Promise<void>;
|
|
37
37
|
deleteAsset(assetId: AssetId): Promise<void>;
|
|
38
38
|
loadDoc(docId: string): Promise<LoroDoc | undefined>;
|
|
39
|
+
deleteDoc(docId: string): Promise<void>;
|
|
39
40
|
loadMeta(): Promise<Flock | undefined>;
|
|
40
41
|
loadAsset(assetId: AssetId): Promise<Uint8Array | undefined>;
|
|
41
42
|
private ensureLayout;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as StorageSavePayload, E as StorageAdapter, r as AssetId } from "../types.js";
|
|
2
2
|
import { Flock } from "@loro-dev/flock";
|
|
3
3
|
import { LoroDoc } from "loro-crdt";
|
|
4
4
|
|
|
@@ -36,6 +36,7 @@ declare class FileSystemStorageAdaptor implements StorageAdapter {
|
|
|
36
36
|
save(payload: StorageSavePayload): Promise<void>;
|
|
37
37
|
deleteAsset(assetId: AssetId): Promise<void>;
|
|
38
38
|
loadDoc(docId: string): Promise<LoroDoc | undefined>;
|
|
39
|
+
deleteDoc(docId: string): Promise<void>;
|
|
39
40
|
loadMeta(): Promise<Flock | undefined>;
|
|
40
41
|
loadAsset(assetId: AssetId): Promise<Uint8Array | undefined>;
|
|
41
42
|
private ensureLayout;
|
|
@@ -66,6 +66,12 @@ var FileSystemStorageAdaptor = class {
|
|
|
66
66
|
await this.writeDocSnapshot(docId, consolidated);
|
|
67
67
|
return doc;
|
|
68
68
|
}
|
|
69
|
+
async deleteDoc(docId) {
|
|
70
|
+
await this.initPromise;
|
|
71
|
+
await removeIfExists(this.docSnapshotPath(docId));
|
|
72
|
+
await removeDirIfExists(this.docUpdatesDir(docId));
|
|
73
|
+
await removeDirIfExists(this.docDir(docId));
|
|
74
|
+
}
|
|
69
75
|
async loadMeta() {
|
|
70
76
|
await this.initPromise;
|
|
71
77
|
const updatePaths = (await listFiles(this.metaUpdatesDir)).map((file) => path.join(this.metaUpdatesDir, file));
|
|
@@ -170,6 +176,17 @@ async function removeIfExists(filePath) {
|
|
|
170
176
|
throw error;
|
|
171
177
|
}
|
|
172
178
|
}
|
|
179
|
+
async function removeDirIfExists(dir) {
|
|
180
|
+
try {
|
|
181
|
+
await promises.rm(dir, {
|
|
182
|
+
recursive: true,
|
|
183
|
+
force: true
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error.code === "ENOENT") return;
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
173
190
|
async function listFiles(dir) {
|
|
174
191
|
try {
|
|
175
192
|
return (await promises.readdir(dir)).sort();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.js","names":["updates: Uint8Array[]","fs"],"sources":["../../src/storage/filesystem.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport { Flock, type ExportBundle } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type { AssetId, StorageAdapter, StorageSavePayload } from \"../types\";\n\nconst textDecoder = new TextDecoder();\n\nexport interface FileSystemStorageAdaptorOptions {\n /**\n * Base directory where metadata, document snapshots, and assets will be stored.\n * Defaults to `.loro-repo` inside the current working directory.\n */\n readonly baseDir?: string;\n /**\n * Subdirectory dedicated to document persistence. Defaults to `docs`.\n */\n readonly docsDirName?: string;\n /**\n * Subdirectory dedicated to asset blobs. Defaults to `assets`.\n */\n readonly assetsDirName?: string;\n /**\n * File name for the metadata snapshot bundle. Defaults to `meta.json`.\n */\n readonly metaFileName?: string;\n}\n\nexport class FileSystemStorageAdaptor implements StorageAdapter {\n private readonly baseDir: string;\n private readonly docsDir: string;\n private readonly assetsDir: string;\n private readonly metaDir: string;\n private readonly metaUpdatesDir: string;\n private readonly legacyMetaPath: string;\n private readonly initPromise: Promise<void>;\n private updateCounter = 0;\n private metaUpdateCounter = 0;\n\n constructor(options: FileSystemStorageAdaptorOptions = {}) {\n this.baseDir = path.resolve(\n options.baseDir ?? path.join(process.cwd(), \".loro-repo\"),\n );\n this.docsDir = path.join(this.baseDir, options.docsDirName ?? \"docs\");\n this.assetsDir = path.join(this.baseDir, options.assetsDirName ?? \"assets\");\n this.metaDir = path.join(this.baseDir, \"meta\");\n this.metaUpdatesDir = path.join(this.metaDir, \"updates\");\n this.legacyMetaPath = path.join(\n this.baseDir,\n options.metaFileName ?? \"meta.json\",\n );\n this.initPromise = this.ensureLayout();\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n await this.initPromise;\n switch (payload.type) {\n case \"doc-snapshot\":\n await this.writeDocSnapshot(payload.docId, payload.snapshot);\n return;\n case \"doc-update\":\n await this.enqueueDocUpdate(payload.docId, payload.update);\n return;\n case \"asset\":\n await this.writeAsset(payload.assetId, payload.data);\n return;\n case \"meta\":\n await this.enqueueMetaUpdate(payload.update);\n return;\n default:\n throw new Error(`Unsupported payload type: ${(payload as { type: string }).type}`);\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n await this.initPromise;\n const filePath = this.assetPath(assetId);\n await removeIfExists(filePath);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n await this.initPromise;\n const snapshotPath = this.docSnapshotPath(docId);\n const snapshotBytes = await readFileIfExists(snapshotPath);\n const updateDir = this.docUpdatesDir(docId);\n const updateFiles = await listFiles(updateDir);\n\n if (!snapshotBytes && updateFiles.length === 0) {\n return undefined;\n }\n\n const doc = snapshotBytes\n ? LoroDoc.fromSnapshot(snapshotBytes)\n : new LoroDoc();\n\n if (updateFiles.length === 0) {\n return doc;\n }\n\n const updatePaths = updateFiles.map((file) => path.join(updateDir, file));\n for (const updatePath of updatePaths) {\n const update = await readFileIfExists(updatePath);\n if (!update) continue;\n doc.import(update);\n }\n\n await Promise.all(updatePaths.map((filePath) => removeIfExists(filePath)));\n\n const consolidated = doc.export({ mode: \"snapshot\" });\n await this.writeDocSnapshot(docId, consolidated);\n return doc;\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n await this.initPromise;\n const updateFiles = await listFiles(this.metaUpdatesDir);\n const updatePaths = updateFiles.map((file) => path.join(this.metaUpdatesDir, file));\n const updates: Uint8Array[] = [];\n\n for (const updatePath of updatePaths) {\n const bytes = await readFileIfExists(updatePath);\n if (bytes) {\n updates.push(bytes);\n }\n }\n\n const legacy = await readFileIfExists(this.legacyMetaPath);\n const totalUpdates = updates.length + (legacy ? 1 : 0);\n if (totalUpdates === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n flock.importJson(JSON.parse(textDecoder.decode(legacy)) as ExportBundle);\n }\n for (const bytes of updates) {\n flock.importJson(JSON.parse(textDecoder.decode(bytes)) as ExportBundle);\n }\n } catch (error) {\n throw new Error(\"Failed to hydrate metadata snapshot\", { cause: error });\n }\n\n if (totalUpdates > 1 || legacy) {\n const snapshot = flock.exportJson();\n const encoded = new TextEncoder().encode(JSON.stringify(snapshot));\n await this.compactMeta(encoded, updatePaths, Boolean(legacy));\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n await this.initPromise;\n return readFileIfExists(this.assetPath(assetId));\n }\n\n private async ensureLayout(): Promise<void> {\n await Promise.all([\n ensureDir(this.baseDir),\n ensureDir(this.docsDir),\n ensureDir(this.assetsDir),\n ensureDir(this.metaUpdatesDir),\n ]);\n }\n\n private async writeDocSnapshot(\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const targetDir = this.docDir(docId);\n await ensureDir(targetDir);\n await writeFileAtomic(this.docSnapshotPath(docId), snapshot);\n }\n\n private async enqueueDocUpdate(\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n const dir = this.docUpdatesDir(docId);\n await ensureDir(dir);\n const counter = (this.updateCounter = (this.updateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.bin`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async writeAsset(assetId: AssetId, data: Uint8Array): Promise<void> {\n const filePath = this.assetPath(assetId);\n await ensureDir(path.dirname(filePath));\n await writeFileAtomic(filePath, data);\n }\n\n private async enqueueMetaUpdate(update: Uint8Array): Promise<void> {\n const dir = this.metaUpdatesDir;\n await ensureDir(dir);\n const counter = (this.metaUpdateCounter = (this.metaUpdateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.json`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async compactMeta(\n update: Uint8Array,\n previousUpdates: string[],\n hadLegacy: boolean,\n ): Promise<void> {\n const targetPath = path.join(this.metaUpdatesDir, \"000000-full.json\");\n await ensureDir(this.metaUpdatesDir);\n await writeFileAtomic(targetPath, update);\n\n const removals = hadLegacy ? [this.legacyMetaPath] : [];\n removals.push(\n ...previousUpdates.filter((filePath) => path.resolve(filePath) !== path.resolve(targetPath)),\n );\n await Promise.all(removals.map((filePath) => removeIfExists(filePath)));\n }\n\n private docDir(docId: string): string {\n return path.join(this.docsDir, encodeComponent(docId));\n }\n\n private docSnapshotPath(docId: string): string {\n return path.join(this.docDir(docId), \"snapshot.bin\");\n }\n\n private docUpdatesDir(docId: string): string {\n return path.join(this.docDir(docId), \"updates\");\n }\n\n private assetPath(assetId: AssetId): string {\n return path.join(this.assetsDir, encodeComponent(assetId));\n }\n}\n\nfunction encodeComponent(value: string): string {\n return Buffer.from(value, \"utf8\").toString(\"base64url\");\n}\n\nasync function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nasync function readFileIfExists(filePath: string): Promise<Uint8Array | undefined> {\n try {\n const data = await fs.readFile(filePath);\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function removeIfExists(filePath: string): Promise<void> {\n try {\n await fs.rm(filePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir);\n return entries.sort();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [];\n }\n throw error;\n }\n}\n\nasync function writeFileAtomic(\n targetPath: string,\n data: Uint8Array,\n): Promise<void> {\n const dir = path.dirname(targetPath);\n await ensureDir(dir);\n const tempPath = path.join(dir, `.tmp-${randomUUID()}`);\n await fs.writeFile(tempPath, data);\n await fs.rename(tempPath, targetPath);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filesystem.js","names":["updates: Uint8Array[]","fs"],"sources":["../../src/storage/filesystem.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport { Flock, type ExportBundle } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type { AssetId, StorageAdapter, StorageSavePayload } from \"../types\";\n\nconst textDecoder = new TextDecoder();\n\nexport interface FileSystemStorageAdaptorOptions {\n /**\n * Base directory where metadata, document snapshots, and assets will be stored.\n * Defaults to `.loro-repo` inside the current working directory.\n */\n readonly baseDir?: string;\n /**\n * Subdirectory dedicated to document persistence. Defaults to `docs`.\n */\n readonly docsDirName?: string;\n /**\n * Subdirectory dedicated to asset blobs. Defaults to `assets`.\n */\n readonly assetsDirName?: string;\n /**\n * File name for the metadata snapshot bundle. Defaults to `meta.json`.\n */\n readonly metaFileName?: string;\n}\n\nexport class FileSystemStorageAdaptor implements StorageAdapter {\n private readonly baseDir: string;\n private readonly docsDir: string;\n private readonly assetsDir: string;\n private readonly metaDir: string;\n private readonly metaUpdatesDir: string;\n private readonly legacyMetaPath: string;\n private readonly initPromise: Promise<void>;\n private updateCounter = 0;\n private metaUpdateCounter = 0;\n\n constructor(options: FileSystemStorageAdaptorOptions = {}) {\n this.baseDir = path.resolve(\n options.baseDir ?? path.join(process.cwd(), \".loro-repo\"),\n );\n this.docsDir = path.join(this.baseDir, options.docsDirName ?? \"docs\");\n this.assetsDir = path.join(this.baseDir, options.assetsDirName ?? \"assets\");\n this.metaDir = path.join(this.baseDir, \"meta\");\n this.metaUpdatesDir = path.join(this.metaDir, \"updates\");\n this.legacyMetaPath = path.join(\n this.baseDir,\n options.metaFileName ?? \"meta.json\",\n );\n this.initPromise = this.ensureLayout();\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n await this.initPromise;\n switch (payload.type) {\n case \"doc-snapshot\":\n await this.writeDocSnapshot(payload.docId, payload.snapshot);\n return;\n case \"doc-update\":\n await this.enqueueDocUpdate(payload.docId, payload.update);\n return;\n case \"asset\":\n await this.writeAsset(payload.assetId, payload.data);\n return;\n case \"meta\":\n await this.enqueueMetaUpdate(payload.update);\n return;\n default:\n throw new Error(`Unsupported payload type: ${(payload as { type: string }).type}`);\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n await this.initPromise;\n const filePath = this.assetPath(assetId);\n await removeIfExists(filePath);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n await this.initPromise;\n const snapshotPath = this.docSnapshotPath(docId);\n const snapshotBytes = await readFileIfExists(snapshotPath);\n const updateDir = this.docUpdatesDir(docId);\n const updateFiles = await listFiles(updateDir);\n\n if (!snapshotBytes && updateFiles.length === 0) {\n return undefined;\n }\n\n const doc = snapshotBytes\n ? LoroDoc.fromSnapshot(snapshotBytes)\n : new LoroDoc();\n\n if (updateFiles.length === 0) {\n return doc;\n }\n\n const updatePaths = updateFiles.map((file) => path.join(updateDir, file));\n for (const updatePath of updatePaths) {\n const update = await readFileIfExists(updatePath);\n if (!update) continue;\n doc.import(update);\n }\n\n await Promise.all(updatePaths.map((filePath) => removeIfExists(filePath)));\n\n const consolidated = doc.export({ mode: \"snapshot\" });\n await this.writeDocSnapshot(docId, consolidated);\n return doc;\n }\n\n async deleteDoc(docId: string): Promise<void> {\n await this.initPromise;\n await removeIfExists(this.docSnapshotPath(docId));\n await removeDirIfExists(this.docUpdatesDir(docId));\n await removeDirIfExists(this.docDir(docId));\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n await this.initPromise;\n const updateFiles = await listFiles(this.metaUpdatesDir);\n const updatePaths = updateFiles.map((file) => path.join(this.metaUpdatesDir, file));\n const updates: Uint8Array[] = [];\n\n for (const updatePath of updatePaths) {\n const bytes = await readFileIfExists(updatePath);\n if (bytes) {\n updates.push(bytes);\n }\n }\n\n const legacy = await readFileIfExists(this.legacyMetaPath);\n const totalUpdates = updates.length + (legacy ? 1 : 0);\n if (totalUpdates === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n flock.importJson(JSON.parse(textDecoder.decode(legacy)) as ExportBundle);\n }\n for (const bytes of updates) {\n flock.importJson(JSON.parse(textDecoder.decode(bytes)) as ExportBundle);\n }\n } catch (error) {\n throw new Error(\"Failed to hydrate metadata snapshot\", { cause: error });\n }\n\n if (totalUpdates > 1 || legacy) {\n const snapshot = flock.exportJson();\n const encoded = new TextEncoder().encode(JSON.stringify(snapshot));\n await this.compactMeta(encoded, updatePaths, Boolean(legacy));\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n await this.initPromise;\n return readFileIfExists(this.assetPath(assetId));\n }\n\n private async ensureLayout(): Promise<void> {\n await Promise.all([\n ensureDir(this.baseDir),\n ensureDir(this.docsDir),\n ensureDir(this.assetsDir),\n ensureDir(this.metaUpdatesDir),\n ]);\n }\n\n private async writeDocSnapshot(\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const targetDir = this.docDir(docId);\n await ensureDir(targetDir);\n await writeFileAtomic(this.docSnapshotPath(docId), snapshot);\n }\n\n private async enqueueDocUpdate(\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n const dir = this.docUpdatesDir(docId);\n await ensureDir(dir);\n const counter = (this.updateCounter = (this.updateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.bin`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async writeAsset(assetId: AssetId, data: Uint8Array): Promise<void> {\n const filePath = this.assetPath(assetId);\n await ensureDir(path.dirname(filePath));\n await writeFileAtomic(filePath, data);\n }\n\n private async enqueueMetaUpdate(update: Uint8Array): Promise<void> {\n const dir = this.metaUpdatesDir;\n await ensureDir(dir);\n const counter = (this.metaUpdateCounter = (this.metaUpdateCounter + 1) % 1_000_000);\n const timestamp = Date.now().toString().padStart(13, \"0\");\n const fileName = `${timestamp}-${counter.toString().padStart(6, \"0\")}.json`;\n const filePath = path.join(dir, fileName);\n await writeFileAtomic(filePath, update);\n }\n\n private async compactMeta(\n update: Uint8Array,\n previousUpdates: string[],\n hadLegacy: boolean,\n ): Promise<void> {\n const targetPath = path.join(this.metaUpdatesDir, \"000000-full.json\");\n await ensureDir(this.metaUpdatesDir);\n await writeFileAtomic(targetPath, update);\n\n const removals = hadLegacy ? [this.legacyMetaPath] : [];\n removals.push(\n ...previousUpdates.filter((filePath) => path.resolve(filePath) !== path.resolve(targetPath)),\n );\n await Promise.all(removals.map((filePath) => removeIfExists(filePath)));\n }\n\n private docDir(docId: string): string {\n return path.join(this.docsDir, encodeComponent(docId));\n }\n\n private docSnapshotPath(docId: string): string {\n return path.join(this.docDir(docId), \"snapshot.bin\");\n }\n\n private docUpdatesDir(docId: string): string {\n return path.join(this.docDir(docId), \"updates\");\n }\n\n private assetPath(assetId: AssetId): string {\n return path.join(this.assetsDir, encodeComponent(assetId));\n }\n}\n\nfunction encodeComponent(value: string): string {\n return Buffer.from(value, \"utf8\").toString(\"base64url\");\n}\n\nasync function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nasync function readFileIfExists(filePath: string): Promise<Uint8Array | undefined> {\n try {\n const data = await fs.readFile(filePath);\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function removeIfExists(filePath: string): Promise<void> {\n try {\n await fs.rm(filePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nasync function removeDirIfExists(dir: string): Promise<void> {\n try {\n await fs.rm(dir, { recursive: true, force: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return;\n }\n throw error;\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir);\n return entries.sort();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [];\n }\n throw error;\n }\n}\n\nasync function writeFileAtomic(\n targetPath: string,\n data: Uint8Array,\n): Promise<void> {\n const dir = path.dirname(targetPath);\n await ensureDir(dir);\n const tempPath = path.join(dir, `.tmp-${randomUUID()}`);\n await fs.writeFile(tempPath, data);\n await fs.rename(tempPath, targetPath);\n}\n"],"mappings":"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"}
|
|
@@ -113,6 +113,10 @@ var IndexedDBStorageAdaptor = class {
|
|
|
113
113
|
}
|
|
114
114
|
return doc;
|
|
115
115
|
}
|
|
116
|
+
async deleteDoc(docId) {
|
|
117
|
+
const db = await this.ensureDb();
|
|
118
|
+
await Promise.all([this.deleteKey(db, this.docStore, docId), this.clearDocUpdates(db, docId)]);
|
|
119
|
+
}
|
|
116
120
|
async loadMeta() {
|
|
117
121
|
const db = await this.ensureDb();
|
|
118
122
|
const legacy = await this.getBinaryFromDb(db, this.metaStore, this.metaKey);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb.cjs","names":["doc: LoroDoc","LoroDoc","consolidated: Uint8Array","Flock","queue: Uint8Array[]"],"sources":["../../src/storage/indexeddb.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type {\n AssetId,\n StorageAdapter,\n StorageSavePayload,\n} from \"../types\";\nimport type { ExportBundle } from \"@loro-dev/flock\";\n\nconst DEFAULT_DB_NAME = \"loro-repo\";\nconst DEFAULT_DB_VERSION = 2;\nconst DEFAULT_DOC_STORE = \"docs\";\nconst DEFAULT_META_STORE = \"meta\";\nconst DEFAULT_ASSET_STORE = \"assets\";\nconst DEFAULT_DOC_UPDATE_STORE = \"doc-updates\";\nconst DEFAULT_META_UPDATE_STORE = \"meta-updates\";\nconst DEFAULT_META_KEY = \"snapshot\";\n\ntype EventListenerOptions = {\n once?: boolean;\n};\n\ntype IDBFactory = {\n open(name: string, version?: number): IDBOpenDBRequest;\n};\n\ntype IDBOpenDBRequest = {\n result: IDBDatabase;\n error: unknown;\n onupgradeneeded: ((event: unknown) => void) | null;\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype ObjectStoreNames = {\n contains?(name: string): boolean;\n length?: number;\n item?(index: number): string | null;\n};\n\ntype IDBDatabase = {\n close(): void;\n createObjectStore(name: string): IDBObjectStore;\n transaction(\n storeName: string,\n mode?: IDBTransactionMode,\n ): IDBTransaction;\n objectStoreNames: ObjectStoreNames;\n};\n\ntype IDBTransactionMode = \"readonly\" | \"readwrite\";\n\ntype IDBTransaction = {\n objectStore(name: string): IDBObjectStore;\n oncomplete: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onabort: ((event: unknown) => void) | null;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype IDBObjectStore = {\n put(value: unknown, key?: unknown): IDBRequest<unknown>;\n get(key: unknown): IDBRequest<unknown>;\n delete(key: unknown): IDBRequest<unknown>;\n};\n\ntype IDBRequest<T> = {\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n result: T;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n\nfunction describeUnknown(cause: unknown): string {\n if (typeof cause === \"string\") return cause;\n if (typeof cause === \"number\" || typeof cause === \"boolean\") {\n return String(cause);\n }\n if (typeof cause === \"bigint\") {\n return cause.toString();\n }\n if (typeof cause === \"symbol\") {\n return cause.description ?? cause.toString();\n }\n if (typeof cause === \"function\") {\n return `[function ${cause.name ?? \"anonymous\"}]`;\n }\n if (cause && typeof cause === \"object\") {\n try {\n return JSON.stringify(cause);\n } catch {\n return \"[object]\";\n }\n }\n return String(cause);\n}\n\nexport interface IndexedDBStorageAdaptorOptions {\n readonly dbName?: string;\n readonly version?: number;\n readonly docStoreName?: string;\n readonly docUpdateStoreName?: string;\n readonly metaStoreName?: string;\n readonly metaUpdateStoreName?: string;\n readonly assetStoreName?: string;\n readonly metaKey?: string;\n}\n\nexport class IndexedDBStorageAdaptor implements StorageAdapter {\n private readonly idb: IDBFactory;\n private readonly dbName: string;\n private readonly version: number;\n private readonly docStore: string;\n private readonly docUpdateStore: string;\n private readonly metaStore: string;\n private readonly metaUpdateStore: string;\n private readonly assetStore: string;\n private readonly metaKey: string;\n private dbPromise?: Promise<IDBDatabase>;\n private closed = false;\n\n constructor(options: IndexedDBStorageAdaptorOptions = {}) {\n const idbFactory = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idbFactory) {\n throw new Error(\"IndexedDB is not available in this environment\");\n }\n this.idb = idbFactory;\n this.dbName = options.dbName ?? DEFAULT_DB_NAME;\n this.version = options.version ?? DEFAULT_DB_VERSION;\n this.docStore = options.docStoreName ?? DEFAULT_DOC_STORE;\n this.docUpdateStore = options.docUpdateStoreName ?? DEFAULT_DOC_UPDATE_STORE;\n this.metaStore = options.metaStoreName ?? DEFAULT_META_STORE;\n this.metaUpdateStore = options.metaUpdateStoreName ?? DEFAULT_META_UPDATE_STORE;\n this.assetStore = options.assetStoreName ?? DEFAULT_ASSET_STORE;\n this.metaKey = options.metaKey ?? DEFAULT_META_KEY;\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n const db = await this.ensureDb();\n switch (payload.type) {\n case \"doc-snapshot\": {\n const snapshot = payload.snapshot.slice();\n await this.storeMergedSnapshot(db, payload.docId, snapshot);\n break;\n }\n case \"doc-update\": {\n const update = payload.update.slice();\n await this.appendDocUpdate(db, payload.docId, update);\n break;\n }\n case \"asset\": {\n const bytes = payload.data.slice();\n await this.putBinary(db, this.assetStore, payload.assetId, bytes);\n break;\n }\n case \"meta\": {\n const bytes = payload.update.slice();\n await this.appendMetaUpdate(db, bytes);\n break;\n }\n default:\n throw new Error(\"Unsupported storage payload type\");\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n const db = await this.ensureDb();\n await this.deleteKey(db, this.assetStore, assetId);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n const db = await this.ensureDb();\n const snapshot = await this.getBinaryFromDb(db, this.docStore, docId);\n const pendingUpdates = await this.getDocUpdates(db, docId);\n\n if (!snapshot && pendingUpdates.length === 0) {\n return undefined;\n }\n\n let doc: LoroDoc;\n try {\n doc = snapshot ? LoroDoc.fromSnapshot(snapshot) : new LoroDoc();\n } catch (error) {\n throw this.createError(\n `Failed to hydrate document snapshot for \"${docId}\"`,\n error,\n );\n }\n\n let appliedUpdates = false;\n for (const update of pendingUpdates) {\n try {\n doc.import(update);\n appliedUpdates = true;\n } catch (error) {\n throw this.createError(\n `Failed to apply queued document update for \"${docId}\"`,\n error,\n );\n }\n }\n\n if (appliedUpdates) {\n let consolidated: Uint8Array;\n try {\n consolidated = doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(\n `Failed to export consolidated snapshot for \"${docId}\"`,\n error,\n );\n }\n await this.writeSnapshot(db, docId, consolidated);\n await this.clearDocUpdates(db, docId);\n }\n\n return doc;\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n const db = await this.ensureDb();\n const legacy = await this.getBinaryFromDb(db, this.metaStore, this.metaKey);\n const queuedUpdates = await this.getMetaUpdates(db);\n if (!legacy && queuedUpdates.length === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n const json = textDecoder.decode(legacy);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n for (const bytes of queuedUpdates) {\n const json = textDecoder.decode(bytes);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n } catch (error) {\n throw this.createError(\"Failed to hydrate metadata snapshot\", error);\n }\n\n if (legacy || queuedUpdates.length > 1) {\n const snapshot = flock.exportJson();\n const encoded = textEncoder.encode(JSON.stringify(snapshot));\n await this.writeMetaSnapshot(db, encoded);\n } else if (queuedUpdates.length === 1) {\n await this.writeMetaSnapshot(db, queuedUpdates[0]);\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n const bytes = await this.getBinary(this.assetStore, assetId);\n return bytes ?? undefined;\n }\n\n async close(): Promise<void> {\n this.closed = true;\n const db = await this.dbPromise;\n if (db) {\n db.close();\n }\n this.dbPromise = undefined;\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.closed) {\n throw new Error(\"IndexedDBStorageAdaptor has been closed\");\n }\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const request = this.idb.open(this.dbName, this.version);\n request.addEventListener(\"upgradeneeded\", () => {\n const db = request.result;\n this.ensureStore(db, this.docStore);\n this.ensureStore(db, this.docUpdateStore);\n this.ensureStore(db, this.metaStore);\n this.ensureStore(db, this.metaUpdateStore);\n this.ensureStore(db, this.assetStore);\n });\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () => {\n reject(\n this.createError(\n `Failed to open IndexedDB database \"${this.dbName}\"`,\n request.error,\n ),\n );\n },\n { once: true },\n );\n });\n }\n return this.dbPromise;\n }\n\n private ensureStore(db: IDBDatabase, storeName: string): void {\n const names = db.objectStoreNames;\n if (this.storeExists(names, storeName)) return;\n db.createObjectStore(storeName);\n }\n\n private storeExists(names: ObjectStoreNames, storeName: string): boolean {\n if (typeof names.contains === \"function\") {\n return names.contains(storeName);\n }\n const length = names.length ?? 0;\n for (let index = 0; index < length; index += 1) {\n const value = names.item?.(index);\n if (value === storeName) return true;\n }\n return false;\n }\n\n private async storeMergedSnapshot(\n db: IDBDatabase,\n docId: string,\n incoming: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, this.docStore, \"readwrite\", async (store) => {\n const existingRaw = await this.wrapRequest(store.get(docId), \"read\");\n const existing = await this.normalizeBinary(existingRaw);\n const merged = this.mergeSnapshots(docId, existing, incoming);\n await this.wrapRequest(store.put(merged, docId), \"write\");\n });\n }\n\n private mergeSnapshots(\n docId: string,\n existing: Uint8Array | undefined,\n incoming: Uint8Array,\n ): Uint8Array {\n try {\n const doc = existing ? LoroDoc.fromSnapshot(existing) : new LoroDoc();\n doc.import(incoming);\n return doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(`Failed to merge snapshot for \"${docId}\"`, error);\n }\n }\n\n private async appendDocUpdate(\n db: IDBDatabase,\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(docId), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, docId), \"write\");\n },\n );\n }\n\n private async getDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(docId), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async appendMetaUpdate(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(\"queue\"), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, \"queue\"), \"write\");\n },\n );\n }\n\n private async getMetaUpdates(db: IDBDatabase): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(\"queue\"), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async writeMetaSnapshot(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n const bytes = update.slice();\n await this.runInTransaction(db, this.metaUpdateStore, \"readwrite\", async (store) => {\n await this.wrapRequest(store.put({ updates: [bytes] }, \"queue\"), \"write\");\n });\n await this.runInTransaction(db, this.metaStore, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(this.metaKey), \"delete\"),\n );\n }\n\n private async clearDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n (store) => this.wrapRequest(store.delete(docId), \"delete\"),\n );\n }\n\n private async writeSnapshot(\n db: IDBDatabase,\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n await this.putBinary(db, this.docStore, docId, snapshot.slice());\n }\n\n private async getBinaryFromDb(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const value = await this.runInTransaction(\n db,\n storeName,\n \"readonly\",\n (store) => this.wrapRequest(store.get(key), \"read\"),\n );\n return this.normalizeBinary(value);\n }\n\n private async normalizeUpdateQueue(value: unknown): Promise<Uint8Array[]> {\n if (value == null) return [];\n const list = Array.isArray(value)\n ? value\n : typeof value === \"object\" && value !== null\n ? (value as { updates?: unknown }).updates\n : undefined;\n\n if (!Array.isArray(list)) return [];\n\n const queue: Uint8Array[] = [];\n for (const entry of list) {\n const bytes = await this.normalizeBinary(entry);\n if (bytes) {\n queue.push(bytes);\n }\n }\n return queue;\n }\n\n private async putBinary(\n db: IDBDatabase,\n storeName: string,\n key: string,\n value: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.put(value, key), \"write\"),\n );\n }\n\n private async deleteKey(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(key), \"delete\"),\n );\n }\n\n private async getBinary(\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const db = await this.ensureDb();\n return this.getBinaryFromDb(db, storeName, key);\n }\n\n private runInTransaction<T>(\n db: IDBDatabase,\n storeName: string,\n mode: IDBTransactionMode,\n executor: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> {\n const tx = db.transaction(storeName, mode);\n const store = tx.objectStore(storeName);\n const completion = new Promise<void>((resolve, reject) => {\n tx.addEventListener(\n \"complete\",\n () => resolve(),\n { once: true },\n );\n tx.addEventListener(\n \"abort\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction aborted\", tx.error),\n ),\n { once: true },\n );\n tx.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction failed\", tx.error),\n ),\n { once: true },\n );\n });\n return Promise.all([executor(store), completion]).then(([result]) => result);\n }\n\n private wrapRequest<T>(\n request: IDBRequest<T>,\n action: string,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\n `IndexedDB request failed during ${action}`,\n request.error,\n ),\n ),\n { once: true },\n );\n });\n }\n\n private async normalizeBinary(value: unknown): Promise<Uint8Array | undefined> {\n if (value == null) return undefined;\n if (value instanceof Uint8Array) {\n return value.slice();\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(\n value.buffer,\n value.byteOffset,\n value.byteLength,\n ).slice();\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value.slice(0));\n }\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"arrayBuffer\" in value\n ) {\n const candidate = value as {\n arrayBuffer?: unknown;\n };\n if (typeof candidate.arrayBuffer === \"function\") {\n const buffer = await candidate.arrayBuffer();\n return new Uint8Array(buffer);\n }\n }\n return undefined;\n }\n\n private createError(message: string, cause: unknown): Error {\n if (cause instanceof Error) {\n return new Error(`${message}: ${cause.message}`, { cause });\n }\n if (cause !== undefined && cause !== null) {\n return new Error(`${message}: ${describeUnknown(cause)}`);\n }\n return new Error(message);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"indexeddb.cjs","names":["doc: LoroDoc","LoroDoc","consolidated: Uint8Array","Flock","queue: Uint8Array[]"],"sources":["../../src/storage/indexeddb.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type {\n AssetId,\n StorageAdapter,\n StorageSavePayload,\n} from \"../types\";\nimport type { ExportBundle } from \"@loro-dev/flock\";\n\nconst DEFAULT_DB_NAME = \"loro-repo\";\nconst DEFAULT_DB_VERSION = 2;\nconst DEFAULT_DOC_STORE = \"docs\";\nconst DEFAULT_META_STORE = \"meta\";\nconst DEFAULT_ASSET_STORE = \"assets\";\nconst DEFAULT_DOC_UPDATE_STORE = \"doc-updates\";\nconst DEFAULT_META_UPDATE_STORE = \"meta-updates\";\nconst DEFAULT_META_KEY = \"snapshot\";\n\ntype EventListenerOptions = {\n once?: boolean;\n};\n\ntype IDBFactory = {\n open(name: string, version?: number): IDBOpenDBRequest;\n};\n\ntype IDBOpenDBRequest = {\n result: IDBDatabase;\n error: unknown;\n onupgradeneeded: ((event: unknown) => void) | null;\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype ObjectStoreNames = {\n contains?(name: string): boolean;\n length?: number;\n item?(index: number): string | null;\n};\n\ntype IDBDatabase = {\n close(): void;\n createObjectStore(name: string): IDBObjectStore;\n transaction(\n storeName: string,\n mode?: IDBTransactionMode,\n ): IDBTransaction;\n objectStoreNames: ObjectStoreNames;\n};\n\ntype IDBTransactionMode = \"readonly\" | \"readwrite\";\n\ntype IDBTransaction = {\n objectStore(name: string): IDBObjectStore;\n oncomplete: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onabort: ((event: unknown) => void) | null;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype IDBObjectStore = {\n put(value: unknown, key?: unknown): IDBRequest<unknown>;\n get(key: unknown): IDBRequest<unknown>;\n delete(key: unknown): IDBRequest<unknown>;\n};\n\ntype IDBRequest<T> = {\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n result: T;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n\nfunction describeUnknown(cause: unknown): string {\n if (typeof cause === \"string\") return cause;\n if (typeof cause === \"number\" || typeof cause === \"boolean\") {\n return String(cause);\n }\n if (typeof cause === \"bigint\") {\n return cause.toString();\n }\n if (typeof cause === \"symbol\") {\n return cause.description ?? cause.toString();\n }\n if (typeof cause === \"function\") {\n return `[function ${cause.name ?? \"anonymous\"}]`;\n }\n if (cause && typeof cause === \"object\") {\n try {\n return JSON.stringify(cause);\n } catch {\n return \"[object]\";\n }\n }\n return String(cause);\n}\n\nexport interface IndexedDBStorageAdaptorOptions {\n readonly dbName?: string;\n readonly version?: number;\n readonly docStoreName?: string;\n readonly docUpdateStoreName?: string;\n readonly metaStoreName?: string;\n readonly metaUpdateStoreName?: string;\n readonly assetStoreName?: string;\n readonly metaKey?: string;\n}\n\nexport class IndexedDBStorageAdaptor implements StorageAdapter {\n private readonly idb: IDBFactory;\n private readonly dbName: string;\n private readonly version: number;\n private readonly docStore: string;\n private readonly docUpdateStore: string;\n private readonly metaStore: string;\n private readonly metaUpdateStore: string;\n private readonly assetStore: string;\n private readonly metaKey: string;\n private dbPromise?: Promise<IDBDatabase>;\n private closed = false;\n\n constructor(options: IndexedDBStorageAdaptorOptions = {}) {\n const idbFactory = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idbFactory) {\n throw new Error(\"IndexedDB is not available in this environment\");\n }\n this.idb = idbFactory;\n this.dbName = options.dbName ?? DEFAULT_DB_NAME;\n this.version = options.version ?? DEFAULT_DB_VERSION;\n this.docStore = options.docStoreName ?? DEFAULT_DOC_STORE;\n this.docUpdateStore = options.docUpdateStoreName ?? DEFAULT_DOC_UPDATE_STORE;\n this.metaStore = options.metaStoreName ?? DEFAULT_META_STORE;\n this.metaUpdateStore = options.metaUpdateStoreName ?? DEFAULT_META_UPDATE_STORE;\n this.assetStore = options.assetStoreName ?? DEFAULT_ASSET_STORE;\n this.metaKey = options.metaKey ?? DEFAULT_META_KEY;\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n const db = await this.ensureDb();\n switch (payload.type) {\n case \"doc-snapshot\": {\n const snapshot = payload.snapshot.slice();\n await this.storeMergedSnapshot(db, payload.docId, snapshot);\n break;\n }\n case \"doc-update\": {\n const update = payload.update.slice();\n await this.appendDocUpdate(db, payload.docId, update);\n break;\n }\n case \"asset\": {\n const bytes = payload.data.slice();\n await this.putBinary(db, this.assetStore, payload.assetId, bytes);\n break;\n }\n case \"meta\": {\n const bytes = payload.update.slice();\n await this.appendMetaUpdate(db, bytes);\n break;\n }\n default:\n throw new Error(\"Unsupported storage payload type\");\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n const db = await this.ensureDb();\n await this.deleteKey(db, this.assetStore, assetId);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n const db = await this.ensureDb();\n const snapshot = await this.getBinaryFromDb(db, this.docStore, docId);\n const pendingUpdates = await this.getDocUpdates(db, docId);\n\n if (!snapshot && pendingUpdates.length === 0) {\n return undefined;\n }\n\n let doc: LoroDoc;\n try {\n doc = snapshot ? LoroDoc.fromSnapshot(snapshot) : new LoroDoc();\n } catch (error) {\n throw this.createError(\n `Failed to hydrate document snapshot for \"${docId}\"`,\n error,\n );\n }\n\n let appliedUpdates = false;\n for (const update of pendingUpdates) {\n try {\n doc.import(update);\n appliedUpdates = true;\n } catch (error) {\n throw this.createError(\n `Failed to apply queued document update for \"${docId}\"`,\n error,\n );\n }\n }\n\n if (appliedUpdates) {\n let consolidated: Uint8Array;\n try {\n consolidated = doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(\n `Failed to export consolidated snapshot for \"${docId}\"`,\n error,\n );\n }\n await this.writeSnapshot(db, docId, consolidated);\n await this.clearDocUpdates(db, docId);\n }\n\n return doc;\n }\n\n async deleteDoc(docId: string): Promise<void> {\n const db = await this.ensureDb();\n await Promise.all([\n this.deleteKey(db, this.docStore, docId),\n this.clearDocUpdates(db, docId),\n ]);\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n const db = await this.ensureDb();\n const legacy = await this.getBinaryFromDb(db, this.metaStore, this.metaKey);\n const queuedUpdates = await this.getMetaUpdates(db);\n if (!legacy && queuedUpdates.length === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n const json = textDecoder.decode(legacy);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n for (const bytes of queuedUpdates) {\n const json = textDecoder.decode(bytes);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n } catch (error) {\n throw this.createError(\"Failed to hydrate metadata snapshot\", error);\n }\n\n if (legacy || queuedUpdates.length > 1) {\n const snapshot = flock.exportJson();\n const encoded = textEncoder.encode(JSON.stringify(snapshot));\n await this.writeMetaSnapshot(db, encoded);\n } else if (queuedUpdates.length === 1) {\n await this.writeMetaSnapshot(db, queuedUpdates[0]);\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n const bytes = await this.getBinary(this.assetStore, assetId);\n return bytes ?? undefined;\n }\n\n async close(): Promise<void> {\n this.closed = true;\n const db = await this.dbPromise;\n if (db) {\n db.close();\n }\n this.dbPromise = undefined;\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.closed) {\n throw new Error(\"IndexedDBStorageAdaptor has been closed\");\n }\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const request = this.idb.open(this.dbName, this.version);\n request.addEventListener(\"upgradeneeded\", () => {\n const db = request.result;\n this.ensureStore(db, this.docStore);\n this.ensureStore(db, this.docUpdateStore);\n this.ensureStore(db, this.metaStore);\n this.ensureStore(db, this.metaUpdateStore);\n this.ensureStore(db, this.assetStore);\n });\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () => {\n reject(\n this.createError(\n `Failed to open IndexedDB database \"${this.dbName}\"`,\n request.error,\n ),\n );\n },\n { once: true },\n );\n });\n }\n return this.dbPromise;\n }\n\n private ensureStore(db: IDBDatabase, storeName: string): void {\n const names = db.objectStoreNames;\n if (this.storeExists(names, storeName)) return;\n db.createObjectStore(storeName);\n }\n\n private storeExists(names: ObjectStoreNames, storeName: string): boolean {\n if (typeof names.contains === \"function\") {\n return names.contains(storeName);\n }\n const length = names.length ?? 0;\n for (let index = 0; index < length; index += 1) {\n const value = names.item?.(index);\n if (value === storeName) return true;\n }\n return false;\n }\n\n private async storeMergedSnapshot(\n db: IDBDatabase,\n docId: string,\n incoming: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, this.docStore, \"readwrite\", async (store) => {\n const existingRaw = await this.wrapRequest(store.get(docId), \"read\");\n const existing = await this.normalizeBinary(existingRaw);\n const merged = this.mergeSnapshots(docId, existing, incoming);\n await this.wrapRequest(store.put(merged, docId), \"write\");\n });\n }\n\n private mergeSnapshots(\n docId: string,\n existing: Uint8Array | undefined,\n incoming: Uint8Array,\n ): Uint8Array {\n try {\n const doc = existing ? LoroDoc.fromSnapshot(existing) : new LoroDoc();\n doc.import(incoming);\n return doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(`Failed to merge snapshot for \"${docId}\"`, error);\n }\n }\n\n private async appendDocUpdate(\n db: IDBDatabase,\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(docId), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, docId), \"write\");\n },\n );\n }\n\n private async getDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(docId), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async appendMetaUpdate(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(\"queue\"), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, \"queue\"), \"write\");\n },\n );\n }\n\n private async getMetaUpdates(db: IDBDatabase): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(\"queue\"), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async writeMetaSnapshot(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n const bytes = update.slice();\n await this.runInTransaction(db, this.metaUpdateStore, \"readwrite\", async (store) => {\n await this.wrapRequest(store.put({ updates: [bytes] }, \"queue\"), \"write\");\n });\n await this.runInTransaction(db, this.metaStore, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(this.metaKey), \"delete\"),\n );\n }\n\n private async clearDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n (store) => this.wrapRequest(store.delete(docId), \"delete\"),\n );\n }\n\n private async writeSnapshot(\n db: IDBDatabase,\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n await this.putBinary(db, this.docStore, docId, snapshot.slice());\n }\n\n private async getBinaryFromDb(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const value = await this.runInTransaction(\n db,\n storeName,\n \"readonly\",\n (store) => this.wrapRequest(store.get(key), \"read\"),\n );\n return this.normalizeBinary(value);\n }\n\n private async normalizeUpdateQueue(value: unknown): Promise<Uint8Array[]> {\n if (value == null) return [];\n const list = Array.isArray(value)\n ? value\n : typeof value === \"object\" && value !== null\n ? (value as { updates?: unknown }).updates\n : undefined;\n\n if (!Array.isArray(list)) return [];\n\n const queue: Uint8Array[] = [];\n for (const entry of list) {\n const bytes = await this.normalizeBinary(entry);\n if (bytes) {\n queue.push(bytes);\n }\n }\n return queue;\n }\n\n private async putBinary(\n db: IDBDatabase,\n storeName: string,\n key: string,\n value: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.put(value, key), \"write\"),\n );\n }\n\n private async deleteKey(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(key), \"delete\"),\n );\n }\n\n private async getBinary(\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const db = await this.ensureDb();\n return this.getBinaryFromDb(db, storeName, key);\n }\n\n private runInTransaction<T>(\n db: IDBDatabase,\n storeName: string,\n mode: IDBTransactionMode,\n executor: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> {\n const tx = db.transaction(storeName, mode);\n const store = tx.objectStore(storeName);\n const completion = new Promise<void>((resolve, reject) => {\n tx.addEventListener(\n \"complete\",\n () => resolve(),\n { once: true },\n );\n tx.addEventListener(\n \"abort\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction aborted\", tx.error),\n ),\n { once: true },\n );\n tx.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction failed\", tx.error),\n ),\n { once: true },\n );\n });\n return Promise.all([executor(store), completion]).then(([result]) => result);\n }\n\n private wrapRequest<T>(\n request: IDBRequest<T>,\n action: string,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\n `IndexedDB request failed during ${action}`,\n request.error,\n ),\n ),\n { once: true },\n );\n });\n }\n\n private async normalizeBinary(value: unknown): Promise<Uint8Array | undefined> {\n if (value == null) return undefined;\n if (value instanceof Uint8Array) {\n return value.slice();\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(\n value.buffer,\n value.byteOffset,\n value.byteLength,\n ).slice();\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value.slice(0));\n }\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"arrayBuffer\" in value\n ) {\n const candidate = value as {\n arrayBuffer?: unknown;\n };\n if (typeof candidate.arrayBuffer === \"function\") {\n const buffer = await candidate.arrayBuffer();\n return new Uint8Array(buffer);\n }\n }\n return undefined;\n }\n\n private createError(message: string, cause: unknown): Error {\n if (cause instanceof Error) {\n return new Error(`${message}: ${cause.message}`, { cause });\n }\n if (cause !== undefined && cause !== null) {\n return new Error(`${message}: ${describeUnknown(cause)}`);\n }\n return new Error(message);\n }\n}\n"],"mappings":";;;;;;;AAUA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,2BAA2B;AACjC,MAAM,4BAA4B;AAClC,MAAM,mBAAmB;AAwEzB,MAAM,cAAc,IAAI,aAAa;AACrC,MAAM,cAAc,IAAI,aAAa;AAErC,SAAS,gBAAgB,OAAwB;AAC/C,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,SACnB,QAAO,MAAM,UAAU;AAEzB,KAAI,OAAO,UAAU,SACnB,QAAO,MAAM,eAAe,MAAM,UAAU;AAE9C,KAAI,OAAO,UAAU,WACnB,QAAO,aAAa,MAAM,QAAQ,YAAY;AAEhD,KAAI,SAAS,OAAO,UAAU,SAC5B,KAAI;AACF,SAAO,KAAK,UAAU,MAAM;SACtB;AACN,SAAO;;AAGX,QAAO,OAAO,MAAM;;AActB,IAAa,0BAAb,MAA+D;CAC7D,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ;CACR,AAAQ,SAAS;CAEjB,YAAY,UAA0C,EAAE,EAAE;EACxD,MAAM,aAAc,WAA0C;AAC9D,MAAI,CAAC,WACH,OAAM,IAAI,MAAM,iDAAiD;AAEnE,OAAK,MAAM;AACX,OAAK,SAAS,QAAQ,UAAU;AAChC,OAAK,UAAU,QAAQ,WAAW;AAClC,OAAK,WAAW,QAAQ,gBAAgB;AACxC,OAAK,iBAAiB,QAAQ,sBAAsB;AACpD,OAAK,YAAY,QAAQ,iBAAiB;AAC1C,OAAK,kBAAkB,QAAQ,uBAAuB;AACtD,OAAK,aAAa,QAAQ,kBAAkB;AAC5C,OAAK,UAAU,QAAQ,WAAW;;CAGpC,MAAM,KAAK,SAA4C;EACrD,MAAM,KAAK,MAAM,KAAK,UAAU;AAChC,UAAQ,QAAQ,MAAhB;GACE,KAAK,gBAAgB;IACnB,MAAM,WAAW,QAAQ,SAAS,OAAO;AACzC,UAAM,KAAK,oBAAoB,IAAI,QAAQ,OAAO,SAAS;AAC3D;;GAEF,KAAK,cAAc;IACjB,MAAM,SAAS,QAAQ,OAAO,OAAO;AACrC,UAAM,KAAK,gBAAgB,IAAI,QAAQ,OAAO,OAAO;AACrD;;GAEF,KAAK,SAAS;IACZ,MAAM,QAAQ,QAAQ,KAAK,OAAO;AAClC,UAAM,KAAK,UAAU,IAAI,KAAK,YAAY,QAAQ,SAAS,MAAM;AACjE;;GAEF,KAAK,QAAQ;IACX,MAAM,QAAQ,QAAQ,OAAO,OAAO;AACpC,UAAM,KAAK,iBAAiB,IAAI,MAAM;AACtC;;GAEF,QACE,OAAM,IAAI,MAAM,mCAAmC;;;CAIzD,MAAM,YAAY,SAAiC;EACjD,MAAM,KAAK,MAAM,KAAK,UAAU;AAChC,QAAM,KAAK,UAAU,IAAI,KAAK,YAAY,QAAQ;;CAGpD,MAAM,QAAQ,OAA6C;EACzD,MAAM,KAAK,MAAM,KAAK,UAAU;EAChC,MAAM,WAAW,MAAM,KAAK,gBAAgB,IAAI,KAAK,UAAU,MAAM;EACrE,MAAM,iBAAiB,MAAM,KAAK,cAAc,IAAI,MAAM;AAE1D,MAAI,CAAC,YAAY,eAAe,WAAW,EACzC;EAGF,IAAIA;AACJ,MAAI;AACF,SAAM,WAAWC,kBAAQ,aAAa,SAAS,GAAG,IAAIA,mBAAS;WACxD,OAAO;AACd,SAAM,KAAK,YACT,4CAA4C,MAAM,IAClD,MACD;;EAGH,IAAI,iBAAiB;AACrB,OAAK,MAAM,UAAU,eACnB,KAAI;AACF,OAAI,OAAO,OAAO;AAClB,oBAAiB;WACV,OAAO;AACd,SAAM,KAAK,YACT,+CAA+C,MAAM,IACrD,MACD;;AAIL,MAAI,gBAAgB;GAClB,IAAIC;AACJ,OAAI;AACF,mBAAe,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;YACxC,OAAO;AACd,UAAM,KAAK,YACT,+CAA+C,MAAM,IACrD,MACD;;AAEH,SAAM,KAAK,cAAc,IAAI,OAAO,aAAa;AACjD,SAAM,KAAK,gBAAgB,IAAI,MAAM;;AAGvC,SAAO;;CAGT,MAAM,UAAU,OAA8B;EAC5C,MAAM,KAAK,MAAM,KAAK,UAAU;AAChC,QAAM,QAAQ,IAAI,CAChB,KAAK,UAAU,IAAI,KAAK,UAAU,MAAM,EACxC,KAAK,gBAAgB,IAAI,MAAM,CAChC,CAAC;;CAGJ,MAAM,WAAuC;EAC3C,MAAM,KAAK,MAAM,KAAK,UAAU;EAChC,MAAM,SAAS,MAAM,KAAK,gBAAgB,IAAI,KAAK,WAAW,KAAK,QAAQ;EAC3E,MAAM,gBAAgB,MAAM,KAAK,eAAe,GAAG;AACnD,MAAI,CAAC,UAAU,cAAc,WAAW,EAAG,QAAO;EAElD,MAAM,QAAQ,IAAIC,wBAAO;AACzB,MAAI;AACF,OAAI,QAAQ;IACV,MAAM,OAAO,YAAY,OAAO,OAAO;IACvC,MAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,UAAM,WAAW,OAAO;;AAE1B,QAAK,MAAM,SAAS,eAAe;IACjC,MAAM,OAAO,YAAY,OAAO,MAAM;IACtC,MAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,UAAM,WAAW,OAAO;;WAEnB,OAAO;AACd,SAAM,KAAK,YAAY,uCAAuC,MAAM;;AAGtE,MAAI,UAAU,cAAc,SAAS,GAAG;GACtC,MAAM,WAAW,MAAM,YAAY;GACnC,MAAM,UAAU,YAAY,OAAO,KAAK,UAAU,SAAS,CAAC;AAC5D,SAAM,KAAK,kBAAkB,IAAI,QAAQ;aAChC,cAAc,WAAW,EAClC,OAAM,KAAK,kBAAkB,IAAI,cAAc,GAAG;AAGpD,SAAO;;CAGT,MAAM,UAAU,SAAmD;AAEjE,SADc,MAAM,KAAK,UAAU,KAAK,YAAY,QAAQ,IAC5C;;CAGlB,MAAM,QAAuB;AAC3B,OAAK,SAAS;EACd,MAAM,KAAK,MAAM,KAAK;AACtB,MAAI,GACF,IAAG,OAAO;AAEZ,OAAK,YAAY;;CAGnB,MAAc,WAAiC;AAC7C,MAAI,KAAK,OACP,OAAM,IAAI,MAAM,0CAA0C;AAE5D,MAAI,CAAC,KAAK,UACR,MAAK,YAAY,IAAI,SAAS,SAAS,WAAW;GAChD,MAAM,UAAU,KAAK,IAAI,KAAK,KAAK,QAAQ,KAAK,QAAQ;AACxD,WAAQ,iBAAiB,uBAAuB;IAC9C,MAAM,KAAK,QAAQ;AACnB,SAAK,YAAY,IAAI,KAAK,SAAS;AACnC,SAAK,YAAY,IAAI,KAAK,eAAe;AACzC,SAAK,YAAY,IAAI,KAAK,UAAU;AACpC,SAAK,YAAY,IAAI,KAAK,gBAAgB;AAC1C,SAAK,YAAY,IAAI,KAAK,WAAW;KACrC;AACF,WAAQ,iBACN,iBACM,QAAQ,QAAQ,OAAO,EAC7B,EAAE,MAAM,MAAM,CACf;AACD,WAAQ,iBACN,eACM;AACJ,WACE,KAAK,YACH,sCAAsC,KAAK,OAAO,IAClD,QAAQ,MACT,CACF;MAEH,EAAE,MAAM,MAAM,CACf;IACD;AAEJ,SAAO,KAAK;;CAGd,AAAQ,YAAY,IAAiB,WAAyB;EAC5D,MAAM,QAAQ,GAAG;AACjB,MAAI,KAAK,YAAY,OAAO,UAAU,CAAE;AACxC,KAAG,kBAAkB,UAAU;;CAGjC,AAAQ,YAAY,OAAyB,WAA4B;AACvE,MAAI,OAAO,MAAM,aAAa,WAC5B,QAAO,MAAM,SAAS,UAAU;EAElC,MAAM,SAAS,MAAM,UAAU;AAC/B,OAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS,EAE3C,KADc,MAAM,OAAO,MAAM,KACnB,UAAW,QAAO;AAElC,SAAO;;CAGT,MAAc,oBACZ,IACA,OACA,UACe;AACf,QAAM,KAAK,iBAAiB,IAAI,KAAK,UAAU,aAAa,OAAO,UAAU;GAC3E,MAAM,cAAc,MAAM,KAAK,YAAY,MAAM,IAAI,MAAM,EAAE,OAAO;GACpE,MAAM,WAAW,MAAM,KAAK,gBAAgB,YAAY;GACxD,MAAM,SAAS,KAAK,eAAe,OAAO,UAAU,SAAS;AAC7D,SAAM,KAAK,YAAY,MAAM,IAAI,QAAQ,MAAM,EAAE,QAAQ;IACzD;;CAGJ,AAAQ,eACN,OACA,UACA,UACY;AACZ,MAAI;GACF,MAAM,MAAM,WAAWF,kBAAQ,aAAa,SAAS,GAAG,IAAIA,mBAAS;AACrE,OAAI,OAAO,SAAS;AACpB,UAAO,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;WAChC,OAAO;AACd,SAAM,KAAK,YAAY,iCAAiC,MAAM,IAAI,MAAM;;;CAI5E,MAAc,gBACZ,IACA,OACA,QACe;AACf,QAAM,KAAK,iBACT,IACA,KAAK,gBACL,aACA,OAAO,UAAU;GACf,MAAM,MAAM,MAAM,KAAK,YAAY,MAAM,IAAI,MAAM,EAAE,OAAO;GAC5D,MAAM,QAAQ,MAAM,KAAK,qBAAqB,IAAI;AAClD,SAAM,KAAK,OAAO,OAAO,CAAC;AAC1B,SAAM,KAAK,YAAY,MAAM,IAAI,EAAE,SAAS,OAAO,EAAE,MAAM,EAAE,QAAQ;IAExE;;CAGH,MAAc,cACZ,IACA,OACuB;EACvB,MAAM,MAAM,MAAM,KAAK,iBACrB,IACA,KAAK,gBACL,aACC,UAAU,KAAK,YAAY,MAAM,IAAI,MAAM,EAAE,OAAO,CACtD;AACD,SAAO,KAAK,qBAAqB,IAAI;;CAGvC,MAAc,iBACZ,IACA,QACe;AACf,QAAM,KAAK,iBACT,IACA,KAAK,iBACL,aACA,OAAO,UAAU;GACf,MAAM,MAAM,MAAM,KAAK,YAAY,MAAM,IAAI,QAAQ,EAAE,OAAO;GAC9D,MAAM,QAAQ,MAAM,KAAK,qBAAqB,IAAI;AAClD,SAAM,KAAK,OAAO,OAAO,CAAC;AAC1B,SAAM,KAAK,YAAY,MAAM,IAAI,EAAE,SAAS,OAAO,EAAE,QAAQ,EAAE,QAAQ;IAE1E;;CAGH,MAAc,eAAe,IAAwC;EACnE,MAAM,MAAM,MAAM,KAAK,iBACrB,IACA,KAAK,iBACL,aACC,UAAU,KAAK,YAAY,MAAM,IAAI,QAAQ,EAAE,OAAO,CACxD;AACD,SAAO,KAAK,qBAAqB,IAAI;;CAGvC,MAAc,kBACZ,IACA,QACe;EACf,MAAM,QAAQ,OAAO,OAAO;AAC5B,QAAM,KAAK,iBAAiB,IAAI,KAAK,iBAAiB,aAAa,OAAO,UAAU;AAClF,SAAM,KAAK,YAAY,MAAM,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ;IACzE;AACF,QAAM,KAAK,iBAAiB,IAAI,KAAK,WAAW,cAAc,UAC5D,KAAK,YAAY,MAAM,OAAO,KAAK,QAAQ,EAAE,SAAS,CACvD;;CAGH,MAAc,gBACZ,IACA,OACe;AACf,QAAM,KAAK,iBACT,IACA,KAAK,gBACL,cACC,UAAU,KAAK,YAAY,MAAM,OAAO,MAAM,EAAE,SAAS,CAC3D;;CAGH,MAAc,cACZ,IACA,OACA,UACe;AACf,QAAM,KAAK,UAAU,IAAI,KAAK,UAAU,OAAO,SAAS,OAAO,CAAC;;CAGlE,MAAc,gBACZ,IACA,WACA,KACiC;EACjC,MAAM,QAAQ,MAAM,KAAK,iBACvB,IACA,WACA,aACC,UAAU,KAAK,YAAY,MAAM,IAAI,IAAI,EAAE,OAAO,CACpD;AACD,SAAO,KAAK,gBAAgB,MAAM;;CAGpC,MAAc,qBAAqB,OAAuC;AACxE,MAAI,SAAS,KAAM,QAAO,EAAE;EAC5B,MAAM,OAAO,MAAM,QAAQ,MAAM,GAC7B,QACA,OAAO,UAAU,YAAY,UAAU,OACpC,MAAgC,UACjC;AAEN,MAAI,CAAC,MAAM,QAAQ,KAAK,CAAE,QAAO,EAAE;EAEnC,MAAMG,QAAsB,EAAE;AAC9B,OAAK,MAAM,SAAS,MAAM;GACxB,MAAM,QAAQ,MAAM,KAAK,gBAAgB,MAAM;AAC/C,OAAI,MACF,OAAM,KAAK,MAAM;;AAGrB,SAAO;;CAGT,MAAc,UACZ,IACA,WACA,KACA,OACe;AACf,QAAM,KAAK,iBAAiB,IAAI,WAAW,cAAc,UACvD,KAAK,YAAY,MAAM,IAAI,OAAO,IAAI,EAAE,QAAQ,CACjD;;CAGH,MAAc,UACZ,IACA,WACA,KACe;AACf,QAAM,KAAK,iBAAiB,IAAI,WAAW,cAAc,UACvD,KAAK,YAAY,MAAM,OAAO,IAAI,EAAE,SAAS,CAC9C;;CAGH,MAAc,UACZ,WACA,KACiC;EACjC,MAAM,KAAK,MAAM,KAAK,UAAU;AAChC,SAAO,KAAK,gBAAgB,IAAI,WAAW,IAAI;;CAGjD,AAAQ,iBACN,IACA,WACA,MACA,UACY;EACZ,MAAM,KAAK,GAAG,YAAY,WAAW,KAAK;EAC1C,MAAM,QAAQ,GAAG,YAAY,UAAU;EACvC,MAAM,aAAa,IAAI,SAAe,SAAS,WAAW;AACxD,MAAG,iBACD,kBACM,SAAS,EACf,EAAE,MAAM,MAAM,CACf;AACD,MAAG,iBACD,eAEE,OACE,KAAK,YAAY,iCAAiC,GAAG,MAAM,CAC5D,EACH,EAAE,MAAM,MAAM,CACf;AACD,MAAG,iBACD,eAEE,OACE,KAAK,YAAY,gCAAgC,GAAG,MAAM,CAC3D,EACH,EAAE,MAAM,MAAM,CACf;IACD;AACF,SAAO,QAAQ,IAAI,CAAC,SAAS,MAAM,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,YAAY,OAAO;;CAG9E,AAAQ,YACN,SACA,QACY;AACZ,SAAO,IAAI,SAAY,SAAS,WAAW;AACzC,WAAQ,iBACN,iBACM,QAAQ,QAAQ,OAAO,EAC7B,EAAE,MAAM,MAAM,CACf;AACD,WAAQ,iBACN,eAEE,OACE,KAAK,YACH,mCAAmC,UACnC,QAAQ,MACT,CACF,EACH,EAAE,MAAM,MAAM,CACf;IACD;;CAGJ,MAAc,gBAAgB,OAAiD;AAC7E,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,iBAAiB,WACnB,QAAO,MAAM,OAAO;AAEtB,MAAI,YAAY,OAAO,MAAM,CAC3B,QAAO,IAAI,WACT,MAAM,QACN,MAAM,YACN,MAAM,WACP,CAAC,OAAO;AAEX,MAAI,iBAAiB,YACnB,QAAO,IAAI,WAAW,MAAM,MAAM,EAAE,CAAC;AAEvC,MACE,OAAO,UAAU,YACjB,UAAU,QACV,iBAAiB,OACjB;GACA,MAAM,YAAY;AAGlB,OAAI,OAAO,UAAU,gBAAgB,YAAY;IAC/C,MAAM,SAAS,MAAM,UAAU,aAAa;AAC5C,WAAO,IAAI,WAAW,OAAO;;;;CAMnC,AAAQ,YAAY,SAAiB,OAAuB;AAC1D,MAAI,iBAAiB,MACnB,QAAO,IAAI,MAAM,GAAG,QAAQ,IAAI,MAAM,WAAW,EAAE,OAAO,CAAC;AAE7D,MAAI,UAAU,UAAa,UAAU,KACnC,wBAAO,IAAI,MAAM,GAAG,QAAQ,IAAI,gBAAgB,MAAM,GAAG;AAE3D,SAAO,IAAI,MAAM,QAAQ"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as StorageSavePayload, E as StorageAdapter, r as AssetId } from "../types.cjs";
|
|
2
2
|
import { Flock } from "@loro-dev/flock";
|
|
3
3
|
import { LoroDoc } from "loro-crdt";
|
|
4
4
|
|
|
@@ -29,6 +29,7 @@ declare class IndexedDBStorageAdaptor implements StorageAdapter {
|
|
|
29
29
|
save(payload: StorageSavePayload): Promise<void>;
|
|
30
30
|
deleteAsset(assetId: AssetId): Promise<void>;
|
|
31
31
|
loadDoc(docId: string): Promise<LoroDoc | undefined>;
|
|
32
|
+
deleteDoc(docId: string): Promise<void>;
|
|
32
33
|
loadMeta(): Promise<Flock | undefined>;
|
|
33
34
|
loadAsset(assetId: AssetId): Promise<Uint8Array | undefined>;
|
|
34
35
|
close(): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as StorageSavePayload, E as StorageAdapter, r as AssetId } from "../types.js";
|
|
2
2
|
import { Flock } from "@loro-dev/flock";
|
|
3
3
|
import { LoroDoc } from "loro-crdt";
|
|
4
4
|
|
|
@@ -29,6 +29,7 @@ declare class IndexedDBStorageAdaptor implements StorageAdapter {
|
|
|
29
29
|
save(payload: StorageSavePayload): Promise<void>;
|
|
30
30
|
deleteAsset(assetId: AssetId): Promise<void>;
|
|
31
31
|
loadDoc(docId: string): Promise<LoroDoc | undefined>;
|
|
32
|
+
deleteDoc(docId: string): Promise<void>;
|
|
32
33
|
loadMeta(): Promise<Flock | undefined>;
|
|
33
34
|
loadAsset(assetId: AssetId): Promise<Uint8Array | undefined>;
|
|
34
35
|
close(): Promise<void>;
|
|
@@ -110,6 +110,10 @@ var IndexedDBStorageAdaptor = class {
|
|
|
110
110
|
}
|
|
111
111
|
return doc;
|
|
112
112
|
}
|
|
113
|
+
async deleteDoc(docId) {
|
|
114
|
+
const db = await this.ensureDb();
|
|
115
|
+
await Promise.all([this.deleteKey(db, this.docStore, docId), this.clearDocUpdates(db, docId)]);
|
|
116
|
+
}
|
|
113
117
|
async loadMeta() {
|
|
114
118
|
const db = await this.ensureDb();
|
|
115
119
|
const legacy = await this.getBinaryFromDb(db, this.metaStore, this.metaKey);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb.js","names":["doc: LoroDoc","consolidated: Uint8Array","queue: Uint8Array[]"],"sources":["../../src/storage/indexeddb.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type {\n AssetId,\n StorageAdapter,\n StorageSavePayload,\n} from \"../types\";\nimport type { ExportBundle } from \"@loro-dev/flock\";\n\nconst DEFAULT_DB_NAME = \"loro-repo\";\nconst DEFAULT_DB_VERSION = 2;\nconst DEFAULT_DOC_STORE = \"docs\";\nconst DEFAULT_META_STORE = \"meta\";\nconst DEFAULT_ASSET_STORE = \"assets\";\nconst DEFAULT_DOC_UPDATE_STORE = \"doc-updates\";\nconst DEFAULT_META_UPDATE_STORE = \"meta-updates\";\nconst DEFAULT_META_KEY = \"snapshot\";\n\ntype EventListenerOptions = {\n once?: boolean;\n};\n\ntype IDBFactory = {\n open(name: string, version?: number): IDBOpenDBRequest;\n};\n\ntype IDBOpenDBRequest = {\n result: IDBDatabase;\n error: unknown;\n onupgradeneeded: ((event: unknown) => void) | null;\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype ObjectStoreNames = {\n contains?(name: string): boolean;\n length?: number;\n item?(index: number): string | null;\n};\n\ntype IDBDatabase = {\n close(): void;\n createObjectStore(name: string): IDBObjectStore;\n transaction(\n storeName: string,\n mode?: IDBTransactionMode,\n ): IDBTransaction;\n objectStoreNames: ObjectStoreNames;\n};\n\ntype IDBTransactionMode = \"readonly\" | \"readwrite\";\n\ntype IDBTransaction = {\n objectStore(name: string): IDBObjectStore;\n oncomplete: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onabort: ((event: unknown) => void) | null;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype IDBObjectStore = {\n put(value: unknown, key?: unknown): IDBRequest<unknown>;\n get(key: unknown): IDBRequest<unknown>;\n delete(key: unknown): IDBRequest<unknown>;\n};\n\ntype IDBRequest<T> = {\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n result: T;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n\nfunction describeUnknown(cause: unknown): string {\n if (typeof cause === \"string\") return cause;\n if (typeof cause === \"number\" || typeof cause === \"boolean\") {\n return String(cause);\n }\n if (typeof cause === \"bigint\") {\n return cause.toString();\n }\n if (typeof cause === \"symbol\") {\n return cause.description ?? cause.toString();\n }\n if (typeof cause === \"function\") {\n return `[function ${cause.name ?? \"anonymous\"}]`;\n }\n if (cause && typeof cause === \"object\") {\n try {\n return JSON.stringify(cause);\n } catch {\n return \"[object]\";\n }\n }\n return String(cause);\n}\n\nexport interface IndexedDBStorageAdaptorOptions {\n readonly dbName?: string;\n readonly version?: number;\n readonly docStoreName?: string;\n readonly docUpdateStoreName?: string;\n readonly metaStoreName?: string;\n readonly metaUpdateStoreName?: string;\n readonly assetStoreName?: string;\n readonly metaKey?: string;\n}\n\nexport class IndexedDBStorageAdaptor implements StorageAdapter {\n private readonly idb: IDBFactory;\n private readonly dbName: string;\n private readonly version: number;\n private readonly docStore: string;\n private readonly docUpdateStore: string;\n private readonly metaStore: string;\n private readonly metaUpdateStore: string;\n private readonly assetStore: string;\n private readonly metaKey: string;\n private dbPromise?: Promise<IDBDatabase>;\n private closed = false;\n\n constructor(options: IndexedDBStorageAdaptorOptions = {}) {\n const idbFactory = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idbFactory) {\n throw new Error(\"IndexedDB is not available in this environment\");\n }\n this.idb = idbFactory;\n this.dbName = options.dbName ?? DEFAULT_DB_NAME;\n this.version = options.version ?? DEFAULT_DB_VERSION;\n this.docStore = options.docStoreName ?? DEFAULT_DOC_STORE;\n this.docUpdateStore = options.docUpdateStoreName ?? DEFAULT_DOC_UPDATE_STORE;\n this.metaStore = options.metaStoreName ?? DEFAULT_META_STORE;\n this.metaUpdateStore = options.metaUpdateStoreName ?? DEFAULT_META_UPDATE_STORE;\n this.assetStore = options.assetStoreName ?? DEFAULT_ASSET_STORE;\n this.metaKey = options.metaKey ?? DEFAULT_META_KEY;\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n const db = await this.ensureDb();\n switch (payload.type) {\n case \"doc-snapshot\": {\n const snapshot = payload.snapshot.slice();\n await this.storeMergedSnapshot(db, payload.docId, snapshot);\n break;\n }\n case \"doc-update\": {\n const update = payload.update.slice();\n await this.appendDocUpdate(db, payload.docId, update);\n break;\n }\n case \"asset\": {\n const bytes = payload.data.slice();\n await this.putBinary(db, this.assetStore, payload.assetId, bytes);\n break;\n }\n case \"meta\": {\n const bytes = payload.update.slice();\n await this.appendMetaUpdate(db, bytes);\n break;\n }\n default:\n throw new Error(\"Unsupported storage payload type\");\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n const db = await this.ensureDb();\n await this.deleteKey(db, this.assetStore, assetId);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n const db = await this.ensureDb();\n const snapshot = await this.getBinaryFromDb(db, this.docStore, docId);\n const pendingUpdates = await this.getDocUpdates(db, docId);\n\n if (!snapshot && pendingUpdates.length === 0) {\n return undefined;\n }\n\n let doc: LoroDoc;\n try {\n doc = snapshot ? LoroDoc.fromSnapshot(snapshot) : new LoroDoc();\n } catch (error) {\n throw this.createError(\n `Failed to hydrate document snapshot for \"${docId}\"`,\n error,\n );\n }\n\n let appliedUpdates = false;\n for (const update of pendingUpdates) {\n try {\n doc.import(update);\n appliedUpdates = true;\n } catch (error) {\n throw this.createError(\n `Failed to apply queued document update for \"${docId}\"`,\n error,\n );\n }\n }\n\n if (appliedUpdates) {\n let consolidated: Uint8Array;\n try {\n consolidated = doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(\n `Failed to export consolidated snapshot for \"${docId}\"`,\n error,\n );\n }\n await this.writeSnapshot(db, docId, consolidated);\n await this.clearDocUpdates(db, docId);\n }\n\n return doc;\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n const db = await this.ensureDb();\n const legacy = await this.getBinaryFromDb(db, this.metaStore, this.metaKey);\n const queuedUpdates = await this.getMetaUpdates(db);\n if (!legacy && queuedUpdates.length === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n const json = textDecoder.decode(legacy);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n for (const bytes of queuedUpdates) {\n const json = textDecoder.decode(bytes);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n } catch (error) {\n throw this.createError(\"Failed to hydrate metadata snapshot\", error);\n }\n\n if (legacy || queuedUpdates.length > 1) {\n const snapshot = flock.exportJson();\n const encoded = textEncoder.encode(JSON.stringify(snapshot));\n await this.writeMetaSnapshot(db, encoded);\n } else if (queuedUpdates.length === 1) {\n await this.writeMetaSnapshot(db, queuedUpdates[0]);\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n const bytes = await this.getBinary(this.assetStore, assetId);\n return bytes ?? undefined;\n }\n\n async close(): Promise<void> {\n this.closed = true;\n const db = await this.dbPromise;\n if (db) {\n db.close();\n }\n this.dbPromise = undefined;\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.closed) {\n throw new Error(\"IndexedDBStorageAdaptor has been closed\");\n }\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const request = this.idb.open(this.dbName, this.version);\n request.addEventListener(\"upgradeneeded\", () => {\n const db = request.result;\n this.ensureStore(db, this.docStore);\n this.ensureStore(db, this.docUpdateStore);\n this.ensureStore(db, this.metaStore);\n this.ensureStore(db, this.metaUpdateStore);\n this.ensureStore(db, this.assetStore);\n });\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () => {\n reject(\n this.createError(\n `Failed to open IndexedDB database \"${this.dbName}\"`,\n request.error,\n ),\n );\n },\n { once: true },\n );\n });\n }\n return this.dbPromise;\n }\n\n private ensureStore(db: IDBDatabase, storeName: string): void {\n const names = db.objectStoreNames;\n if (this.storeExists(names, storeName)) return;\n db.createObjectStore(storeName);\n }\n\n private storeExists(names: ObjectStoreNames, storeName: string): boolean {\n if (typeof names.contains === \"function\") {\n return names.contains(storeName);\n }\n const length = names.length ?? 0;\n for (let index = 0; index < length; index += 1) {\n const value = names.item?.(index);\n if (value === storeName) return true;\n }\n return false;\n }\n\n private async storeMergedSnapshot(\n db: IDBDatabase,\n docId: string,\n incoming: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, this.docStore, \"readwrite\", async (store) => {\n const existingRaw = await this.wrapRequest(store.get(docId), \"read\");\n const existing = await this.normalizeBinary(existingRaw);\n const merged = this.mergeSnapshots(docId, existing, incoming);\n await this.wrapRequest(store.put(merged, docId), \"write\");\n });\n }\n\n private mergeSnapshots(\n docId: string,\n existing: Uint8Array | undefined,\n incoming: Uint8Array,\n ): Uint8Array {\n try {\n const doc = existing ? LoroDoc.fromSnapshot(existing) : new LoroDoc();\n doc.import(incoming);\n return doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(`Failed to merge snapshot for \"${docId}\"`, error);\n }\n }\n\n private async appendDocUpdate(\n db: IDBDatabase,\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(docId), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, docId), \"write\");\n },\n );\n }\n\n private async getDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(docId), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async appendMetaUpdate(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(\"queue\"), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, \"queue\"), \"write\");\n },\n );\n }\n\n private async getMetaUpdates(db: IDBDatabase): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(\"queue\"), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async writeMetaSnapshot(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n const bytes = update.slice();\n await this.runInTransaction(db, this.metaUpdateStore, \"readwrite\", async (store) => {\n await this.wrapRequest(store.put({ updates: [bytes] }, \"queue\"), \"write\");\n });\n await this.runInTransaction(db, this.metaStore, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(this.metaKey), \"delete\"),\n );\n }\n\n private async clearDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n (store) => this.wrapRequest(store.delete(docId), \"delete\"),\n );\n }\n\n private async writeSnapshot(\n db: IDBDatabase,\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n await this.putBinary(db, this.docStore, docId, snapshot.slice());\n }\n\n private async getBinaryFromDb(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const value = await this.runInTransaction(\n db,\n storeName,\n \"readonly\",\n (store) => this.wrapRequest(store.get(key), \"read\"),\n );\n return this.normalizeBinary(value);\n }\n\n private async normalizeUpdateQueue(value: unknown): Promise<Uint8Array[]> {\n if (value == null) return [];\n const list = Array.isArray(value)\n ? value\n : typeof value === \"object\" && value !== null\n ? (value as { updates?: unknown }).updates\n : undefined;\n\n if (!Array.isArray(list)) return [];\n\n const queue: Uint8Array[] = [];\n for (const entry of list) {\n const bytes = await this.normalizeBinary(entry);\n if (bytes) {\n queue.push(bytes);\n }\n }\n return queue;\n }\n\n private async putBinary(\n db: IDBDatabase,\n storeName: string,\n key: string,\n value: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.put(value, key), \"write\"),\n );\n }\n\n private async deleteKey(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(key), \"delete\"),\n );\n }\n\n private async getBinary(\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const db = await this.ensureDb();\n return this.getBinaryFromDb(db, storeName, key);\n }\n\n private runInTransaction<T>(\n db: IDBDatabase,\n storeName: string,\n mode: IDBTransactionMode,\n executor: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> {\n const tx = db.transaction(storeName, mode);\n const store = tx.objectStore(storeName);\n const completion = new Promise<void>((resolve, reject) => {\n tx.addEventListener(\n \"complete\",\n () => resolve(),\n { once: true },\n );\n tx.addEventListener(\n \"abort\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction aborted\", tx.error),\n ),\n { once: true },\n );\n tx.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction failed\", tx.error),\n ),\n { once: true },\n );\n });\n return Promise.all([executor(store), completion]).then(([result]) => result);\n }\n\n private wrapRequest<T>(\n request: IDBRequest<T>,\n action: string,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\n `IndexedDB request failed during ${action}`,\n request.error,\n ),\n ),\n { once: true },\n );\n });\n }\n\n private async normalizeBinary(value: unknown): Promise<Uint8Array | undefined> {\n if (value == null) return undefined;\n if (value instanceof Uint8Array) {\n return value.slice();\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(\n value.buffer,\n value.byteOffset,\n value.byteLength,\n ).slice();\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value.slice(0));\n }\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"arrayBuffer\" in value\n ) {\n const candidate = value as {\n arrayBuffer?: unknown;\n };\n if (typeof candidate.arrayBuffer === \"function\") {\n const buffer = await candidate.arrayBuffer();\n return new Uint8Array(buffer);\n }\n }\n return undefined;\n }\n\n private createError(message: string, cause: unknown): Error {\n if (cause instanceof Error) {\n return new Error(`${message}: ${cause.message}`, { cause });\n }\n if (cause !== undefined && cause !== null) {\n return new Error(`${message}: ${describeUnknown(cause)}`);\n }\n return new Error(message);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"indexeddb.js","names":["doc: LoroDoc","consolidated: Uint8Array","queue: Uint8Array[]"],"sources":["../../src/storage/indexeddb.ts"],"sourcesContent":["import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\n\nimport type {\n AssetId,\n StorageAdapter,\n StorageSavePayload,\n} from \"../types\";\nimport type { ExportBundle } from \"@loro-dev/flock\";\n\nconst DEFAULT_DB_NAME = \"loro-repo\";\nconst DEFAULT_DB_VERSION = 2;\nconst DEFAULT_DOC_STORE = \"docs\";\nconst DEFAULT_META_STORE = \"meta\";\nconst DEFAULT_ASSET_STORE = \"assets\";\nconst DEFAULT_DOC_UPDATE_STORE = \"doc-updates\";\nconst DEFAULT_META_UPDATE_STORE = \"meta-updates\";\nconst DEFAULT_META_KEY = \"snapshot\";\n\ntype EventListenerOptions = {\n once?: boolean;\n};\n\ntype IDBFactory = {\n open(name: string, version?: number): IDBOpenDBRequest;\n};\n\ntype IDBOpenDBRequest = {\n result: IDBDatabase;\n error: unknown;\n onupgradeneeded: ((event: unknown) => void) | null;\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype ObjectStoreNames = {\n contains?(name: string): boolean;\n length?: number;\n item?(index: number): string | null;\n};\n\ntype IDBDatabase = {\n close(): void;\n createObjectStore(name: string): IDBObjectStore;\n transaction(\n storeName: string,\n mode?: IDBTransactionMode,\n ): IDBTransaction;\n objectStoreNames: ObjectStoreNames;\n};\n\ntype IDBTransactionMode = \"readonly\" | \"readwrite\";\n\ntype IDBTransaction = {\n objectStore(name: string): IDBObjectStore;\n oncomplete: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onabort: ((event: unknown) => void) | null;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\ntype IDBObjectStore = {\n put(value: unknown, key?: unknown): IDBRequest<unknown>;\n get(key: unknown): IDBRequest<unknown>;\n delete(key: unknown): IDBRequest<unknown>;\n};\n\ntype IDBRequest<T> = {\n onsuccess: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n result: T;\n error: unknown;\n addEventListener(\n type: string,\n listener: (event: unknown) => void,\n options?: EventListenerOptions,\n ): void;\n};\n\nconst textDecoder = new TextDecoder();\nconst textEncoder = new TextEncoder();\n\nfunction describeUnknown(cause: unknown): string {\n if (typeof cause === \"string\") return cause;\n if (typeof cause === \"number\" || typeof cause === \"boolean\") {\n return String(cause);\n }\n if (typeof cause === \"bigint\") {\n return cause.toString();\n }\n if (typeof cause === \"symbol\") {\n return cause.description ?? cause.toString();\n }\n if (typeof cause === \"function\") {\n return `[function ${cause.name ?? \"anonymous\"}]`;\n }\n if (cause && typeof cause === \"object\") {\n try {\n return JSON.stringify(cause);\n } catch {\n return \"[object]\";\n }\n }\n return String(cause);\n}\n\nexport interface IndexedDBStorageAdaptorOptions {\n readonly dbName?: string;\n readonly version?: number;\n readonly docStoreName?: string;\n readonly docUpdateStoreName?: string;\n readonly metaStoreName?: string;\n readonly metaUpdateStoreName?: string;\n readonly assetStoreName?: string;\n readonly metaKey?: string;\n}\n\nexport class IndexedDBStorageAdaptor implements StorageAdapter {\n private readonly idb: IDBFactory;\n private readonly dbName: string;\n private readonly version: number;\n private readonly docStore: string;\n private readonly docUpdateStore: string;\n private readonly metaStore: string;\n private readonly metaUpdateStore: string;\n private readonly assetStore: string;\n private readonly metaKey: string;\n private dbPromise?: Promise<IDBDatabase>;\n private closed = false;\n\n constructor(options: IndexedDBStorageAdaptorOptions = {}) {\n const idbFactory = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idbFactory) {\n throw new Error(\"IndexedDB is not available in this environment\");\n }\n this.idb = idbFactory;\n this.dbName = options.dbName ?? DEFAULT_DB_NAME;\n this.version = options.version ?? DEFAULT_DB_VERSION;\n this.docStore = options.docStoreName ?? DEFAULT_DOC_STORE;\n this.docUpdateStore = options.docUpdateStoreName ?? DEFAULT_DOC_UPDATE_STORE;\n this.metaStore = options.metaStoreName ?? DEFAULT_META_STORE;\n this.metaUpdateStore = options.metaUpdateStoreName ?? DEFAULT_META_UPDATE_STORE;\n this.assetStore = options.assetStoreName ?? DEFAULT_ASSET_STORE;\n this.metaKey = options.metaKey ?? DEFAULT_META_KEY;\n }\n\n async save(payload: StorageSavePayload): Promise<void> {\n const db = await this.ensureDb();\n switch (payload.type) {\n case \"doc-snapshot\": {\n const snapshot = payload.snapshot.slice();\n await this.storeMergedSnapshot(db, payload.docId, snapshot);\n break;\n }\n case \"doc-update\": {\n const update = payload.update.slice();\n await this.appendDocUpdate(db, payload.docId, update);\n break;\n }\n case \"asset\": {\n const bytes = payload.data.slice();\n await this.putBinary(db, this.assetStore, payload.assetId, bytes);\n break;\n }\n case \"meta\": {\n const bytes = payload.update.slice();\n await this.appendMetaUpdate(db, bytes);\n break;\n }\n default:\n throw new Error(\"Unsupported storage payload type\");\n }\n }\n\n async deleteAsset(assetId: AssetId): Promise<void> {\n const db = await this.ensureDb();\n await this.deleteKey(db, this.assetStore, assetId);\n }\n\n async loadDoc(docId: string): Promise<LoroDoc | undefined> {\n const db = await this.ensureDb();\n const snapshot = await this.getBinaryFromDb(db, this.docStore, docId);\n const pendingUpdates = await this.getDocUpdates(db, docId);\n\n if (!snapshot && pendingUpdates.length === 0) {\n return undefined;\n }\n\n let doc: LoroDoc;\n try {\n doc = snapshot ? LoroDoc.fromSnapshot(snapshot) : new LoroDoc();\n } catch (error) {\n throw this.createError(\n `Failed to hydrate document snapshot for \"${docId}\"`,\n error,\n );\n }\n\n let appliedUpdates = false;\n for (const update of pendingUpdates) {\n try {\n doc.import(update);\n appliedUpdates = true;\n } catch (error) {\n throw this.createError(\n `Failed to apply queued document update for \"${docId}\"`,\n error,\n );\n }\n }\n\n if (appliedUpdates) {\n let consolidated: Uint8Array;\n try {\n consolidated = doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(\n `Failed to export consolidated snapshot for \"${docId}\"`,\n error,\n );\n }\n await this.writeSnapshot(db, docId, consolidated);\n await this.clearDocUpdates(db, docId);\n }\n\n return doc;\n }\n\n async deleteDoc(docId: string): Promise<void> {\n const db = await this.ensureDb();\n await Promise.all([\n this.deleteKey(db, this.docStore, docId),\n this.clearDocUpdates(db, docId),\n ]);\n }\n\n async loadMeta(): Promise<Flock | undefined> {\n const db = await this.ensureDb();\n const legacy = await this.getBinaryFromDb(db, this.metaStore, this.metaKey);\n const queuedUpdates = await this.getMetaUpdates(db);\n if (!legacy && queuedUpdates.length === 0) return undefined;\n\n const flock = new Flock();\n try {\n if (legacy) {\n const json = textDecoder.decode(legacy);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n for (const bytes of queuedUpdates) {\n const json = textDecoder.decode(bytes);\n const bundle = JSON.parse(json) as ExportBundle;\n flock.importJson(bundle);\n }\n } catch (error) {\n throw this.createError(\"Failed to hydrate metadata snapshot\", error);\n }\n\n if (legacy || queuedUpdates.length > 1) {\n const snapshot = flock.exportJson();\n const encoded = textEncoder.encode(JSON.stringify(snapshot));\n await this.writeMetaSnapshot(db, encoded);\n } else if (queuedUpdates.length === 1) {\n await this.writeMetaSnapshot(db, queuedUpdates[0]);\n }\n\n return flock;\n }\n\n async loadAsset(assetId: AssetId): Promise<Uint8Array | undefined> {\n const bytes = await this.getBinary(this.assetStore, assetId);\n return bytes ?? undefined;\n }\n\n async close(): Promise<void> {\n this.closed = true;\n const db = await this.dbPromise;\n if (db) {\n db.close();\n }\n this.dbPromise = undefined;\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.closed) {\n throw new Error(\"IndexedDBStorageAdaptor has been closed\");\n }\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const request = this.idb.open(this.dbName, this.version);\n request.addEventListener(\"upgradeneeded\", () => {\n const db = request.result;\n this.ensureStore(db, this.docStore);\n this.ensureStore(db, this.docUpdateStore);\n this.ensureStore(db, this.metaStore);\n this.ensureStore(db, this.metaUpdateStore);\n this.ensureStore(db, this.assetStore);\n });\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () => {\n reject(\n this.createError(\n `Failed to open IndexedDB database \"${this.dbName}\"`,\n request.error,\n ),\n );\n },\n { once: true },\n );\n });\n }\n return this.dbPromise;\n }\n\n private ensureStore(db: IDBDatabase, storeName: string): void {\n const names = db.objectStoreNames;\n if (this.storeExists(names, storeName)) return;\n db.createObjectStore(storeName);\n }\n\n private storeExists(names: ObjectStoreNames, storeName: string): boolean {\n if (typeof names.contains === \"function\") {\n return names.contains(storeName);\n }\n const length = names.length ?? 0;\n for (let index = 0; index < length; index += 1) {\n const value = names.item?.(index);\n if (value === storeName) return true;\n }\n return false;\n }\n\n private async storeMergedSnapshot(\n db: IDBDatabase,\n docId: string,\n incoming: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, this.docStore, \"readwrite\", async (store) => {\n const existingRaw = await this.wrapRequest(store.get(docId), \"read\");\n const existing = await this.normalizeBinary(existingRaw);\n const merged = this.mergeSnapshots(docId, existing, incoming);\n await this.wrapRequest(store.put(merged, docId), \"write\");\n });\n }\n\n private mergeSnapshots(\n docId: string,\n existing: Uint8Array | undefined,\n incoming: Uint8Array,\n ): Uint8Array {\n try {\n const doc = existing ? LoroDoc.fromSnapshot(existing) : new LoroDoc();\n doc.import(incoming);\n return doc.export({ mode: \"snapshot\" });\n } catch (error) {\n throw this.createError(`Failed to merge snapshot for \"${docId}\"`, error);\n }\n }\n\n private async appendDocUpdate(\n db: IDBDatabase,\n docId: string,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(docId), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, docId), \"write\");\n },\n );\n }\n\n private async getDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(docId), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async appendMetaUpdate(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readwrite\",\n async (store) => {\n const raw = await this.wrapRequest(store.get(\"queue\"), \"read\");\n const queue = await this.normalizeUpdateQueue(raw);\n queue.push(update.slice());\n await this.wrapRequest(store.put({ updates: queue }, \"queue\"), \"write\");\n },\n );\n }\n\n private async getMetaUpdates(db: IDBDatabase): Promise<Uint8Array[]> {\n const raw = await this.runInTransaction(\n db,\n this.metaUpdateStore,\n \"readonly\",\n (store) => this.wrapRequest(store.get(\"queue\"), \"read\"),\n );\n return this.normalizeUpdateQueue(raw);\n }\n\n private async writeMetaSnapshot(\n db: IDBDatabase,\n update: Uint8Array,\n ): Promise<void> {\n const bytes = update.slice();\n await this.runInTransaction(db, this.metaUpdateStore, \"readwrite\", async (store) => {\n await this.wrapRequest(store.put({ updates: [bytes] }, \"queue\"), \"write\");\n });\n await this.runInTransaction(db, this.metaStore, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(this.metaKey), \"delete\"),\n );\n }\n\n private async clearDocUpdates(\n db: IDBDatabase,\n docId: string,\n ): Promise<void> {\n await this.runInTransaction(\n db,\n this.docUpdateStore,\n \"readwrite\",\n (store) => this.wrapRequest(store.delete(docId), \"delete\"),\n );\n }\n\n private async writeSnapshot(\n db: IDBDatabase,\n docId: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n await this.putBinary(db, this.docStore, docId, snapshot.slice());\n }\n\n private async getBinaryFromDb(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const value = await this.runInTransaction(\n db,\n storeName,\n \"readonly\",\n (store) => this.wrapRequest(store.get(key), \"read\"),\n );\n return this.normalizeBinary(value);\n }\n\n private async normalizeUpdateQueue(value: unknown): Promise<Uint8Array[]> {\n if (value == null) return [];\n const list = Array.isArray(value)\n ? value\n : typeof value === \"object\" && value !== null\n ? (value as { updates?: unknown }).updates\n : undefined;\n\n if (!Array.isArray(list)) return [];\n\n const queue: Uint8Array[] = [];\n for (const entry of list) {\n const bytes = await this.normalizeBinary(entry);\n if (bytes) {\n queue.push(bytes);\n }\n }\n return queue;\n }\n\n private async putBinary(\n db: IDBDatabase,\n storeName: string,\n key: string,\n value: Uint8Array,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.put(value, key), \"write\"),\n );\n }\n\n private async deleteKey(\n db: IDBDatabase,\n storeName: string,\n key: string,\n ): Promise<void> {\n await this.runInTransaction(db, storeName, \"readwrite\", (store) =>\n this.wrapRequest(store.delete(key), \"delete\"),\n );\n }\n\n private async getBinary(\n storeName: string,\n key: string,\n ): Promise<Uint8Array | undefined> {\n const db = await this.ensureDb();\n return this.getBinaryFromDb(db, storeName, key);\n }\n\n private runInTransaction<T>(\n db: IDBDatabase,\n storeName: string,\n mode: IDBTransactionMode,\n executor: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> {\n const tx = db.transaction(storeName, mode);\n const store = tx.objectStore(storeName);\n const completion = new Promise<void>((resolve, reject) => {\n tx.addEventListener(\n \"complete\",\n () => resolve(),\n { once: true },\n );\n tx.addEventListener(\n \"abort\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction aborted\", tx.error),\n ),\n { once: true },\n );\n tx.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\"IndexedDB transaction failed\", tx.error),\n ),\n { once: true },\n );\n });\n return Promise.all([executor(store), completion]).then(([result]) => result);\n }\n\n private wrapRequest<T>(\n request: IDBRequest<T>,\n action: string,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.addEventListener(\n \"success\",\n () => resolve(request.result),\n { once: true },\n );\n request.addEventListener(\n \"error\",\n () =>\n reject(\n this.createError(\n `IndexedDB request failed during ${action}`,\n request.error,\n ),\n ),\n { once: true },\n );\n });\n }\n\n private async normalizeBinary(value: unknown): Promise<Uint8Array | undefined> {\n if (value == null) return undefined;\n if (value instanceof Uint8Array) {\n return value.slice();\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(\n value.buffer,\n value.byteOffset,\n value.byteLength,\n ).slice();\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value.slice(0));\n }\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"arrayBuffer\" in value\n ) {\n const candidate = value as {\n arrayBuffer?: unknown;\n };\n if (typeof candidate.arrayBuffer === \"function\") {\n const buffer = await candidate.arrayBuffer();\n return new Uint8Array(buffer);\n }\n }\n return undefined;\n }\n\n private createError(message: string, cause: unknown): Error {\n if (cause instanceof Error) {\n return new Error(`${message}: ${cause.message}`, { cause });\n }\n if (cause !== undefined && cause !== null) {\n return new Error(`${message}: ${describeUnknown(cause)}`);\n }\n return new Error(message);\n }\n}\n"],"mappings":"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"}
|