@bendyline/docblocks 0.1.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/dist/chunk-GUNM43XZ.js +63 -0
  4. package/dist/chunk-GUNM43XZ.js.map +1 -0
  5. package/dist/chunk-NSVTXALR.js +559 -0
  6. package/dist/chunk-NSVTXALR.js.map +1 -0
  7. package/dist/filesystem/index.d.ts +5 -0
  8. package/dist/filesystem/index.d.ts.map +1 -0
  9. package/dist/filesystem/index.js +4 -0
  10. package/dist/filesystem/index.js.map +1 -0
  11. package/dist/filesystem/indexeddb-content-container.d.ts +20 -0
  12. package/dist/filesystem/indexeddb-content-container.d.ts.map +1 -0
  13. package/dist/filesystem/indexeddb-content-container.js +93 -0
  14. package/dist/filesystem/indexeddb-content-container.js.map +1 -0
  15. package/dist/filesystem/indexeddb-provider.d.ts +32 -0
  16. package/dist/filesystem/indexeddb-provider.d.ts.map +1 -0
  17. package/dist/filesystem/indexeddb-provider.js +253 -0
  18. package/dist/filesystem/indexeddb-provider.js.map +1 -0
  19. package/dist/filesystem/native-provider.d.ts +44 -0
  20. package/dist/filesystem/native-provider.d.ts.map +1 -0
  21. package/dist/filesystem/native-provider.js +302 -0
  22. package/dist/filesystem/native-provider.js.map +1 -0
  23. package/dist/filesystem/types.d.ts +50 -0
  24. package/dist/filesystem/types.d.ts.map +1 -0
  25. package/dist/filesystem/types.js +8 -0
  26. package/dist/filesystem/types.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/workspace/index.d.ts +3 -0
  32. package/dist/workspace/index.d.ts.map +1 -0
  33. package/dist/workspace/index.js +2 -0
  34. package/dist/workspace/index.js.map +1 -0
  35. package/dist/workspace/types.d.ts +15 -0
  36. package/dist/workspace/types.d.ts.map +1 -0
  37. package/dist/workspace/types.js +6 -0
  38. package/dist/workspace/types.js.map +1 -0
  39. package/dist/workspace/workspace-manager.d.ts +35 -0
  40. package/dist/workspace/workspace-manager.d.ts.map +1 -0
  41. package/dist/workspace/workspace-manager.js +82 -0
  42. package/dist/workspace/workspace-manager.js.map +1 -0
  43. package/package.json +59 -0
  44. package/src/filesystem/index.ts +20 -0
  45. package/src/filesystem/indexeddb-content-container.ts +104 -0
  46. package/src/filesystem/indexeddb-provider.ts +299 -0
  47. package/src/filesystem/native-provider.ts +348 -0
  48. package/src/filesystem/types.ts +69 -0
  49. package/src/index.ts +8 -0
  50. package/src/workspace/index.ts +10 -0
  51. package/src/workspace/types.ts +15 -0
  52. package/src/workspace/workspace-manager.ts +90 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bendyline
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @bendyline/docblocks
2
+
3
+ Core data structures and filesystem abstractions for DocBlocks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bendyline/docblocks
9
+ ```
10
+
11
+ ## Exports
12
+
13
+ The package provides two main modules:
14
+
15
+ ### Filesystem (`@bendyline/docblocks/filesystem`)
16
+
17
+ Pluggable filesystem abstraction layer with multiple storage backends.
18
+
19
+ - **`FileSystemProvider`** — Abstract interface for filesystem operations (`readFile`, `writeFile`, `readDirectory`, `delete`, `rename`, `createDirectory`, `stat`)
20
+ - **`IndexedDBFileSystemProvider`** — Browser-based persistent storage using IndexedDB
21
+ - **`NativeFileSystemProvider`** — Native filesystem access via the File System Access API
22
+ - **`IndexedDBContentContainer`** — Content management layer for media and document storage
23
+
24
+ ```ts
25
+ import { IndexedDBFileSystemProvider } from '@bendyline/docblocks/filesystem';
26
+
27
+ const fs = new IndexedDBFileSystemProvider('my-workspace');
28
+ await fs.writeFile('/doc.md', '# Hello');
29
+ const content = await fs.readFile('/doc.md');
30
+ ```
31
+
32
+ ### Workspace (`@bendyline/docblocks/workspace`)
33
+
34
+ Workspace management utilities for organizing document projects.
35
+
36
+ - **`listWorkspaces`** — List all known workspaces
37
+ - **`getWorkspace`** / **`saveWorkspace`** / **`removeWorkspace`** — CRUD operations
38
+ - **`touchWorkspace`** — Update last-opened timestamp
39
+ - **`ensureDefaultWorkspace`** — Create a default workspace if none exist
40
+
41
+ ```ts
42
+ import { listWorkspaces, ensureDefaultWorkspace } from '@bendyline/docblocks/workspace';
43
+
44
+ const workspaces = await listWorkspaces();
45
+ ```
46
+
47
+ ## License
48
+
49
+ MIT
@@ -0,0 +1,63 @@
1
+ // src/workspace/workspace-manager.ts
2
+ import { LocalForageAdapter } from "@bendyline/squisq/storage";
3
+ var DB_NAME = "docblocks-workspaces";
4
+ var STORE_NAME = "workspaces";
5
+ var LIST_KEY = "workspace-list";
6
+ var DEFAULT_WORKSPACE_ID = "default";
7
+ var store = new LocalForageAdapter({
8
+ name: DB_NAME,
9
+ storeName: STORE_NAME
10
+ });
11
+ async function listWorkspaces() {
12
+ const list = await store.get(LIST_KEY);
13
+ return list ?? [];
14
+ }
15
+ async function getWorkspace(id) {
16
+ const list = await listWorkspaces();
17
+ return list.find((w) => w.id === id) ?? null;
18
+ }
19
+ async function saveWorkspace(workspace) {
20
+ const list = await listWorkspaces();
21
+ const idx = list.findIndex((w) => w.id === workspace.id);
22
+ if (idx >= 0) {
23
+ list[idx] = workspace;
24
+ } else {
25
+ list.push(workspace);
26
+ }
27
+ await store.set(LIST_KEY, list);
28
+ }
29
+ async function removeWorkspace(id) {
30
+ const list = await listWorkspaces();
31
+ const filtered = list.filter((w) => w.id !== id);
32
+ await store.set(LIST_KEY, filtered);
33
+ }
34
+ async function touchWorkspace(id) {
35
+ const list = await listWorkspaces();
36
+ const workspace = list.find((w) => w.id === id);
37
+ if (workspace) {
38
+ workspace.lastOpened = (/* @__PURE__ */ new Date()).toISOString();
39
+ await store.set(LIST_KEY, list);
40
+ }
41
+ }
42
+ async function ensureDefaultWorkspace() {
43
+ const existing = await getWorkspace(DEFAULT_WORKSPACE_ID);
44
+ if (existing) return existing;
45
+ const descriptor = {
46
+ id: DEFAULT_WORKSPACE_ID,
47
+ name: "My Documents",
48
+ type: "indexeddb",
49
+ lastOpened: (/* @__PURE__ */ new Date()).toISOString()
50
+ };
51
+ await saveWorkspace(descriptor);
52
+ return descriptor;
53
+ }
54
+
55
+ export {
56
+ listWorkspaces,
57
+ getWorkspace,
58
+ saveWorkspace,
59
+ removeWorkspace,
60
+ touchWorkspace,
61
+ ensureDefaultWorkspace
62
+ };
63
+ //# sourceMappingURL=chunk-GUNM43XZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workspace/workspace-manager.ts"],"sourcesContent":["/**\n * WorkspaceManager — manages the list of known workspaces in IndexedDB\n * and provides helpers for creating / switching / removing them.\n *\n * The actual FileSystemProvider instances are created by the caller\n * (since native providers need a DirectoryHandle from user interaction).\n */\n\nimport { LocalForageAdapter } from '@bendyline/squisq/storage';\nimport type { WorkspaceDescriptor } from './types.js';\n\nconst DB_NAME = 'docblocks-workspaces';\nconst STORE_NAME = 'workspaces';\nconst LIST_KEY = 'workspace-list';\nconst DEFAULT_WORKSPACE_ID = 'default';\n\nconst store = new LocalForageAdapter({\n name: DB_NAME,\n storeName: STORE_NAME,\n});\n\n/**\n * Get the list of all known workspace descriptors.\n */\nexport async function listWorkspaces(): Promise<WorkspaceDescriptor[]> {\n const list = await store.get<WorkspaceDescriptor[]>(LIST_KEY);\n return list ?? [];\n}\n\n/**\n * Get a specific workspace descriptor by id.\n */\nexport async function getWorkspace(id: string): Promise<WorkspaceDescriptor | null> {\n const list = await listWorkspaces();\n return list.find((w) => w.id === id) ?? null;\n}\n\n/**\n * Add or update a workspace descriptor. If a workspace with the same id\n * already exists, it is replaced.\n */\nexport async function saveWorkspace(workspace: WorkspaceDescriptor): Promise<void> {\n const list = await listWorkspaces();\n const idx = list.findIndex((w) => w.id === workspace.id);\n if (idx >= 0) {\n list[idx] = workspace;\n } else {\n list.push(workspace);\n }\n await store.set(LIST_KEY, list);\n}\n\n/**\n * Remove a workspace descriptor by id.\n */\nexport async function removeWorkspace(id: string): Promise<void> {\n const list = await listWorkspaces();\n const filtered = list.filter((w) => w.id !== id);\n await store.set(LIST_KEY, filtered);\n}\n\n/**\n * Touch the lastOpened timestamp on a workspace.\n */\nexport async function touchWorkspace(id: string): Promise<void> {\n const list = await listWorkspaces();\n const workspace = list.find((w) => w.id === id);\n if (workspace) {\n workspace.lastOpened = new Date().toISOString();\n await store.set(LIST_KEY, list);\n }\n}\n\n/**\n * Ensure a default IndexedDB workspace exists. Called on app startup.\n * Returns the descriptor.\n */\nexport async function ensureDefaultWorkspace(): Promise<WorkspaceDescriptor> {\n const existing = await getWorkspace(DEFAULT_WORKSPACE_ID);\n if (existing) return existing;\n\n const descriptor: WorkspaceDescriptor = {\n id: DEFAULT_WORKSPACE_ID,\n name: 'My Documents',\n type: 'indexeddb',\n lastOpened: new Date().toISOString(),\n };\n await saveWorkspace(descriptor);\n return descriptor;\n}\n"],"mappings":";AAQA,SAAS,0BAA0B;AAGnC,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,uBAAuB;AAE7B,IAAM,QAAQ,IAAI,mBAAmB;AAAA,EACnC,MAAM;AAAA,EACN,WAAW;AACb,CAAC;AAKD,eAAsB,iBAAiD;AACrE,QAAM,OAAO,MAAM,MAAM,IAA2B,QAAQ;AAC5D,SAAO,QAAQ,CAAC;AAClB;AAKA,eAAsB,aAAa,IAAiD;AAClF,QAAM,OAAO,MAAM,eAAe;AAClC,SAAO,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AAC1C;AAMA,eAAsB,cAAc,WAA+C;AACjF,QAAM,OAAO,MAAM,eAAe;AAClC,QAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE;AACvD,MAAI,OAAO,GAAG;AACZ,SAAK,GAAG,IAAI;AAAA,EACd,OAAO;AACL,SAAK,KAAK,SAAS;AAAA,EACrB;AACA,QAAM,MAAM,IAAI,UAAU,IAAI;AAChC;AAKA,eAAsB,gBAAgB,IAA2B;AAC/D,QAAM,OAAO,MAAM,eAAe;AAClC,QAAM,WAAW,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAC/C,QAAM,MAAM,IAAI,UAAU,QAAQ;AACpC;AAKA,eAAsB,eAAe,IAA2B;AAC9D,QAAM,OAAO,MAAM,eAAe;AAClC,QAAM,YAAY,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC9C,MAAI,WAAW;AACb,cAAU,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC9C,UAAM,MAAM,IAAI,UAAU,IAAI;AAAA,EAChC;AACF;AAMA,eAAsB,yBAAuD;AAC3E,QAAM,WAAW,MAAM,aAAa,oBAAoB;AACxD,MAAI,SAAU,QAAO;AAErB,QAAM,aAAkC;AAAA,IACtC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC;AACA,QAAM,cAAc,UAAU;AAC9B,SAAO;AACT;","names":[]}
@@ -0,0 +1,559 @@
1
+ // src/filesystem/indexeddb-provider.ts
2
+ import { LocalForageAdapter } from "@bendyline/squisq/storage";
3
+ function normalisePath(p) {
4
+ return p.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "");
5
+ }
6
+ function parentDir(p) {
7
+ const idx = p.lastIndexOf("/");
8
+ return idx === -1 ? "" : p.slice(0, idx);
9
+ }
10
+ function baseName(p) {
11
+ const idx = p.lastIndexOf("/");
12
+ return idx === -1 ? p : p.slice(idx + 1);
13
+ }
14
+ function contentKey(path) {
15
+ return `fs:${path}:content`;
16
+ }
17
+ function binaryKey(path) {
18
+ return `fs:${path}:binary`;
19
+ }
20
+ function metaKey(path) {
21
+ return `fs:${path}:meta`;
22
+ }
23
+ var DIRS_KEY = "fs:dirs";
24
+ var IndexedDBFileSystemProvider = class {
25
+ constructor(id, label) {
26
+ this.id = id;
27
+ this.label = label;
28
+ this.store = new LocalForageAdapter({
29
+ name: `docblocks-fs-${id}`,
30
+ storeName: "files"
31
+ });
32
+ }
33
+ // ── Directory tracking ──────────────────────────────────────────
34
+ async getDirs() {
35
+ const raw = await this.store.get(DIRS_KEY);
36
+ return new Set(raw ?? []);
37
+ }
38
+ async saveDirs(dirs) {
39
+ await this.store.set(DIRS_KEY, [...dirs]);
40
+ }
41
+ /** Ensure a directory (and all ancestors) are tracked. */
42
+ async ensureDir(dirPath) {
43
+ if (!dirPath) return;
44
+ const dirs = await this.getDirs();
45
+ const parts = dirPath.split("/");
46
+ let current = "";
47
+ let changed = false;
48
+ for (const part of parts) {
49
+ current = current ? `${current}/${part}` : part;
50
+ if (!dirs.has(current)) {
51
+ dirs.add(current);
52
+ changed = true;
53
+ }
54
+ }
55
+ if (changed) {
56
+ await this.saveDirs(dirs);
57
+ }
58
+ }
59
+ // ── FileSystemProvider implementation ───────────────────────────
60
+ async readFile(path) {
61
+ const p = normalisePath(path);
62
+ return this.store.get(contentKey(p));
63
+ }
64
+ async writeFile(path, content) {
65
+ const p = normalisePath(path);
66
+ const parent = parentDir(p);
67
+ if (parent) {
68
+ await this.ensureDir(parent);
69
+ }
70
+ const meta = {
71
+ name: baseName(p),
72
+ path: p,
73
+ size: new Blob([content]).size,
74
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
75
+ };
76
+ await this.store.set(contentKey(p), content);
77
+ await this.store.set(metaKey(p), meta);
78
+ }
79
+ async delete(path) {
80
+ const p = normalisePath(path);
81
+ await this.store.remove(contentKey(p));
82
+ await this.store.remove(binaryKey(p));
83
+ await this.store.remove(metaKey(p));
84
+ const dirs = await this.getDirs();
85
+ if (dirs.has(p)) {
86
+ const prefix = p + "/";
87
+ const toRemove = [p];
88
+ for (const d of dirs) {
89
+ if (d.startsWith(prefix)) {
90
+ toRemove.push(d);
91
+ }
92
+ }
93
+ for (const d of toRemove) {
94
+ dirs.delete(d);
95
+ }
96
+ await this.saveDirs(dirs);
97
+ const allKeys = await this.store.keys();
98
+ const filePrefix = `fs:${p}/`;
99
+ const keysToRemove = allKeys.filter((k) => k.startsWith(filePrefix));
100
+ await Promise.all(keysToRemove.map((k) => this.store.remove(k)));
101
+ }
102
+ }
103
+ async rename(oldPath, newPath) {
104
+ const op = normalisePath(oldPath);
105
+ const np = normalisePath(newPath);
106
+ const content = await this.store.get(contentKey(op));
107
+ const binary = await this.store.get(binaryKey(op));
108
+ const meta = await this.store.get(metaKey(op));
109
+ if (content !== null) {
110
+ await this.store.set(contentKey(np), content);
111
+ }
112
+ if (binary !== null) {
113
+ await this.store.set(binaryKey(np), binary);
114
+ }
115
+ if (meta) {
116
+ meta.name = baseName(np);
117
+ meta.path = np;
118
+ await this.store.set(metaKey(np), meta);
119
+ }
120
+ const newParent = parentDir(np);
121
+ if (newParent) {
122
+ await this.ensureDir(newParent);
123
+ }
124
+ await this.store.remove(contentKey(op));
125
+ await this.store.remove(binaryKey(op));
126
+ await this.store.remove(metaKey(op));
127
+ const dirs = await this.getDirs();
128
+ if (dirs.has(op)) {
129
+ dirs.delete(op);
130
+ dirs.add(np);
131
+ const oldPrefix = op + "/";
132
+ const newPrefix = np + "/";
133
+ for (const d of [...dirs]) {
134
+ if (d.startsWith(oldPrefix)) {
135
+ dirs.delete(d);
136
+ dirs.add(newPrefix + d.slice(oldPrefix.length));
137
+ }
138
+ }
139
+ await this.saveDirs(dirs);
140
+ }
141
+ }
142
+ async readDirectory(path) {
143
+ const p = normalisePath(path);
144
+ const dirs = await this.getDirs();
145
+ const entries = [];
146
+ const seen = /* @__PURE__ */ new Set();
147
+ const prefix = p ? p + "/" : "";
148
+ for (const d of dirs) {
149
+ if (!p && !d.includes("/")) {
150
+ if (!seen.has(d)) {
151
+ seen.add(d);
152
+ entries.push({ kind: "directory", name: d, path: d });
153
+ }
154
+ } else if (p && d.startsWith(prefix)) {
155
+ const rest = d.slice(prefix.length);
156
+ if (!rest.includes("/")) {
157
+ if (!seen.has(rest)) {
158
+ seen.add(rest);
159
+ entries.push({ kind: "directory", name: rest, path: d });
160
+ }
161
+ }
162
+ }
163
+ }
164
+ const allKeys = await this.store.keys();
165
+ const metaPrefix = p ? `fs:${p}/` : "fs:";
166
+ const metaSuffix = ":meta";
167
+ for (const key of allKeys) {
168
+ if (!key.startsWith(metaPrefix) || !key.endsWith(metaSuffix)) continue;
169
+ const filePath = key.slice(3, -metaSuffix.length);
170
+ const rel = p ? filePath.slice(prefix.length) : filePath;
171
+ if (rel.includes("/")) continue;
172
+ if (!seen.has(rel)) {
173
+ seen.add(rel);
174
+ entries.push({ kind: "file", name: rel, path: filePath });
175
+ }
176
+ }
177
+ entries.sort((a, b) => {
178
+ if (a.kind !== b.kind) return a.kind === "directory" ? -1 : 1;
179
+ return a.name.localeCompare(b.name);
180
+ });
181
+ return entries;
182
+ }
183
+ async exists(path) {
184
+ const p = normalisePath(path);
185
+ const dirs = await this.getDirs();
186
+ if (dirs.has(p)) return true;
187
+ const meta = await this.store.get(metaKey(p));
188
+ return meta !== null;
189
+ }
190
+ async createDirectory(path) {
191
+ const p = normalisePath(path);
192
+ await this.ensureDir(p);
193
+ }
194
+ async stat(path) {
195
+ const p = normalisePath(path);
196
+ return this.store.get(metaKey(p));
197
+ }
198
+ async readBinary(path) {
199
+ const p = normalisePath(path);
200
+ return this.store.get(binaryKey(p));
201
+ }
202
+ async writeBinary(path, data) {
203
+ const p = normalisePath(path);
204
+ const parent = parentDir(p);
205
+ if (parent) {
206
+ await this.ensureDir(parent);
207
+ }
208
+ const meta = {
209
+ name: baseName(p),
210
+ path: p,
211
+ size: data.byteLength,
212
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
213
+ };
214
+ await this.store.set(binaryKey(p), data);
215
+ await this.store.set(metaKey(p), meta);
216
+ }
217
+ };
218
+
219
+ // src/filesystem/indexeddb-content-container.ts
220
+ import { findDocumentPath } from "@bendyline/squisq/storage";
221
+ var EXTENSION_MIME_MAP = {
222
+ ".md": "text/markdown",
223
+ ".txt": "text/plain",
224
+ ".json": "application/json",
225
+ ".jpg": "image/jpeg",
226
+ ".jpeg": "image/jpeg",
227
+ ".png": "image/png",
228
+ ".gif": "image/gif",
229
+ ".svg": "image/svg+xml",
230
+ ".webp": "image/webp",
231
+ ".avif": "image/avif",
232
+ ".mp4": "video/mp4",
233
+ ".webm": "video/webm",
234
+ ".mp3": "audio/mpeg",
235
+ ".wav": "audio/wav",
236
+ ".ogg": "audio/ogg"
237
+ };
238
+ function guessMimeType(path) {
239
+ const dot = path.lastIndexOf(".");
240
+ if (dot === -1) return "application/octet-stream";
241
+ const ext = path.slice(dot).toLowerCase();
242
+ return EXTENSION_MIME_MAP[ext] ?? "application/octet-stream";
243
+ }
244
+ var IndexedDBContentContainer = class {
245
+ constructor(workspaceId) {
246
+ this.provider = new IndexedDBFileSystemProvider(`${workspaceId}-media`, "Media Storage");
247
+ }
248
+ async readFile(path) {
249
+ return this.provider.readBinary(path);
250
+ }
251
+ async writeFile(path, data, _mimeType) {
252
+ await this.provider.writeBinary(path, data);
253
+ }
254
+ async removeFile(path) {
255
+ await this.provider.delete(path);
256
+ }
257
+ async listFiles(prefix) {
258
+ const entries = [];
259
+ const walk = async (dir) => {
260
+ const children = await this.provider.readDirectory(dir);
261
+ for (const child of children) {
262
+ if (child.kind === "directory") {
263
+ await walk(child.path);
264
+ } else {
265
+ const filePath = child.path.replace(/^\//, "");
266
+ if (prefix && !filePath.startsWith(prefix)) continue;
267
+ const meta = await this.provider.stat(child.path);
268
+ entries.push({
269
+ path: filePath,
270
+ mimeType: guessMimeType(filePath),
271
+ size: meta?.size ?? 0
272
+ });
273
+ }
274
+ }
275
+ };
276
+ await walk("/");
277
+ return entries;
278
+ }
279
+ async exists(path) {
280
+ return this.provider.exists(path);
281
+ }
282
+ async getDocumentPath() {
283
+ return findDocumentPath(await this.listFiles());
284
+ }
285
+ async readDocument() {
286
+ const docPath = await this.getDocumentPath();
287
+ if (!docPath) return null;
288
+ const data = await this.readFile(docPath);
289
+ if (!data) return null;
290
+ return new TextDecoder().decode(data);
291
+ }
292
+ async writeDocument(markdown, filename) {
293
+ const name = filename ?? "index.md";
294
+ const data = new TextEncoder().encode(markdown);
295
+ await this.writeFile(name, data, "text/markdown");
296
+ }
297
+ };
298
+
299
+ // src/filesystem/native-provider.ts
300
+ function isNativeFileSystemSupported() {
301
+ return typeof globalThis !== "undefined" && "showDirectoryPicker" in globalThis;
302
+ }
303
+ var HANDLE_DB_NAME = "docblocks-handles";
304
+ var HANDLE_STORE_NAME = "directory-handles";
305
+ function openHandleDB() {
306
+ return new Promise((resolve, reject) => {
307
+ const req = indexedDB.open(HANDLE_DB_NAME, 1);
308
+ req.onupgradeneeded = () => {
309
+ req.result.createObjectStore(HANDLE_STORE_NAME);
310
+ };
311
+ req.onsuccess = () => resolve(req.result);
312
+ req.onerror = () => reject(req.error);
313
+ });
314
+ }
315
+ async function storeDirectoryHandle(workspaceId, handle) {
316
+ const db = await openHandleDB();
317
+ return new Promise((resolve, reject) => {
318
+ const tx = db.transaction(HANDLE_STORE_NAME, "readwrite");
319
+ tx.objectStore(HANDLE_STORE_NAME).put(handle, workspaceId);
320
+ tx.oncomplete = () => {
321
+ db.close();
322
+ resolve();
323
+ };
324
+ tx.onerror = () => {
325
+ db.close();
326
+ reject(tx.error);
327
+ };
328
+ });
329
+ }
330
+ async function loadDirectoryHandle(workspaceId) {
331
+ const db = await openHandleDB();
332
+ return new Promise((resolve, reject) => {
333
+ const tx = db.transaction(HANDLE_STORE_NAME, "readonly");
334
+ const req = tx.objectStore(HANDLE_STORE_NAME).get(workspaceId);
335
+ req.onsuccess = () => {
336
+ db.close();
337
+ resolve(req.result ?? null);
338
+ };
339
+ req.onerror = () => {
340
+ db.close();
341
+ reject(req.error);
342
+ };
343
+ });
344
+ }
345
+ async function removeDirectoryHandle(workspaceId) {
346
+ const db = await openHandleDB();
347
+ return new Promise((resolve, reject) => {
348
+ const tx = db.transaction(HANDLE_STORE_NAME, "readwrite");
349
+ tx.objectStore(HANDLE_STORE_NAME).delete(workspaceId);
350
+ tx.oncomplete = () => {
351
+ db.close();
352
+ resolve();
353
+ };
354
+ tx.onerror = () => {
355
+ db.close();
356
+ reject(tx.error);
357
+ };
358
+ });
359
+ }
360
+ function normalisePath2(p) {
361
+ return p.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "");
362
+ }
363
+ async function resolveDir(root, dirPath) {
364
+ if (!dirPath) return root;
365
+ const parts = dirPath.split("/");
366
+ let current = root;
367
+ for (const part of parts) {
368
+ try {
369
+ current = await current.getDirectoryHandle(part);
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+ return current;
375
+ }
376
+ async function resolveDirCreate(root, dirPath) {
377
+ if (!dirPath) return root;
378
+ const parts = dirPath.split("/");
379
+ let current = root;
380
+ for (const part of parts) {
381
+ current = await current.getDirectoryHandle(part, { create: true });
382
+ }
383
+ return current;
384
+ }
385
+ function parentDir2(p) {
386
+ const idx = p.lastIndexOf("/");
387
+ return idx === -1 ? "" : p.slice(0, idx);
388
+ }
389
+ function baseName2(p) {
390
+ const idx = p.lastIndexOf("/");
391
+ return idx === -1 ? p : p.slice(idx + 1);
392
+ }
393
+ var NativeFileSystemProvider = class {
394
+ constructor(id, root) {
395
+ this.id = id;
396
+ this.label = root.name;
397
+ this.root = root;
398
+ }
399
+ async readFile(path) {
400
+ const p = normalisePath2(path);
401
+ const dir = await resolveDir(this.root, parentDir2(p));
402
+ if (!dir) return null;
403
+ try {
404
+ const fileHandle = await dir.getFileHandle(baseName2(p));
405
+ const file = await fileHandle.getFile();
406
+ return file.text();
407
+ } catch {
408
+ return null;
409
+ }
410
+ }
411
+ async writeFile(path, content) {
412
+ const p = normalisePath2(path);
413
+ const dir = await resolveDirCreate(this.root, parentDir2(p));
414
+ const fileHandle = await dir.getFileHandle(baseName2(p), { create: true });
415
+ const writable = await fileHandle.createWritable();
416
+ await writable.write(content);
417
+ await writable.close();
418
+ }
419
+ async delete(path) {
420
+ const p = normalisePath2(path);
421
+ const parent = parentDir2(p);
422
+ const name = baseName2(p);
423
+ const dir = await resolveDir(this.root, parent);
424
+ if (!dir) return;
425
+ await dir.removeEntry(name, { recursive: true });
426
+ }
427
+ async rename(oldPath, newPath) {
428
+ const op = normalisePath2(oldPath);
429
+ const np = normalisePath2(newPath);
430
+ const content = await this.readFile(op);
431
+ if (content !== null) {
432
+ await this.writeFile(np, content);
433
+ await this.delete(op);
434
+ return;
435
+ }
436
+ const binary = await this.readBinary(op);
437
+ if (binary !== null) {
438
+ await this.writeBinary(np, binary);
439
+ await this.delete(op);
440
+ }
441
+ }
442
+ async readDirectory(path) {
443
+ const p = normalisePath2(path);
444
+ const dir = await resolveDir(this.root, p);
445
+ if (!dir) return [];
446
+ const entries = [];
447
+ for await (const [name, handle] of dir) {
448
+ const entryPath = p ? `${p}/${name}` : name;
449
+ if (handle.kind === "directory") {
450
+ entries.push({ kind: "directory", name, path: entryPath });
451
+ } else {
452
+ entries.push({ kind: "file", name, path: entryPath });
453
+ }
454
+ }
455
+ entries.sort((a, b) => {
456
+ if (a.kind !== b.kind) return a.kind === "directory" ? -1 : 1;
457
+ return a.name.localeCompare(b.name);
458
+ });
459
+ return entries;
460
+ }
461
+ async exists(path) {
462
+ const p = normalisePath2(path);
463
+ const parent = parentDir2(p);
464
+ const name = baseName2(p);
465
+ const dir = await resolveDir(this.root, parent);
466
+ if (!dir) return false;
467
+ try {
468
+ await dir.getFileHandle(name);
469
+ return true;
470
+ } catch {
471
+ try {
472
+ await dir.getDirectoryHandle(name);
473
+ return true;
474
+ } catch {
475
+ return false;
476
+ }
477
+ }
478
+ }
479
+ async createDirectory(path) {
480
+ const p = normalisePath2(path);
481
+ await resolveDirCreate(this.root, p);
482
+ }
483
+ async stat(path) {
484
+ const p = normalisePath2(path);
485
+ const dir = await resolveDir(this.root, parentDir2(p));
486
+ if (!dir) return null;
487
+ try {
488
+ const fileHandle = await dir.getFileHandle(baseName2(p));
489
+ const file = await fileHandle.getFile();
490
+ return {
491
+ name: file.name,
492
+ path: p,
493
+ size: file.size,
494
+ lastModified: new Date(file.lastModified).toISOString()
495
+ };
496
+ } catch {
497
+ return null;
498
+ }
499
+ }
500
+ async readBinary(path) {
501
+ const p = normalisePath2(path);
502
+ const dir = await resolveDir(this.root, parentDir2(p));
503
+ if (!dir) return null;
504
+ try {
505
+ const fileHandle = await dir.getFileHandle(baseName2(p));
506
+ const file = await fileHandle.getFile();
507
+ return file.arrayBuffer();
508
+ } catch {
509
+ return null;
510
+ }
511
+ }
512
+ async writeBinary(path, data) {
513
+ const p = normalisePath2(path);
514
+ const dir = await resolveDirCreate(this.root, parentDir2(p));
515
+ const fileHandle = await dir.getFileHandle(baseName2(p), { create: true });
516
+ const writable = await fileHandle.createWritable();
517
+ if (data instanceof ArrayBuffer) {
518
+ await writable.write(data);
519
+ } else {
520
+ await writable.write(data.buffer);
521
+ }
522
+ await writable.close();
523
+ }
524
+ };
525
+ async function openNativeFolder() {
526
+ if (!isNativeFileSystemSupported()) {
527
+ throw new Error("File System Access API is not supported in this browser");
528
+ }
529
+ const handle = await globalThis.showDirectoryPicker();
530
+ const id = `native-${handle.name}-${Date.now()}`;
531
+ await storeDirectoryHandle(id, handle);
532
+ return new NativeFileSystemProvider(id, handle);
533
+ }
534
+ async function restoreNativeFolder(workspaceId) {
535
+ const handle = await loadDirectoryHandle(workspaceId);
536
+ if (!handle) return null;
537
+ const opts = { mode: "readwrite" };
538
+ const h = handle;
539
+ if (await h.queryPermission(opts) === "granted") {
540
+ return new NativeFileSystemProvider(workspaceId, handle);
541
+ }
542
+ if (await h.requestPermission(opts) === "granted") {
543
+ return new NativeFileSystemProvider(workspaceId, handle);
544
+ }
545
+ return null;
546
+ }
547
+
548
+ export {
549
+ IndexedDBFileSystemProvider,
550
+ IndexedDBContentContainer,
551
+ isNativeFileSystemSupported,
552
+ storeDirectoryHandle,
553
+ loadDirectoryHandle,
554
+ removeDirectoryHandle,
555
+ NativeFileSystemProvider,
556
+ openNativeFolder,
557
+ restoreNativeFolder
558
+ };
559
+ //# sourceMappingURL=chunk-NSVTXALR.js.map