@bendyline/docblocks 1.1.2 → 2.0.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 +76 -11
- package/dist/chunk-2D7Y4IR3.js +1293 -0
- package/dist/chunk-2D7Y4IR3.js.map +1 -0
- package/dist/chunk-5MYAICRE.js +957 -0
- package/dist/chunk-5MYAICRE.js.map +1 -0
- package/dist/chunk-75UUF6C4.js +802 -0
- package/dist/chunk-75UUF6C4.js.map +1 -0
- package/dist/chunk-AEHCVWU5.js +25 -0
- package/dist/chunk-AEHCVWU5.js.map +1 -0
- package/dist/chunk-AYZ4HN5M.js +20 -0
- package/dist/chunk-AYZ4HN5M.js.map +1 -0
- package/dist/chunk-EGNDADXB.js +111 -0
- package/dist/chunk-EGNDADXB.js.map +1 -0
- package/dist/chunk-EZKJZUNJ.js +13 -0
- package/dist/chunk-EZKJZUNJ.js.map +1 -0
- package/dist/chunk-FNW556FN.js +75 -0
- package/dist/chunk-FNW556FN.js.map +1 -0
- package/dist/chunk-GCPBEMMH.js +563 -0
- package/dist/chunk-GCPBEMMH.js.map +1 -0
- package/dist/chunk-J32GU3F3.js +1664 -0
- package/dist/chunk-J32GU3F3.js.map +1 -0
- package/dist/chunk-PGR7LK6D.js +1062 -0
- package/dist/chunk-PGR7LK6D.js.map +1 -0
- package/dist/chunk-RQJRQF46.js +787 -0
- package/dist/chunk-RQJRQF46.js.map +1 -0
- package/dist/{chunk-ME76RUMR.js → chunk-RVQHY3XU.js} +15 -1
- package/dist/chunk-RVQHY3XU.js.map +1 -0
- package/dist/chunk-SI2LU2RP.js +224 -0
- package/dist/chunk-SI2LU2RP.js.map +1 -0
- package/dist/chunk-W5UUUII5.js +142 -0
- package/dist/chunk-W5UUUII5.js.map +1 -0
- package/dist/document/document-session.d.ts +127 -0
- package/dist/document/document-session.d.ts.map +1 -0
- package/dist/document/document-session.js +749 -0
- package/dist/document/document-session.js.map +1 -0
- package/dist/document/filesystem-target.d.ts +17 -0
- package/dist/document/filesystem-target.d.ts.map +1 -0
- package/dist/document/filesystem-target.js +70 -0
- package/dist/document/filesystem-target.js.map +1 -0
- package/dist/document/index.d.ts +6 -0
- package/dist/document/index.d.ts.map +1 -0
- package/dist/document/index.js +4 -0
- package/dist/document/index.js.map +1 -0
- package/dist/document/recovery-journal.d.ts +125 -0
- package/dist/document/recovery-journal.d.ts.map +1 -0
- package/dist/document/recovery-journal.js +385 -0
- package/dist/document/recovery-journal.js.map +1 -0
- package/dist/document/types.d.ts +89 -0
- package/dist/document/types.d.ts.map +1 -0
- package/dist/document/types.js +9 -0
- package/dist/document/types.js.map +1 -0
- package/dist/filesystem/commit-lock.d.ts +3 -0
- package/dist/filesystem/commit-lock.d.ts.map +1 -0
- package/dist/filesystem/commit-lock.js +20 -0
- package/dist/filesystem/commit-lock.js.map +1 -0
- package/dist/filesystem/dbk-workspace.d.ts +34 -0
- package/dist/filesystem/dbk-workspace.d.ts.map +1 -0
- package/dist/filesystem/dbk-workspace.js +85 -0
- package/dist/filesystem/dbk-workspace.js.map +1 -0
- package/dist/filesystem/electron-provider-v2.d.ts +36 -0
- package/dist/filesystem/electron-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/electron-provider-v2.js +503 -0
- package/dist/filesystem/electron-provider-v2.js.map +1 -0
- package/dist/filesystem/electron-provider.d.ts +6 -3
- package/dist/filesystem/electron-provider.d.ts.map +1 -1
- package/dist/filesystem/electron-provider.js +18 -13
- package/dist/filesystem/electron-provider.js.map +1 -1
- package/dist/filesystem/electron.d.ts +4 -0
- package/dist/filesystem/electron.d.ts.map +1 -0
- package/dist/filesystem/electron.js +4 -0
- package/dist/filesystem/electron.js.map +1 -0
- package/dist/filesystem/filesystem-content-container.d.ts +1 -1
- package/dist/filesystem/filesystem-content-container.d.ts.map +1 -1
- package/dist/filesystem/filesystem-content-container.js +50 -13
- package/dist/filesystem/filesystem-content-container.js.map +1 -1
- package/dist/filesystem/fs-error.d.ts +46 -0
- package/dist/filesystem/fs-error.d.ts.map +1 -0
- package/dist/filesystem/fs-error.js +170 -0
- package/dist/filesystem/fs-error.js.map +1 -0
- package/dist/filesystem/index.d.ts +15 -1
- package/dist/filesystem/index.d.ts.map +1 -1
- package/dist/filesystem/index.js +13 -0
- package/dist/filesystem/index.js.map +1 -1
- package/dist/filesystem/indexeddb-content-container.d.ts +3 -1
- package/dist/filesystem/indexeddb-content-container.d.ts.map +1 -1
- package/dist/filesystem/indexeddb-content-container.js +18 -11
- package/dist/filesystem/indexeddb-content-container.js.map +1 -1
- package/dist/filesystem/indexeddb-provider-v2.d.ts +96 -0
- package/dist/filesystem/indexeddb-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/indexeddb-provider-v2.js +1241 -0
- package/dist/filesystem/indexeddb-provider-v2.js.map +1 -0
- package/dist/filesystem/indexeddb-provider.d.ts +21 -14
- package/dist/filesystem/indexeddb-provider.d.ts.map +1 -1
- package/dist/filesystem/indexeddb-provider.js +111 -261
- package/dist/filesystem/indexeddb-provider.js.map +1 -1
- package/dist/filesystem/indexeddb-store.d.ts +34 -0
- package/dist/filesystem/indexeddb-store.d.ts.map +1 -0
- package/dist/filesystem/indexeddb-store.js +165 -0
- package/dist/filesystem/indexeddb-store.js.map +1 -0
- package/dist/filesystem/indexeddb.d.ts +5 -0
- package/dist/filesystem/indexeddb.d.ts.map +1 -0
- package/dist/filesystem/indexeddb.js +5 -0
- package/dist/filesystem/indexeddb.js.map +1 -0
- package/dist/filesystem/legacy-v2-adapter.d.ts +62 -0
- package/dist/filesystem/legacy-v2-adapter.d.ts.map +1 -0
- package/dist/filesystem/legacy-v2-adapter.js +499 -0
- package/dist/filesystem/legacy-v2-adapter.js.map +1 -0
- package/dist/filesystem/memory-provider-v2.d.ts +92 -0
- package/dist/filesystem/memory-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/memory-provider-v2.js +615 -0
- package/dist/filesystem/memory-provider-v2.js.map +1 -0
- package/dist/filesystem/memory-provider.d.ts +54 -0
- package/dist/filesystem/memory-provider.d.ts.map +1 -0
- package/dist/filesystem/memory-provider.js +132 -0
- package/dist/filesystem/memory-provider.js.map +1 -0
- package/dist/filesystem/memory.d.ts +4 -0
- package/dist/filesystem/memory.d.ts.map +1 -0
- package/dist/filesystem/memory.js +4 -0
- package/dist/filesystem/memory.js.map +1 -0
- package/dist/filesystem/move-entry.d.ts +9 -0
- package/dist/filesystem/move-entry.d.ts.map +1 -0
- package/dist/filesystem/move-entry.js +103 -0
- package/dist/filesystem/move-entry.js.map +1 -0
- package/dist/filesystem/move-error.d.ts +53 -0
- package/dist/filesystem/move-error.d.ts.map +1 -0
- package/dist/filesystem/move-error.js +99 -0
- package/dist/filesystem/move-error.js.map +1 -0
- package/dist/filesystem/native-provider-v2.d.ts +75 -0
- package/dist/filesystem/native-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/native-provider-v2.js +849 -0
- package/dist/filesystem/native-provider-v2.js.map +1 -0
- package/dist/filesystem/native-provider.d.ts +9 -1
- package/dist/filesystem/native-provider.d.ts.map +1 -1
- package/dist/filesystem/native-provider.js +172 -37
- package/dist/filesystem/native-provider.js.map +1 -1
- package/dist/filesystem/native.d.ts +4 -0
- package/dist/filesystem/native.d.ts.map +1 -0
- package/dist/filesystem/native.js +4 -0
- package/dist/filesystem/native.js.map +1 -0
- package/dist/filesystem/types.d.ts +37 -4
- package/dist/filesystem/types.d.ts.map +1 -1
- package/dist/filesystem/types.js +11 -3
- package/dist/filesystem/types.js.map +1 -1
- package/dist/filesystem/v2.d.ts +172 -0
- package/dist/filesystem/v2.d.ts.map +1 -0
- package/dist/filesystem/v2.js +16 -0
- package/dist/filesystem/v2.js.map +1 -0
- package/dist/filesystem/workspace-path.d.ts +21 -0
- package/dist/filesystem/workspace-path.d.ts.map +1 -0
- package/dist/filesystem/workspace-path.js +80 -0
- package/dist/filesystem/workspace-path.js.map +1 -0
- package/dist/host/filesystem-v2.d.ts +45 -0
- package/dist/host/filesystem-v2.d.ts.map +1 -0
- package/dist/host/filesystem-v2.js +13 -0
- package/dist/host/filesystem-v2.js.map +1 -0
- package/dist/host/git.d.ts +200 -0
- package/dist/host/git.d.ts.map +1 -0
- package/dist/host/git.js +15 -0
- package/dist/host/git.js.map +1 -0
- package/dist/host/index.d.ts +4 -1
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -0
- package/dist/host/index.js.map +1 -1
- package/dist/host/types.d.ts +114 -31
- package/dist/host/types.d.ts.map +1 -1
- package/dist/host/wire-policy.d.ts +22 -0
- package/dist/host/wire-policy.d.ts.map +1 -0
- package/dist/host/wire-policy.js +92 -0
- package/dist/host/wire-policy.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/types.d.ts +466 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +25 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp/wire.d.ts +29 -0
- package/dist/mcp/wire.d.ts.map +1 -0
- package/dist/mcp/wire.js +1046 -0
- package/dist/mcp/wire.js.map +1 -0
- package/dist/mcp/zod.d.ts +5763 -0
- package/dist/mcp/zod.d.ts.map +1 -0
- package/dist/mcp/zod.js +908 -0
- package/dist/mcp/zod.js.map +1 -0
- package/dist/types-CX893hgH.d.ts +468 -0
- package/dist/types-CsX6EN2t.d.ts +301 -0
- package/dist/types-eOlt9J7n.d.ts +478 -0
- package/dist/vscode/index.d.ts +3 -0
- package/dist/vscode/index.d.ts.map +1 -0
- package/dist/vscode/index.js +3 -0
- package/dist/vscode/index.js.map +1 -0
- package/dist/vscode/messages.d.ts +204 -0
- package/dist/vscode/messages.d.ts.map +1 -0
- package/dist/vscode/messages.js +494 -0
- package/dist/vscode/messages.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -2
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +2 -1
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/reconcile-electron.d.ts +19 -0
- package/dist/workspace/reconcile-electron.d.ts.map +1 -0
- package/dist/workspace/reconcile-electron.js +42 -0
- package/dist/workspace/reconcile-electron.js.map +1 -0
- package/dist/workspace/types.d.ts +41 -1
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/types.js +0 -4
- package/dist/workspace/types.js.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +17 -5
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +55 -11
- package/dist/workspace/workspace-manager.js.map +1 -1
- package/dist/workspace-path-CWVrcuPL.d.ts +21 -0
- package/package.json +43 -2
- package/src/document/document-session.ts +834 -0
- package/src/document/filesystem-target.ts +109 -0
- package/src/document/index.ts +41 -0
- package/src/document/recovery-journal.ts +536 -0
- package/src/document/types.ts +116 -0
- package/src/filesystem/commit-lock.ts +29 -0
- package/src/filesystem/dbk-workspace.ts +135 -0
- package/src/filesystem/electron-provider-v2.ts +630 -0
- package/src/filesystem/electron-provider.ts +25 -14
- package/src/filesystem/electron.ts +3 -0
- package/src/filesystem/filesystem-content-container.ts +53 -15
- package/src/filesystem/fs-error.ts +234 -0
- package/src/filesystem/index.ts +119 -0
- package/src/filesystem/indexeddb-content-container.ts +20 -12
- package/src/filesystem/indexeddb-provider-v2.ts +1740 -0
- package/src/filesystem/indexeddb-provider.ts +126 -300
- package/src/filesystem/indexeddb-store.ts +207 -0
- package/src/filesystem/indexeddb.ts +12 -0
- package/src/filesystem/legacy-v2-adapter.ts +779 -0
- package/src/filesystem/memory-provider-v2.ts +896 -0
- package/src/filesystem/memory-provider.ts +180 -0
- package/src/filesystem/memory.ts +15 -0
- package/src/filesystem/move-entry.ts +142 -0
- package/src/filesystem/move-error.ts +159 -0
- package/src/filesystem/native-provider-v2.ts +1165 -0
- package/src/filesystem/native-provider.ts +194 -41
- package/src/filesystem/native.ts +16 -0
- package/src/filesystem/types.ts +49 -4
- package/src/filesystem/v2.ts +231 -0
- package/src/filesystem/workspace-path.ts +92 -0
- package/src/host/filesystem-v2.ts +104 -0
- package/src/host/git.ts +246 -0
- package/src/host/index.ts +46 -0
- package/src/host/types.ts +155 -34
- package/src/host/wire-policy.ts +103 -0
- package/src/index.ts +2 -1
- package/src/mcp/index.ts +3 -0
- package/src/mcp/types.ts +594 -0
- package/src/mcp/wire.ts +1280 -0
- package/src/mcp/zod.ts +1000 -0
- package/src/vscode/index.ts +2 -0
- package/src/vscode/messages.ts +704 -0
- package/src/workspace/index.ts +8 -1
- package/src/workspace/reconcile-electron.ts +65 -0
- package/src/workspace/types.ts +39 -1
- package/src/workspace/workspace-manager.ts +71 -10
- package/dist/chunk-GUNM43XZ.js +0 -63
- package/dist/chunk-GUNM43XZ.js.map +0 -1
- package/dist/chunk-IM7LCQJ6.js +0 -850
- package/dist/chunk-IM7LCQJ6.js.map +0 -1
- package/dist/chunk-ME76RUMR.js.map +0 -1
- package/dist/index-pgBHBQ9G.d.ts +0 -213
|
@@ -1,345 +1,171 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* using the LocalForageAdapter from @bendyline/squisq/storage.
|
|
2
|
+
* Legacy FileSystemProvider facade over the revisioned IndexedDB authority.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* fs:{path}:meta → FileMeta object
|
|
9
|
-
* fs:dirs → Set<string> of known directory paths
|
|
4
|
+
* The old `fs:*` namespace is migration input only. Keeping this facade as an
|
|
5
|
+
* adapter prevents current callers from recreating a second persistent source
|
|
6
|
+
* of truth while the workspace moves to FileSystemProviderV2.
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function parentDir(p: string): string {
|
|
24
|
-
const idx = p.lastIndexOf('/');
|
|
25
|
-
return idx === -1 ? '' : p.slice(0, idx);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Get the filename component of a path. */
|
|
29
|
-
function baseName(p: string): string {
|
|
30
|
-
const idx = p.lastIndexOf('/');
|
|
31
|
-
return idx === -1 ? p : p.slice(idx + 1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ── Key helpers ────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
function contentKey(path: string): string {
|
|
37
|
-
return `fs:${path}:content`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function binaryKey(path: string): string {
|
|
41
|
-
return `fs:${path}:binary`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function metaKey(path: string): string {
|
|
45
|
-
return `fs:${path}:meta`;
|
|
9
|
+
import type { FileCommitResult, FileMeta, FileSystemEntry, FileSystemProvider } from './types.js';
|
|
10
|
+
import { FsError, type FsOperation } from './fs-error.js';
|
|
11
|
+
import { parseWorkspacePath, type WorkspacePath } from './workspace-path.js';
|
|
12
|
+
import { RawIndexedDBFileSystemStore, type IndexedDBFileSystemStore } from './indexeddb-store.js';
|
|
13
|
+
import { IndexedDBFileSystemProviderV2 } from './indexeddb-provider-v2.js';
|
|
14
|
+
|
|
15
|
+
export interface IndexedDBFileSystemProviderOptions {
|
|
16
|
+
/** Injectable transaction store for deterministic fault/concurrency tests. */
|
|
17
|
+
store?: IndexedDBFileSystemStore;
|
|
18
|
+
/** Injectable metadata clock. */
|
|
19
|
+
now?: () => Date;
|
|
46
20
|
}
|
|
47
21
|
|
|
48
|
-
const DIRS_KEY = 'fs:dirs';
|
|
49
|
-
|
|
50
|
-
// ── Implementation ─────────────────────────────────────────────────
|
|
51
|
-
|
|
52
22
|
export class IndexedDBFileSystemProvider implements FileSystemProvider {
|
|
53
|
-
readonly id: string;
|
|
54
|
-
readonly label: string;
|
|
23
|
+
public readonly id: string;
|
|
24
|
+
public readonly label: string;
|
|
25
|
+
public readonly v2: IndexedDBFileSystemProviderV2;
|
|
55
26
|
|
|
56
|
-
private
|
|
27
|
+
private disposed = false;
|
|
57
28
|
|
|
58
|
-
constructor(id: string, label: string) {
|
|
29
|
+
public constructor(id: string, label: string, options: IndexedDBFileSystemProviderOptions = {}) {
|
|
59
30
|
this.id = id;
|
|
60
31
|
this.label = label;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
32
|
+
const store = options.store ?? new RawIndexedDBFileSystemStore(`docblocks-fs-${id}`, 'files');
|
|
33
|
+
this.v2 = new IndexedDBFileSystemProviderV2(id, label, {
|
|
34
|
+
store,
|
|
35
|
+
now: options.now,
|
|
36
|
+
closeStoreOnDispose: true,
|
|
64
37
|
});
|
|
65
38
|
}
|
|
66
39
|
|
|
67
|
-
|
|
40
|
+
/** Release the underlying database connection after accepted work settles. */
|
|
41
|
+
public dispose(): Promise<void> {
|
|
42
|
+
this.disposed = true;
|
|
43
|
+
return this.v2.dispose();
|
|
44
|
+
}
|
|
68
45
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
46
|
+
public async readFile(path: string): Promise<string | null> {
|
|
47
|
+
this.assertOpen('read');
|
|
48
|
+
const read = await this.v2.readFile(this.path(path, false, 'read'));
|
|
49
|
+
return read ? new TextDecoder().decode(read.data) : null;
|
|
72
50
|
}
|
|
73
51
|
|
|
74
|
-
|
|
75
|
-
|
|
52
|
+
public async writeFile(path: string, content: string): Promise<void> {
|
|
53
|
+
this.assertOpen('write');
|
|
54
|
+
await this.v2.writeFile(this.path(path, false, 'write'), new TextEncoder().encode(content), {
|
|
55
|
+
createParents: true,
|
|
56
|
+
});
|
|
76
57
|
}
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
59
|
+
public async commitFile(
|
|
60
|
+
path: string,
|
|
61
|
+
content: string,
|
|
62
|
+
expectedContent: string | null,
|
|
63
|
+
): Promise<FileCommitResult> {
|
|
64
|
+
this.assertOpen('write');
|
|
65
|
+
const canonical = this.path(path, false, 'write');
|
|
66
|
+
const current = await this.v2.readFile(canonical);
|
|
67
|
+
const currentContent = current ? new TextDecoder().decode(current.data) : null;
|
|
68
|
+
if (currentContent !== expectedContent && currentContent !== content) {
|
|
69
|
+
return {
|
|
70
|
+
status: 'conflict',
|
|
71
|
+
content: currentContent,
|
|
72
|
+
version: current?.entry.version ?? null,
|
|
73
|
+
};
|
|
91
74
|
}
|
|
92
|
-
|
|
93
|
-
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const committed = await this.v2.writeFile(canonical, new TextEncoder().encode(content), {
|
|
78
|
+
createParents: true,
|
|
79
|
+
expectedVersion: current?.entry.version ?? null,
|
|
80
|
+
});
|
|
81
|
+
return { status: 'committed', version: committed.version };
|
|
82
|
+
} catch (error: unknown) {
|
|
83
|
+
if (!(error instanceof FsError) || error.code !== 'conflict') throw error;
|
|
84
|
+
const latest = await this.v2.readFile(canonical);
|
|
85
|
+
return {
|
|
86
|
+
status: 'conflict',
|
|
87
|
+
content: latest ? new TextDecoder().decode(latest.data) : null,
|
|
88
|
+
version: latest?.entry.version ?? null,
|
|
89
|
+
};
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
92
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
public async delete(path: string): Promise<void> {
|
|
94
|
+
this.assertOpen('remove');
|
|
95
|
+
await this.v2.remove(this.path(path, false, 'remove'), {
|
|
96
|
+
recursive: true,
|
|
97
|
+
missing: 'ignore',
|
|
98
|
+
});
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
async
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const meta: FileMeta = {
|
|
112
|
-
name: baseName(p),
|
|
113
|
-
path: p,
|
|
114
|
-
size: new Blob([content]).size,
|
|
115
|
-
lastModified: new Date().toISOString(),
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
await this.store.set(contentKey(p), content);
|
|
119
|
-
await this.store.set(metaKey(p), meta);
|
|
101
|
+
public async rename(oldPath: string, newPath: string): Promise<void> {
|
|
102
|
+
this.assertOpen('move');
|
|
103
|
+
await this.v2.move(this.path(oldPath, false, 'move'), this.path(newPath, false, 'move'), {
|
|
104
|
+
createParents: true,
|
|
105
|
+
});
|
|
120
106
|
}
|
|
121
107
|
|
|
122
|
-
async
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const toRemove: string[] = [p];
|
|
135
|
-
for (const d of dirs) {
|
|
136
|
-
if (d.startsWith(prefix)) {
|
|
137
|
-
toRemove.push(d);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
for (const d of toRemove) {
|
|
141
|
-
dirs.delete(d);
|
|
142
|
-
}
|
|
143
|
-
await this.saveDirs(dirs);
|
|
144
|
-
|
|
145
|
-
// Remove all file keys under this directory
|
|
146
|
-
const allKeys = await this.store.keys();
|
|
147
|
-
const filePrefix = `fs:${p}/`;
|
|
148
|
-
const keysToRemove = allKeys.filter((k) => k.startsWith(filePrefix));
|
|
149
|
-
await Promise.all(keysToRemove.map((k) => this.store.remove(k)));
|
|
108
|
+
public async readDirectory(path: string): Promise<FileSystemEntry[]> {
|
|
109
|
+
this.assertOpen('list');
|
|
110
|
+
try {
|
|
111
|
+
const entries = await this.v2.readDirectory(this.path(path, true, 'list'));
|
|
112
|
+
return entries.map((entry) => ({
|
|
113
|
+
kind: entry.kind,
|
|
114
|
+
name: entry.name,
|
|
115
|
+
path: entry.path,
|
|
116
|
+
}));
|
|
117
|
+
} catch (error: unknown) {
|
|
118
|
+
if (error instanceof FsError && error.code === 'not-found') return [];
|
|
119
|
+
throw error;
|
|
150
120
|
}
|
|
151
121
|
}
|
|
152
122
|
|
|
153
|
-
async
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (op === np) return;
|
|
157
|
-
if (!op || !np) {
|
|
158
|
-
throw new Error('Cannot rename the filesystem root');
|
|
159
|
-
}
|
|
160
|
-
if (np.startsWith(op + '/')) {
|
|
161
|
-
throw new Error('Cannot move a directory into itself');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const dirs = await this.getDirs();
|
|
165
|
-
|
|
166
|
-
// Handle directory rename by moving every tracked child directory and
|
|
167
|
-
// every file record keyed under the old directory prefix.
|
|
168
|
-
if (dirs.has(op)) {
|
|
169
|
-
const newParent = parentDir(np);
|
|
170
|
-
if (newParent) {
|
|
171
|
-
await this.ensureDir(newParent);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const allKeys = await this.store.keys();
|
|
175
|
-
const oldKeyPrefix = `fs:${op}/`;
|
|
176
|
-
const newKeyPrefix = `fs:${np}/`;
|
|
177
|
-
const metaSuffix = ':meta';
|
|
178
|
-
const keysToMove = allKeys.filter((k) => k.startsWith(oldKeyPrefix));
|
|
179
|
-
|
|
180
|
-
await Promise.all(
|
|
181
|
-
keysToMove.map(async (oldKey) => {
|
|
182
|
-
const newKey = newKeyPrefix + oldKey.slice(oldKeyPrefix.length);
|
|
183
|
-
if (oldKey.endsWith(metaSuffix)) {
|
|
184
|
-
const meta = await this.store.get<FileMeta>(oldKey);
|
|
185
|
-
if (meta) {
|
|
186
|
-
const newFilePath = newKey.slice(3, -metaSuffix.length);
|
|
187
|
-
await this.store.set(metaKey(newFilePath), {
|
|
188
|
-
...meta,
|
|
189
|
-
name: baseName(newFilePath),
|
|
190
|
-
path: newFilePath,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
const value = await this.store.get<unknown>(oldKey);
|
|
195
|
-
if (value !== null) {
|
|
196
|
-
await this.store.set(newKey, value);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
await this.store.remove(oldKey);
|
|
200
|
-
}),
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
dirs.delete(op);
|
|
204
|
-
dirs.add(np);
|
|
205
|
-
const oldPrefix = op + '/';
|
|
206
|
-
const newPrefix = np + '/';
|
|
207
|
-
for (const d of [...dirs]) {
|
|
208
|
-
if (d.startsWith(oldPrefix)) {
|
|
209
|
-
dirs.delete(d);
|
|
210
|
-
dirs.add(newPrefix + d.slice(oldPrefix.length));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
await this.saveDirs(dirs);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Read existing data
|
|
218
|
-
const content = await this.store.get<string>(contentKey(op));
|
|
219
|
-
const binary = await this.store.get<ArrayBuffer>(binaryKey(op));
|
|
220
|
-
const meta = await this.store.get<FileMeta>(metaKey(op));
|
|
221
|
-
|
|
222
|
-
// Write to new location
|
|
223
|
-
if (content !== null) {
|
|
224
|
-
await this.store.set(contentKey(np), content);
|
|
225
|
-
}
|
|
226
|
-
if (binary !== null) {
|
|
227
|
-
await this.store.set(binaryKey(np), binary);
|
|
228
|
-
}
|
|
229
|
-
if (meta) {
|
|
230
|
-
await this.store.set(metaKey(np), {
|
|
231
|
-
...meta,
|
|
232
|
-
name: baseName(np),
|
|
233
|
-
path: np,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Ensure parent dir of new path exists
|
|
238
|
-
const newParent = parentDir(np);
|
|
239
|
-
if (newParent) {
|
|
240
|
-
await this.ensureDir(newParent);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Delete old
|
|
244
|
-
await this.store.remove(contentKey(op));
|
|
245
|
-
await this.store.remove(binaryKey(op));
|
|
246
|
-
await this.store.remove(metaKey(op));
|
|
123
|
+
public async exists(path: string): Promise<boolean> {
|
|
124
|
+
this.assertOpen('stat');
|
|
125
|
+
return (await this.v2.stat(this.path(path, true, 'stat'))) !== null;
|
|
247
126
|
}
|
|
248
127
|
|
|
249
|
-
async
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// Find child directories
|
|
256
|
-
const prefix = p ? p + '/' : '';
|
|
257
|
-
for (const d of dirs) {
|
|
258
|
-
if (!p && !d.includes('/')) {
|
|
259
|
-
// Root-level directory
|
|
260
|
-
if (!seen.has(d)) {
|
|
261
|
-
seen.add(d);
|
|
262
|
-
entries.push({ kind: 'directory', name: d, path: d });
|
|
263
|
-
}
|
|
264
|
-
} else if (p && d.startsWith(prefix)) {
|
|
265
|
-
const rest = d.slice(prefix.length);
|
|
266
|
-
if (!rest.includes('/')) {
|
|
267
|
-
// Direct child directory
|
|
268
|
-
if (!seen.has(rest)) {
|
|
269
|
-
seen.add(rest);
|
|
270
|
-
entries.push({ kind: 'directory', name: rest, path: d });
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Find child files by scanning meta keys
|
|
277
|
-
const allKeys = await this.store.keys();
|
|
278
|
-
const metaPrefix = p ? `fs:${p}/` : 'fs:';
|
|
279
|
-
const metaSuffix = ':meta';
|
|
280
|
-
|
|
281
|
-
for (const key of allKeys) {
|
|
282
|
-
if (!key.startsWith(metaPrefix) || !key.endsWith(metaSuffix)) continue;
|
|
283
|
-
|
|
284
|
-
const filePath = key.slice(3, -metaSuffix.length); // strip "fs:" and ":meta"
|
|
285
|
-
const rel = p ? filePath.slice(prefix.length) : filePath;
|
|
286
|
-
|
|
287
|
-
// Only direct children (no further slashes)
|
|
288
|
-
if (rel.includes('/')) continue;
|
|
289
|
-
|
|
290
|
-
if (!seen.has(rel)) {
|
|
291
|
-
seen.add(rel);
|
|
292
|
-
entries.push({ kind: 'file', name: rel, path: filePath });
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Sort: directories first, then alphabetical
|
|
297
|
-
entries.sort((a, b) => {
|
|
298
|
-
if (a.kind !== b.kind) return a.kind === 'directory' ? -1 : 1;
|
|
299
|
-
return a.name.localeCompare(b.name);
|
|
128
|
+
public async createDirectory(path: string): Promise<void> {
|
|
129
|
+
this.assertOpen('create-directory');
|
|
130
|
+
await this.v2.createDirectory(this.path(path, false, 'create-directory'), {
|
|
131
|
+
createParents: true,
|
|
132
|
+
mode: 'ensure',
|
|
300
133
|
});
|
|
301
|
-
|
|
302
|
-
return entries;
|
|
303
134
|
}
|
|
304
135
|
|
|
305
|
-
async
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
136
|
+
public async stat(path: string): Promise<FileMeta | null> {
|
|
137
|
+
this.assertOpen('stat');
|
|
138
|
+
const entry = await this.v2.stat(this.path(path, false, 'stat'));
|
|
139
|
+
if (!entry || entry.kind !== 'file') return null;
|
|
140
|
+
return {
|
|
141
|
+
name: entry.name,
|
|
142
|
+
path: entry.path,
|
|
143
|
+
size: entry.size,
|
|
144
|
+
lastModified: entry.lastModified,
|
|
145
|
+
};
|
|
311
146
|
}
|
|
312
147
|
|
|
313
|
-
async
|
|
314
|
-
|
|
315
|
-
await this.
|
|
148
|
+
public async readBinary(path: string): Promise<ArrayBuffer | null> {
|
|
149
|
+
this.assertOpen('read');
|
|
150
|
+
return (await this.v2.readFile(this.path(path, false, 'read')))?.data ?? null;
|
|
316
151
|
}
|
|
317
152
|
|
|
318
|
-
async
|
|
319
|
-
|
|
320
|
-
|
|
153
|
+
public async writeBinary(path: string, data: ArrayBuffer | Uint8Array): Promise<void> {
|
|
154
|
+
this.assertOpen('write');
|
|
155
|
+
await this.v2.writeFile(this.path(path, false, 'write'), data, { createParents: true });
|
|
321
156
|
}
|
|
322
157
|
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
158
|
+
private path(path: string, allowRoot: boolean, operation: FsOperation): WorkspacePath {
|
|
159
|
+
const canonical = parseWorkspacePath(path);
|
|
160
|
+
if (canonical || allowRoot) return canonical;
|
|
161
|
+
throw new FsError('invalid-path', 'The filesystem root cannot be used as a mutation target.', {
|
|
162
|
+
operation,
|
|
163
|
+
path,
|
|
164
|
+
});
|
|
326
165
|
}
|
|
327
166
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (parent) {
|
|
332
|
-
await this.ensureDir(parent);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const meta: FileMeta = {
|
|
336
|
-
name: baseName(p),
|
|
337
|
-
path: p,
|
|
338
|
-
size: data.byteLength,
|
|
339
|
-
lastModified: new Date().toISOString(),
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
await this.store.set(binaryKey(p), data);
|
|
343
|
-
await this.store.set(metaKey(p), meta);
|
|
167
|
+
private assertOpen(operation: FsOperation): void {
|
|
168
|
+
if (!this.disposed) return;
|
|
169
|
+
throw new FsError('closed', 'The IndexedDB filesystem provider is disposed.', { operation });
|
|
344
170
|
}
|
|
345
171
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, error-propagating IndexedDB store used by the virtual filesystem.
|
|
3
|
+
*
|
|
4
|
+
* The previous Squisq LocalForage adapter intentionally converted failed
|
|
5
|
+
* reads/enumerations/removals into null/empty/success. Those semantics are
|
|
6
|
+
* unsuitable for filesystem mutations: an unavailable database must never be
|
|
7
|
+
* interpreted as an absent file. This layer opens the old database and object
|
|
8
|
+
* store directly so existing data can be migrated in one transaction.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type IndexedDBFileSystemTransactionMode = 'readonly' | 'readwrite';
|
|
12
|
+
|
|
13
|
+
export interface IndexedDBFileSystemTransaction {
|
|
14
|
+
get<T>(key: string): Promise<T | null>;
|
|
15
|
+
put<T>(key: string, value: T): Promise<void>;
|
|
16
|
+
delete(key: string): Promise<void>;
|
|
17
|
+
keys(): Promise<string[]>;
|
|
18
|
+
/** O(log n) namespace probe in the concrete IndexedDB implementation. */
|
|
19
|
+
hasKeyWithPrefix(prefix: string): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IndexedDBFileSystemStore {
|
|
23
|
+
transaction<T>(
|
|
24
|
+
mode: IndexedDBFileSystemTransactionMode,
|
|
25
|
+
operation: (transaction: IndexedDBFileSystemTransaction) => Promise<T>,
|
|
26
|
+
): Promise<T>;
|
|
27
|
+
close?(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class RawIndexedDBFileSystemStore implements IndexedDBFileSystemStore {
|
|
31
|
+
private database: IDBDatabase | null = null;
|
|
32
|
+
private opening: Promise<IDBDatabase> | null = null;
|
|
33
|
+
private closed = false;
|
|
34
|
+
|
|
35
|
+
public constructor(
|
|
36
|
+
private readonly databaseName: string,
|
|
37
|
+
private readonly storeName: string,
|
|
38
|
+
) {}
|
|
39
|
+
|
|
40
|
+
public async transaction<T>(
|
|
41
|
+
mode: IndexedDBFileSystemTransactionMode,
|
|
42
|
+
operation: (transaction: IndexedDBFileSystemTransaction) => Promise<T>,
|
|
43
|
+
): Promise<T> {
|
|
44
|
+
const database = await this.getDatabase();
|
|
45
|
+
const nativeTransaction = database.transaction(this.storeName, mode);
|
|
46
|
+
const completion = waitForTransaction(nativeTransaction);
|
|
47
|
+
const transaction = new RawIndexedDBFileSystemTransaction(
|
|
48
|
+
nativeTransaction.objectStore(this.storeName),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
let result: T;
|
|
52
|
+
try {
|
|
53
|
+
result = await operation(transaction);
|
|
54
|
+
} catch (error: unknown) {
|
|
55
|
+
try {
|
|
56
|
+
nativeTransaction.abort();
|
|
57
|
+
} catch {
|
|
58
|
+
// The request failure may already have aborted the transaction.
|
|
59
|
+
}
|
|
60
|
+
await completion.catch(() => undefined);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await completion;
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public close(): void {
|
|
69
|
+
if (this.closed) return;
|
|
70
|
+
this.closed = true;
|
|
71
|
+
this.database?.close();
|
|
72
|
+
this.database = null;
|
|
73
|
+
this.opening = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private getDatabase(): Promise<IDBDatabase> {
|
|
77
|
+
if (this.closed) {
|
|
78
|
+
return Promise.reject(new Error('The IndexedDB filesystem store is closed.'));
|
|
79
|
+
}
|
|
80
|
+
if (this.database) return Promise.resolve(this.database);
|
|
81
|
+
if (this.opening) return this.opening;
|
|
82
|
+
if (typeof indexedDB === 'undefined') {
|
|
83
|
+
return Promise.reject(new Error('IndexedDB is not available in this environment.'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.opening = openDatabaseWithStore(this.databaseName, this.storeName)
|
|
87
|
+
.then((database) => {
|
|
88
|
+
if (this.closed) {
|
|
89
|
+
database.close();
|
|
90
|
+
throw new Error('The IndexedDB filesystem store is closed.');
|
|
91
|
+
}
|
|
92
|
+
this.database = database;
|
|
93
|
+
this.opening = null;
|
|
94
|
+
database.onversionchange = () => {
|
|
95
|
+
database.close();
|
|
96
|
+
if (this.database === database) this.database = null;
|
|
97
|
+
};
|
|
98
|
+
return database;
|
|
99
|
+
})
|
|
100
|
+
.catch((error: unknown) => {
|
|
101
|
+
this.opening = null;
|
|
102
|
+
throw error;
|
|
103
|
+
});
|
|
104
|
+
return this.opening;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class RawIndexedDBFileSystemTransaction implements IndexedDBFileSystemTransaction {
|
|
109
|
+
public constructor(private readonly store: IDBObjectStore) {}
|
|
110
|
+
|
|
111
|
+
public async get<T>(key: string): Promise<T | null> {
|
|
112
|
+
const value = await requestResult(this.store.get(key));
|
|
113
|
+
return value === undefined ? null : (value as T);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public async put<T>(key: string, value: T): Promise<void> {
|
|
117
|
+
await requestResult(this.store.put(value, key));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public async delete(key: string): Promise<void> {
|
|
121
|
+
await requestResult(this.store.delete(key));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public async keys(): Promise<string[]> {
|
|
125
|
+
const keys = await requestResult(this.store.getAllKeys());
|
|
126
|
+
return keys.map((key) => {
|
|
127
|
+
if (typeof key !== 'string') {
|
|
128
|
+
throw new Error('The DocBlocks filesystem store contains a non-string key.');
|
|
129
|
+
}
|
|
130
|
+
return key;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public async hasKeyWithPrefix(prefix: string): Promise<boolean> {
|
|
135
|
+
const cursor = await requestResult(this.store.openKeyCursor(IDBKeyRange.lowerBound(prefix)));
|
|
136
|
+
return cursor !== null && typeof cursor.key === 'string' && cursor.key.startsWith(prefix);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function openDatabaseWithStore(
|
|
141
|
+
databaseName: string,
|
|
142
|
+
storeName: string,
|
|
143
|
+
): Promise<IDBDatabase> {
|
|
144
|
+
const database = await openDatabase(databaseName, undefined, (upgradeDatabase) => {
|
|
145
|
+
if (!upgradeDatabase.objectStoreNames.contains(storeName)) {
|
|
146
|
+
upgradeDatabase.createObjectStore(storeName);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if (database.objectStoreNames.contains(storeName)) return database;
|
|
150
|
+
|
|
151
|
+
const nextVersion = database.version + 1;
|
|
152
|
+
database.close();
|
|
153
|
+
return openDatabase(databaseName, nextVersion, (upgradeDatabase) => {
|
|
154
|
+
if (!upgradeDatabase.objectStoreNames.contains(storeName)) {
|
|
155
|
+
upgradeDatabase.createObjectStore(storeName);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function openDatabase(
|
|
161
|
+
databaseName: string,
|
|
162
|
+
version: number | undefined,
|
|
163
|
+
upgrade: (database: IDBDatabase) => void,
|
|
164
|
+
): Promise<IDBDatabase> {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const request =
|
|
167
|
+
version === undefined ? indexedDB.open(databaseName) : indexedDB.open(databaseName, version);
|
|
168
|
+
let settled = false;
|
|
169
|
+
|
|
170
|
+
request.onupgradeneeded = () => upgrade(request.result);
|
|
171
|
+
request.onsuccess = () => {
|
|
172
|
+
if (settled) {
|
|
173
|
+
request.result.close();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
settled = true;
|
|
177
|
+
resolve(request.result);
|
|
178
|
+
};
|
|
179
|
+
request.onerror = () => {
|
|
180
|
+
if (settled) return;
|
|
181
|
+
settled = true;
|
|
182
|
+
reject(request.error ?? new Error(`Could not open IndexedDB database ${databaseName}.`));
|
|
183
|
+
};
|
|
184
|
+
request.onblocked = () => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
reject(new Error(`IndexedDB database ${databaseName} is blocked by another open page.`));
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function requestResult<T>(request: IDBRequest<T>): Promise<T> {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
request.onsuccess = () => resolve(request.result);
|
|
195
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed.'));
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function waitForTransaction(transaction: IDBTransaction): Promise<void> {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
transaction.oncomplete = () => resolve();
|
|
202
|
+
transaction.onabort = () =>
|
|
203
|
+
reject(transaction.error ?? new Error('IndexedDB transaction was aborted.'));
|
|
204
|
+
transaction.onerror = () =>
|
|
205
|
+
reject(transaction.error ?? new Error('IndexedDB transaction failed.'));
|
|
206
|
+
});
|
|
207
|
+
}
|