@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.
Files changed (270) hide show
  1. package/README.md +76 -11
  2. package/dist/chunk-2D7Y4IR3.js +1293 -0
  3. package/dist/chunk-2D7Y4IR3.js.map +1 -0
  4. package/dist/chunk-5MYAICRE.js +957 -0
  5. package/dist/chunk-5MYAICRE.js.map +1 -0
  6. package/dist/chunk-75UUF6C4.js +802 -0
  7. package/dist/chunk-75UUF6C4.js.map +1 -0
  8. package/dist/chunk-AEHCVWU5.js +25 -0
  9. package/dist/chunk-AEHCVWU5.js.map +1 -0
  10. package/dist/chunk-AYZ4HN5M.js +20 -0
  11. package/dist/chunk-AYZ4HN5M.js.map +1 -0
  12. package/dist/chunk-EGNDADXB.js +111 -0
  13. package/dist/chunk-EGNDADXB.js.map +1 -0
  14. package/dist/chunk-EZKJZUNJ.js +13 -0
  15. package/dist/chunk-EZKJZUNJ.js.map +1 -0
  16. package/dist/chunk-FNW556FN.js +75 -0
  17. package/dist/chunk-FNW556FN.js.map +1 -0
  18. package/dist/chunk-GCPBEMMH.js +563 -0
  19. package/dist/chunk-GCPBEMMH.js.map +1 -0
  20. package/dist/chunk-J32GU3F3.js +1664 -0
  21. package/dist/chunk-J32GU3F3.js.map +1 -0
  22. package/dist/chunk-PGR7LK6D.js +1062 -0
  23. package/dist/chunk-PGR7LK6D.js.map +1 -0
  24. package/dist/chunk-RQJRQF46.js +787 -0
  25. package/dist/chunk-RQJRQF46.js.map +1 -0
  26. package/dist/{chunk-ME76RUMR.js → chunk-RVQHY3XU.js} +15 -1
  27. package/dist/chunk-RVQHY3XU.js.map +1 -0
  28. package/dist/chunk-SI2LU2RP.js +224 -0
  29. package/dist/chunk-SI2LU2RP.js.map +1 -0
  30. package/dist/chunk-W5UUUII5.js +142 -0
  31. package/dist/chunk-W5UUUII5.js.map +1 -0
  32. package/dist/document/document-session.d.ts +127 -0
  33. package/dist/document/document-session.d.ts.map +1 -0
  34. package/dist/document/document-session.js +749 -0
  35. package/dist/document/document-session.js.map +1 -0
  36. package/dist/document/filesystem-target.d.ts +17 -0
  37. package/dist/document/filesystem-target.d.ts.map +1 -0
  38. package/dist/document/filesystem-target.js +70 -0
  39. package/dist/document/filesystem-target.js.map +1 -0
  40. package/dist/document/index.d.ts +6 -0
  41. package/dist/document/index.d.ts.map +1 -0
  42. package/dist/document/index.js +4 -0
  43. package/dist/document/index.js.map +1 -0
  44. package/dist/document/recovery-journal.d.ts +125 -0
  45. package/dist/document/recovery-journal.d.ts.map +1 -0
  46. package/dist/document/recovery-journal.js +385 -0
  47. package/dist/document/recovery-journal.js.map +1 -0
  48. package/dist/document/types.d.ts +89 -0
  49. package/dist/document/types.d.ts.map +1 -0
  50. package/dist/document/types.js +9 -0
  51. package/dist/document/types.js.map +1 -0
  52. package/dist/filesystem/commit-lock.d.ts +3 -0
  53. package/dist/filesystem/commit-lock.d.ts.map +1 -0
  54. package/dist/filesystem/commit-lock.js +20 -0
  55. package/dist/filesystem/commit-lock.js.map +1 -0
  56. package/dist/filesystem/dbk-workspace.d.ts +34 -0
  57. package/dist/filesystem/dbk-workspace.d.ts.map +1 -0
  58. package/dist/filesystem/dbk-workspace.js +85 -0
  59. package/dist/filesystem/dbk-workspace.js.map +1 -0
  60. package/dist/filesystem/electron-provider-v2.d.ts +36 -0
  61. package/dist/filesystem/electron-provider-v2.d.ts.map +1 -0
  62. package/dist/filesystem/electron-provider-v2.js +503 -0
  63. package/dist/filesystem/electron-provider-v2.js.map +1 -0
  64. package/dist/filesystem/electron-provider.d.ts +6 -3
  65. package/dist/filesystem/electron-provider.d.ts.map +1 -1
  66. package/dist/filesystem/electron-provider.js +18 -13
  67. package/dist/filesystem/electron-provider.js.map +1 -1
  68. package/dist/filesystem/electron.d.ts +4 -0
  69. package/dist/filesystem/electron.d.ts.map +1 -0
  70. package/dist/filesystem/electron.js +4 -0
  71. package/dist/filesystem/electron.js.map +1 -0
  72. package/dist/filesystem/filesystem-content-container.d.ts +1 -1
  73. package/dist/filesystem/filesystem-content-container.d.ts.map +1 -1
  74. package/dist/filesystem/filesystem-content-container.js +50 -13
  75. package/dist/filesystem/filesystem-content-container.js.map +1 -1
  76. package/dist/filesystem/fs-error.d.ts +46 -0
  77. package/dist/filesystem/fs-error.d.ts.map +1 -0
  78. package/dist/filesystem/fs-error.js +170 -0
  79. package/dist/filesystem/fs-error.js.map +1 -0
  80. package/dist/filesystem/index.d.ts +15 -1
  81. package/dist/filesystem/index.d.ts.map +1 -1
  82. package/dist/filesystem/index.js +13 -0
  83. package/dist/filesystem/index.js.map +1 -1
  84. package/dist/filesystem/indexeddb-content-container.d.ts +3 -1
  85. package/dist/filesystem/indexeddb-content-container.d.ts.map +1 -1
  86. package/dist/filesystem/indexeddb-content-container.js +18 -11
  87. package/dist/filesystem/indexeddb-content-container.js.map +1 -1
  88. package/dist/filesystem/indexeddb-provider-v2.d.ts +96 -0
  89. package/dist/filesystem/indexeddb-provider-v2.d.ts.map +1 -0
  90. package/dist/filesystem/indexeddb-provider-v2.js +1241 -0
  91. package/dist/filesystem/indexeddb-provider-v2.js.map +1 -0
  92. package/dist/filesystem/indexeddb-provider.d.ts +21 -14
  93. package/dist/filesystem/indexeddb-provider.d.ts.map +1 -1
  94. package/dist/filesystem/indexeddb-provider.js +111 -261
  95. package/dist/filesystem/indexeddb-provider.js.map +1 -1
  96. package/dist/filesystem/indexeddb-store.d.ts +34 -0
  97. package/dist/filesystem/indexeddb-store.d.ts.map +1 -0
  98. package/dist/filesystem/indexeddb-store.js +165 -0
  99. package/dist/filesystem/indexeddb-store.js.map +1 -0
  100. package/dist/filesystem/indexeddb.d.ts +5 -0
  101. package/dist/filesystem/indexeddb.d.ts.map +1 -0
  102. package/dist/filesystem/indexeddb.js +5 -0
  103. package/dist/filesystem/indexeddb.js.map +1 -0
  104. package/dist/filesystem/legacy-v2-adapter.d.ts +62 -0
  105. package/dist/filesystem/legacy-v2-adapter.d.ts.map +1 -0
  106. package/dist/filesystem/legacy-v2-adapter.js +499 -0
  107. package/dist/filesystem/legacy-v2-adapter.js.map +1 -0
  108. package/dist/filesystem/memory-provider-v2.d.ts +92 -0
  109. package/dist/filesystem/memory-provider-v2.d.ts.map +1 -0
  110. package/dist/filesystem/memory-provider-v2.js +615 -0
  111. package/dist/filesystem/memory-provider-v2.js.map +1 -0
  112. package/dist/filesystem/memory-provider.d.ts +54 -0
  113. package/dist/filesystem/memory-provider.d.ts.map +1 -0
  114. package/dist/filesystem/memory-provider.js +132 -0
  115. package/dist/filesystem/memory-provider.js.map +1 -0
  116. package/dist/filesystem/memory.d.ts +4 -0
  117. package/dist/filesystem/memory.d.ts.map +1 -0
  118. package/dist/filesystem/memory.js +4 -0
  119. package/dist/filesystem/memory.js.map +1 -0
  120. package/dist/filesystem/move-entry.d.ts +9 -0
  121. package/dist/filesystem/move-entry.d.ts.map +1 -0
  122. package/dist/filesystem/move-entry.js +103 -0
  123. package/dist/filesystem/move-entry.js.map +1 -0
  124. package/dist/filesystem/move-error.d.ts +53 -0
  125. package/dist/filesystem/move-error.d.ts.map +1 -0
  126. package/dist/filesystem/move-error.js +99 -0
  127. package/dist/filesystem/move-error.js.map +1 -0
  128. package/dist/filesystem/native-provider-v2.d.ts +75 -0
  129. package/dist/filesystem/native-provider-v2.d.ts.map +1 -0
  130. package/dist/filesystem/native-provider-v2.js +849 -0
  131. package/dist/filesystem/native-provider-v2.js.map +1 -0
  132. package/dist/filesystem/native-provider.d.ts +9 -1
  133. package/dist/filesystem/native-provider.d.ts.map +1 -1
  134. package/dist/filesystem/native-provider.js +172 -37
  135. package/dist/filesystem/native-provider.js.map +1 -1
  136. package/dist/filesystem/native.d.ts +4 -0
  137. package/dist/filesystem/native.d.ts.map +1 -0
  138. package/dist/filesystem/native.js +4 -0
  139. package/dist/filesystem/native.js.map +1 -0
  140. package/dist/filesystem/types.d.ts +37 -4
  141. package/dist/filesystem/types.d.ts.map +1 -1
  142. package/dist/filesystem/types.js +11 -3
  143. package/dist/filesystem/types.js.map +1 -1
  144. package/dist/filesystem/v2.d.ts +172 -0
  145. package/dist/filesystem/v2.d.ts.map +1 -0
  146. package/dist/filesystem/v2.js +16 -0
  147. package/dist/filesystem/v2.js.map +1 -0
  148. package/dist/filesystem/workspace-path.d.ts +21 -0
  149. package/dist/filesystem/workspace-path.d.ts.map +1 -0
  150. package/dist/filesystem/workspace-path.js +80 -0
  151. package/dist/filesystem/workspace-path.js.map +1 -0
  152. package/dist/host/filesystem-v2.d.ts +45 -0
  153. package/dist/host/filesystem-v2.d.ts.map +1 -0
  154. package/dist/host/filesystem-v2.js +13 -0
  155. package/dist/host/filesystem-v2.js.map +1 -0
  156. package/dist/host/git.d.ts +200 -0
  157. package/dist/host/git.d.ts.map +1 -0
  158. package/dist/host/git.js +15 -0
  159. package/dist/host/git.js.map +1 -0
  160. package/dist/host/index.d.ts +4 -1
  161. package/dist/host/index.d.ts.map +1 -1
  162. package/dist/host/index.js +2 -0
  163. package/dist/host/index.js.map +1 -1
  164. package/dist/host/types.d.ts +114 -31
  165. package/dist/host/types.d.ts.map +1 -1
  166. package/dist/host/wire-policy.d.ts +22 -0
  167. package/dist/host/wire-policy.d.ts.map +1 -0
  168. package/dist/host/wire-policy.js +92 -0
  169. package/dist/host/wire-policy.js.map +1 -0
  170. package/dist/index.d.ts +2 -1
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +2 -1
  173. package/dist/index.js.map +1 -1
  174. package/dist/mcp/index.d.ts +4 -0
  175. package/dist/mcp/index.d.ts.map +1 -0
  176. package/dist/mcp/index.js +4 -0
  177. package/dist/mcp/index.js.map +1 -0
  178. package/dist/mcp/types.d.ts +466 -0
  179. package/dist/mcp/types.d.ts.map +1 -0
  180. package/dist/mcp/types.js +25 -0
  181. package/dist/mcp/types.js.map +1 -0
  182. package/dist/mcp/wire.d.ts +29 -0
  183. package/dist/mcp/wire.d.ts.map +1 -0
  184. package/dist/mcp/wire.js +1046 -0
  185. package/dist/mcp/wire.js.map +1 -0
  186. package/dist/mcp/zod.d.ts +5763 -0
  187. package/dist/mcp/zod.d.ts.map +1 -0
  188. package/dist/mcp/zod.js +908 -0
  189. package/dist/mcp/zod.js.map +1 -0
  190. package/dist/types-CX893hgH.d.ts +468 -0
  191. package/dist/types-CsX6EN2t.d.ts +301 -0
  192. package/dist/types-eOlt9J7n.d.ts +478 -0
  193. package/dist/vscode/index.d.ts +3 -0
  194. package/dist/vscode/index.d.ts.map +1 -0
  195. package/dist/vscode/index.js +3 -0
  196. package/dist/vscode/index.js.map +1 -0
  197. package/dist/vscode/messages.d.ts +204 -0
  198. package/dist/vscode/messages.d.ts.map +1 -0
  199. package/dist/vscode/messages.js +494 -0
  200. package/dist/vscode/messages.js.map +1 -0
  201. package/dist/workspace/index.d.ts +3 -2
  202. package/dist/workspace/index.d.ts.map +1 -1
  203. package/dist/workspace/index.js +2 -1
  204. package/dist/workspace/index.js.map +1 -1
  205. package/dist/workspace/reconcile-electron.d.ts +19 -0
  206. package/dist/workspace/reconcile-electron.d.ts.map +1 -0
  207. package/dist/workspace/reconcile-electron.js +42 -0
  208. package/dist/workspace/reconcile-electron.js.map +1 -0
  209. package/dist/workspace/types.d.ts +41 -1
  210. package/dist/workspace/types.d.ts.map +1 -1
  211. package/dist/workspace/types.js +0 -4
  212. package/dist/workspace/types.js.map +1 -1
  213. package/dist/workspace/workspace-manager.d.ts +17 -5
  214. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  215. package/dist/workspace/workspace-manager.js +55 -11
  216. package/dist/workspace/workspace-manager.js.map +1 -1
  217. package/dist/workspace-path-CWVrcuPL.d.ts +21 -0
  218. package/package.json +43 -2
  219. package/src/document/document-session.ts +834 -0
  220. package/src/document/filesystem-target.ts +109 -0
  221. package/src/document/index.ts +41 -0
  222. package/src/document/recovery-journal.ts +536 -0
  223. package/src/document/types.ts +116 -0
  224. package/src/filesystem/commit-lock.ts +29 -0
  225. package/src/filesystem/dbk-workspace.ts +135 -0
  226. package/src/filesystem/electron-provider-v2.ts +630 -0
  227. package/src/filesystem/electron-provider.ts +25 -14
  228. package/src/filesystem/electron.ts +3 -0
  229. package/src/filesystem/filesystem-content-container.ts +53 -15
  230. package/src/filesystem/fs-error.ts +234 -0
  231. package/src/filesystem/index.ts +119 -0
  232. package/src/filesystem/indexeddb-content-container.ts +20 -12
  233. package/src/filesystem/indexeddb-provider-v2.ts +1740 -0
  234. package/src/filesystem/indexeddb-provider.ts +126 -300
  235. package/src/filesystem/indexeddb-store.ts +207 -0
  236. package/src/filesystem/indexeddb.ts +12 -0
  237. package/src/filesystem/legacy-v2-adapter.ts +779 -0
  238. package/src/filesystem/memory-provider-v2.ts +896 -0
  239. package/src/filesystem/memory-provider.ts +180 -0
  240. package/src/filesystem/memory.ts +15 -0
  241. package/src/filesystem/move-entry.ts +142 -0
  242. package/src/filesystem/move-error.ts +159 -0
  243. package/src/filesystem/native-provider-v2.ts +1165 -0
  244. package/src/filesystem/native-provider.ts +194 -41
  245. package/src/filesystem/native.ts +16 -0
  246. package/src/filesystem/types.ts +49 -4
  247. package/src/filesystem/v2.ts +231 -0
  248. package/src/filesystem/workspace-path.ts +92 -0
  249. package/src/host/filesystem-v2.ts +104 -0
  250. package/src/host/git.ts +246 -0
  251. package/src/host/index.ts +46 -0
  252. package/src/host/types.ts +155 -34
  253. package/src/host/wire-policy.ts +103 -0
  254. package/src/index.ts +2 -1
  255. package/src/mcp/index.ts +3 -0
  256. package/src/mcp/types.ts +594 -0
  257. package/src/mcp/wire.ts +1280 -0
  258. package/src/mcp/zod.ts +1000 -0
  259. package/src/vscode/index.ts +2 -0
  260. package/src/vscode/messages.ts +704 -0
  261. package/src/workspace/index.ts +8 -1
  262. package/src/workspace/reconcile-electron.ts +65 -0
  263. package/src/workspace/types.ts +39 -1
  264. package/src/workspace/workspace-manager.ts +71 -10
  265. package/dist/chunk-GUNM43XZ.js +0 -63
  266. package/dist/chunk-GUNM43XZ.js.map +0 -1
  267. package/dist/chunk-IM7LCQJ6.js +0 -850
  268. package/dist/chunk-IM7LCQJ6.js.map +0 -1
  269. package/dist/chunk-ME76RUMR.js.map +0 -1
  270. package/dist/index-pgBHBQ9G.d.ts +0 -213
@@ -0,0 +1,1664 @@
1
+ import {
2
+ withFileCommitLock
3
+ } from "./chunk-AEHCVWU5.js";
4
+ import {
5
+ parseFileSystemVersion
6
+ } from "./chunk-AYZ4HN5M.js";
7
+ import {
8
+ FsError,
9
+ WORKSPACE_ROOT,
10
+ fsErrorFromUnknown,
11
+ parseWorkspacePath,
12
+ workspacePathBasename,
13
+ workspacePathContains,
14
+ workspacePathDirname
15
+ } from "./chunk-SI2LU2RP.js";
16
+
17
+ // src/filesystem/indexeddb-store.ts
18
+ var RawIndexedDBFileSystemStore = class {
19
+ constructor(databaseName, storeName) {
20
+ this.databaseName = databaseName;
21
+ this.storeName = storeName;
22
+ this.database = null;
23
+ this.opening = null;
24
+ this.closed = false;
25
+ }
26
+ async transaction(mode, operation) {
27
+ const database = await this.getDatabase();
28
+ const nativeTransaction = database.transaction(this.storeName, mode);
29
+ const completion = waitForTransaction(nativeTransaction);
30
+ const transaction = new RawIndexedDBFileSystemTransaction(
31
+ nativeTransaction.objectStore(this.storeName)
32
+ );
33
+ let result;
34
+ try {
35
+ result = await operation(transaction);
36
+ } catch (error) {
37
+ try {
38
+ nativeTransaction.abort();
39
+ } catch {
40
+ }
41
+ await completion.catch(() => void 0);
42
+ throw error;
43
+ }
44
+ await completion;
45
+ return result;
46
+ }
47
+ close() {
48
+ if (this.closed) return;
49
+ this.closed = true;
50
+ this.database?.close();
51
+ this.database = null;
52
+ this.opening = null;
53
+ }
54
+ getDatabase() {
55
+ if (this.closed) {
56
+ return Promise.reject(new Error("The IndexedDB filesystem store is closed."));
57
+ }
58
+ if (this.database) return Promise.resolve(this.database);
59
+ if (this.opening) return this.opening;
60
+ if (typeof indexedDB === "undefined") {
61
+ return Promise.reject(new Error("IndexedDB is not available in this environment."));
62
+ }
63
+ this.opening = openDatabaseWithStore(this.databaseName, this.storeName).then((database) => {
64
+ if (this.closed) {
65
+ database.close();
66
+ throw new Error("The IndexedDB filesystem store is closed.");
67
+ }
68
+ this.database = database;
69
+ this.opening = null;
70
+ database.onversionchange = () => {
71
+ database.close();
72
+ if (this.database === database) this.database = null;
73
+ };
74
+ return database;
75
+ }).catch((error) => {
76
+ this.opening = null;
77
+ throw error;
78
+ });
79
+ return this.opening;
80
+ }
81
+ };
82
+ var RawIndexedDBFileSystemTransaction = class {
83
+ constructor(store) {
84
+ this.store = store;
85
+ }
86
+ async get(key) {
87
+ const value = await requestResult(this.store.get(key));
88
+ return value === void 0 ? null : value;
89
+ }
90
+ async put(key, value) {
91
+ await requestResult(this.store.put(value, key));
92
+ }
93
+ async delete(key) {
94
+ await requestResult(this.store.delete(key));
95
+ }
96
+ async keys() {
97
+ const keys = await requestResult(this.store.getAllKeys());
98
+ return keys.map((key) => {
99
+ if (typeof key !== "string") {
100
+ throw new Error("The DocBlocks filesystem store contains a non-string key.");
101
+ }
102
+ return key;
103
+ });
104
+ }
105
+ async hasKeyWithPrefix(prefix) {
106
+ const cursor = await requestResult(this.store.openKeyCursor(IDBKeyRange.lowerBound(prefix)));
107
+ return cursor !== null && typeof cursor.key === "string" && cursor.key.startsWith(prefix);
108
+ }
109
+ };
110
+ async function openDatabaseWithStore(databaseName, storeName) {
111
+ const database = await openDatabase(databaseName, void 0, (upgradeDatabase) => {
112
+ if (!upgradeDatabase.objectStoreNames.contains(storeName)) {
113
+ upgradeDatabase.createObjectStore(storeName);
114
+ }
115
+ });
116
+ if (database.objectStoreNames.contains(storeName)) return database;
117
+ const nextVersion = database.version + 1;
118
+ database.close();
119
+ return openDatabase(databaseName, nextVersion, (upgradeDatabase) => {
120
+ if (!upgradeDatabase.objectStoreNames.contains(storeName)) {
121
+ upgradeDatabase.createObjectStore(storeName);
122
+ }
123
+ });
124
+ }
125
+ function openDatabase(databaseName, version, upgrade) {
126
+ return new Promise((resolve, reject) => {
127
+ const request = version === void 0 ? indexedDB.open(databaseName) : indexedDB.open(databaseName, version);
128
+ let settled = false;
129
+ request.onupgradeneeded = () => upgrade(request.result);
130
+ request.onsuccess = () => {
131
+ if (settled) {
132
+ request.result.close();
133
+ return;
134
+ }
135
+ settled = true;
136
+ resolve(request.result);
137
+ };
138
+ request.onerror = () => {
139
+ if (settled) return;
140
+ settled = true;
141
+ reject(request.error ?? new Error(`Could not open IndexedDB database ${databaseName}.`));
142
+ };
143
+ request.onblocked = () => {
144
+ if (settled) return;
145
+ settled = true;
146
+ reject(new Error(`IndexedDB database ${databaseName} is blocked by another open page.`));
147
+ };
148
+ });
149
+ }
150
+ function requestResult(request) {
151
+ return new Promise((resolve, reject) => {
152
+ request.onsuccess = () => resolve(request.result);
153
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed."));
154
+ });
155
+ }
156
+ function waitForTransaction(transaction) {
157
+ return new Promise((resolve, reject) => {
158
+ transaction.oncomplete = () => resolve();
159
+ transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB transaction was aborted."));
160
+ transaction.onerror = () => reject(transaction.error ?? new Error("IndexedDB transaction failed."));
161
+ });
162
+ }
163
+
164
+ // src/filesystem/indexeddb-provider-v2.ts
165
+ var ENTRY_SCHEMA_VERSION = 1;
166
+ var STATE_SCHEMA_VERSION = 1;
167
+ var LEGACY_CONFLICT_SCHEMA_VERSION = 2;
168
+ var LEGACY_CONFLICT_SCHEMA_VERSION_V1 = 1;
169
+ var ENTRY_PREFIX = "v2:entry:";
170
+ var STATE_KEY = "v2:state";
171
+ var LEGACY_CONFLICT_PREFIX = "v2:legacy-conflict:";
172
+ var LEGACY_IGNORED_PREFIX = "v2:legacy-ignored:";
173
+ var LEGACY_OPAQUE_PREFIX = "v2:legacy-opaque:";
174
+ var LEGACY_DIRS_KEY = "fs:dirs";
175
+ var LEGACY_PREFIX = "fs:";
176
+ var LEGACY_CONTENT_SUFFIX = ":content";
177
+ var LEGACY_BINARY_SUFFIX = ":binary";
178
+ var LEGACY_META_SUFFIX = ":meta";
179
+ var LegacyNamespaceDetectedError = class extends Error {
180
+ };
181
+ var INDEXEDDB_V2_CAPABILITIES = Object.freeze({
182
+ writeAtomicity: "cross-context",
183
+ moveAtomicity: "cross-context",
184
+ snapshotAtomicity: "cross-context",
185
+ conditionalWrite: "storage-atomic",
186
+ recursiveRemove: true,
187
+ watch: false,
188
+ caseSensitivity: "sensitive",
189
+ symlinkPolicy: "unsupported",
190
+ durability: "best-effort"
191
+ });
192
+ var IndexedDBFileSystemProviderV2 = class {
193
+ constructor(id, label, options = {}) {
194
+ this.capabilities = INDEXEDDB_V2_CAPABILITIES;
195
+ this.subscriptions = /* @__PURE__ */ new Set();
196
+ this.inFlight = /* @__PURE__ */ new Set();
197
+ this.initialization = null;
198
+ this.disposePromise = null;
199
+ this.storeClosed = false;
200
+ this.disposed = false;
201
+ this.id = id;
202
+ this.label = label;
203
+ this.store = options.store ?? new RawIndexedDBFileSystemStore(`docblocks-fs-${id}`, "files");
204
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
205
+ this.closeStoreOnDispose = options.closeStoreOnDispose ?? true;
206
+ this.versionPrefix = `idb-${encodeURIComponent(id).slice(0, 900) || "workspace"}`;
207
+ }
208
+ /** Track accepted storage work so disposal can join it before closing. */
209
+ runSharedTransaction(mode, operation, work) {
210
+ this.assertOpen(operation);
211
+ const transaction = this.store.transaction(mode, work);
212
+ this.inFlight.add(transaction);
213
+ void transaction.then(
214
+ () => this.inFlight.delete(transaction),
215
+ () => this.inFlight.delete(transaction)
216
+ );
217
+ return transaction;
218
+ }
219
+ async stat(path) {
220
+ this.assertOpen("stat");
221
+ const canonical = parseWorkspacePath(path);
222
+ await this.ensureInitialized("stat");
223
+ return this.transaction("readonly", { operation: "stat", path: canonical }, async (tx) => {
224
+ const entry = await readEntry(tx, canonical);
225
+ return entry ? toEntrySnapshot(entry) : null;
226
+ });
227
+ }
228
+ async readFile(path) {
229
+ this.assertOpen("read");
230
+ const canonical = parseWorkspacePath(path);
231
+ await this.ensureInitialized("read");
232
+ return this.transaction("readonly", { operation: "read", path: canonical }, async (tx) => {
233
+ const entry = await readEntry(tx, canonical);
234
+ if (!entry) return null;
235
+ if (entry.kind !== "file") {
236
+ throw this.error(
237
+ "type-mismatch",
238
+ "read",
239
+ canonical,
240
+ "Expected a file but found a directory."
241
+ );
242
+ }
243
+ return Object.freeze({ entry: toFileSnapshot(entry), data: copyBytes(entry.data) });
244
+ });
245
+ }
246
+ async readDirectory(path) {
247
+ this.assertOpen("list");
248
+ const canonical = parseWorkspacePath(path);
249
+ await this.ensureInitialized("list");
250
+ return this.transaction("readonly", { operation: "list", path: canonical }, async (tx) => {
251
+ const directory = await readEntry(tx, canonical);
252
+ if (!directory) {
253
+ throw this.error("not-found", "list", canonical, "Directory does not exist.");
254
+ }
255
+ if (directory.kind !== "directory") {
256
+ throw this.error(
257
+ "type-mismatch",
258
+ "list",
259
+ canonical,
260
+ "Expected a directory but found a file."
261
+ );
262
+ }
263
+ const paths = await listEntryPaths(tx);
264
+ const children = [];
265
+ for (const candidate of paths) {
266
+ if (candidate && workspacePathDirname(candidate) === canonical) {
267
+ const entry = await readRequiredEntry(tx, candidate);
268
+ children.push(toEntrySnapshot(entry));
269
+ }
270
+ }
271
+ children.sort(compareSnapshots);
272
+ return Object.freeze(children);
273
+ });
274
+ }
275
+ async writeFile(path, data, options = {}) {
276
+ this.assertOpen("write");
277
+ const canonical = this.canonicalNonRoot(path, "write");
278
+ const ownedData = copyBytes(data);
279
+ await this.ensureInitialized("write");
280
+ return this.withMutationLock(
281
+ () => this.transaction("readwrite", { operation: "write", path: canonical }, async (tx) => {
282
+ await assertNoLegacyConflict(tx, canonical, "write");
283
+ const mutation = new EntryMutation(tx);
284
+ const existing = await mutation.get(canonical);
285
+ if (existing?.kind === "directory") {
286
+ throw this.error(
287
+ "type-mismatch",
288
+ "write",
289
+ canonical,
290
+ "Cannot replace a directory with a file."
291
+ );
292
+ }
293
+ const mode = options.mode ?? "upsert";
294
+ if (mode === "create" && existing) {
295
+ throw this.error("already-exists", "write", canonical, "File already exists.");
296
+ }
297
+ if (mode === "replace" && !existing) {
298
+ throw this.error("not-found", "write", canonical, "File does not exist.");
299
+ }
300
+ this.assertExpectedVersion(existing, options.expectedVersion, "write", canonical);
301
+ const missingParents = await this.planParents(
302
+ mutation,
303
+ canonical,
304
+ options.createParents ?? false,
305
+ "write"
306
+ );
307
+ const next = await this.nextMutationVersion(tx);
308
+ this.addDirectories(mutation, missingParents, next);
309
+ const file = {
310
+ schemaVersion: ENTRY_SCHEMA_VERSION,
311
+ kind: "file",
312
+ path: canonical,
313
+ data: ownedData,
314
+ version: next.version,
315
+ lastModified: next.lastModified
316
+ };
317
+ mutation.set(canonical, file);
318
+ await this.touchParents(mutation, canonical, next);
319
+ await mutation.commit(next.state);
320
+ return toFileSnapshot(file);
321
+ })
322
+ );
323
+ }
324
+ async createDirectory(path, options = {}) {
325
+ this.assertOpen("create-directory");
326
+ const canonical = parseWorkspacePath(path);
327
+ await this.ensureInitialized("create-directory");
328
+ return this.withMutationLock(
329
+ () => this.transaction(
330
+ "readwrite",
331
+ { operation: "create-directory", path: canonical },
332
+ async (tx) => {
333
+ await assertNoLegacyConflict(tx, canonical, "create-directory");
334
+ const mutation = new EntryMutation(tx);
335
+ const existing = await mutation.get(canonical);
336
+ if (existing) {
337
+ if (existing.kind !== "directory") {
338
+ throw this.error(
339
+ "type-mismatch",
340
+ "create-directory",
341
+ canonical,
342
+ "Cannot replace a file with a directory."
343
+ );
344
+ }
345
+ if ((options.mode ?? "ensure") === "create") {
346
+ throw this.error(
347
+ "already-exists",
348
+ "create-directory",
349
+ canonical,
350
+ "Directory already exists."
351
+ );
352
+ }
353
+ return toDirectorySnapshot(existing);
354
+ }
355
+ const missingParents = await this.planParents(
356
+ mutation,
357
+ canonical,
358
+ options.createParents ?? false,
359
+ "create-directory"
360
+ );
361
+ const next = await this.nextMutationVersion(tx);
362
+ this.addDirectories(mutation, [...missingParents, canonical], next);
363
+ await this.touchParents(mutation, canonical, next);
364
+ await mutation.commit(next.state);
365
+ const created = await mutation.get(canonical);
366
+ if (!created || created.kind !== "directory") {
367
+ throw corrupt("Created directory was not staged.", canonical);
368
+ }
369
+ return toDirectorySnapshot(created);
370
+ }
371
+ )
372
+ );
373
+ }
374
+ async remove(path, options = {}) {
375
+ this.assertOpen("remove");
376
+ const canonical = this.canonicalNonRoot(path, "remove");
377
+ await this.ensureInitialized("remove");
378
+ return this.withMutationLock(
379
+ () => this.transaction("readwrite", { operation: "remove", path: canonical }, async (tx) => {
380
+ const mutation = new EntryMutation(tx);
381
+ const existing = await mutation.get(canonical);
382
+ const state = await readRequiredState(tx);
383
+ if (!existing) {
384
+ if ((options.missing ?? "error") === "ignore") {
385
+ return Object.freeze({
386
+ removed: false,
387
+ version: parseFileSystemVersion(state.treeVersion)
388
+ });
389
+ }
390
+ throw this.error("not-found", "remove", canonical, "Entry does not exist.");
391
+ }
392
+ this.assertExpectedVersion(existing, options.expectedVersion, "remove", canonical);
393
+ const paths = await mutation.paths();
394
+ const descendants = paths.filter(
395
+ (candidate) => workspacePathContains(canonical, candidate, false)
396
+ );
397
+ if (existing.kind === "directory" && descendants.length > 0 && !options.recursive) {
398
+ throw this.error("not-empty", "remove", canonical, "Directory is not empty.");
399
+ }
400
+ const next = this.advanceState(state);
401
+ mutation.delete(canonical);
402
+ if (existing.kind === "directory") {
403
+ for (const descendant of descendants) mutation.delete(descendant);
404
+ }
405
+ await this.touchParents(mutation, canonical, next);
406
+ await mutation.commit(next.state);
407
+ return Object.freeze({ removed: true, version: next.version });
408
+ })
409
+ );
410
+ }
411
+ async move(oldPath, newPath, options = {}) {
412
+ this.assertOpen("move");
413
+ const sourcePath = this.canonicalNonRoot(oldPath, "move");
414
+ const destinationPath = this.canonicalNonRoot(newPath, "move");
415
+ await this.ensureInitialized("move");
416
+ return this.withMutationLock(
417
+ () => this.transaction(
418
+ "readwrite",
419
+ { operation: "move", path: sourcePath, destinationPath },
420
+ async (tx) => {
421
+ await assertNoLegacyConflict(tx, sourcePath, "move");
422
+ await assertNoLegacyConflict(tx, destinationPath, "move");
423
+ const mutation = new EntryMutation(tx);
424
+ const source = await mutation.get(sourcePath);
425
+ if (!source) {
426
+ throw this.error("not-found", "move", sourcePath, "Source does not exist.");
427
+ }
428
+ this.assertExpectedVersion(source, options.expectedVersion, "move", sourcePath);
429
+ if (sourcePath === destinationPath) return toEntrySnapshot(source);
430
+ if (await mutation.get(destinationPath)) {
431
+ throw new FsError("already-exists", "Destination already exists.", {
432
+ operation: "move",
433
+ path: sourcePath,
434
+ destinationPath
435
+ });
436
+ }
437
+ if (source.kind === "directory" && workspacePathContains(sourcePath, destinationPath, false)) {
438
+ throw new FsError("invalid-path", "Cannot move a directory into itself.", {
439
+ operation: "move",
440
+ path: sourcePath,
441
+ destinationPath
442
+ });
443
+ }
444
+ const missingParents = await this.planParents(
445
+ mutation,
446
+ destinationPath,
447
+ options.createParents ?? false,
448
+ "move"
449
+ );
450
+ const paths = await mutation.paths();
451
+ const sourcePaths = paths.filter(
452
+ (candidate) => workspacePathContains(sourcePath, candidate)
453
+ );
454
+ const sourceSet = new Set(sourcePaths);
455
+ const remapped = [];
456
+ for (const currentPath of sourcePaths) {
457
+ const suffix = currentPath.slice(sourcePath.length);
458
+ const targetPath = parseWorkspacePath(`${destinationPath}${suffix}`);
459
+ const collision = await mutation.get(targetPath);
460
+ if (collision && !sourceSet.has(targetPath)) {
461
+ throw new FsError("already-exists", "A destination descendant already exists.", {
462
+ operation: "move",
463
+ path: currentPath,
464
+ destinationPath: targetPath
465
+ });
466
+ }
467
+ remapped.push([currentPath, targetPath, await mutation.require(currentPath)]);
468
+ }
469
+ const state = await readRequiredState(tx);
470
+ const next = this.advanceState(state);
471
+ for (const currentPath of sourcePaths) mutation.delete(currentPath);
472
+ this.addDirectories(mutation, missingParents, next);
473
+ for (const [, targetPath, current] of remapped) {
474
+ mutation.set(targetPath, withPathAndVersion(current, targetPath, next));
475
+ }
476
+ await this.touchParents(mutation, sourcePath, next);
477
+ await this.touchParents(mutation, destinationPath, next);
478
+ await mutation.commit(next.state);
479
+ return toEntrySnapshot(await mutation.require(destinationPath));
480
+ }
481
+ )
482
+ );
483
+ }
484
+ async snapshot() {
485
+ this.assertOpen("snapshot");
486
+ await this.ensureInitialized("snapshot");
487
+ return this.transaction("readonly", { operation: "snapshot" }, async (tx) => {
488
+ const [state, paths] = await Promise.all([readRequiredState(tx), listEntryPaths(tx)]);
489
+ const entries = [];
490
+ for (const path of paths.sort(compareText)) {
491
+ const entry = await readRequiredEntry(tx, path);
492
+ entries.push(
493
+ entry.kind === "file" ? Object.freeze({ ...toFileSnapshot(entry), data: copyBytes(entry.data) }) : toDirectorySnapshot(entry)
494
+ );
495
+ }
496
+ return Object.freeze({
497
+ version: parseFileSystemVersion(state.treeVersion),
498
+ entries: Object.freeze(entries)
499
+ });
500
+ });
501
+ }
502
+ /** IndexedDB has no reliable cross-browser external-change observer. */
503
+ watch(_listener, options = {}) {
504
+ this.assertOpen("watch");
505
+ const error = new FsError("not-supported", "IndexedDB filesystem watching is unavailable.", {
506
+ operation: "watch"
507
+ });
508
+ let closed = false;
509
+ let disposePromise = null;
510
+ const ready = Promise.reject(error);
511
+ void ready.catch((caught) => {
512
+ try {
513
+ options.onError?.(caught);
514
+ } catch {
515
+ }
516
+ });
517
+ const subscription = {
518
+ get closed() {
519
+ return closed;
520
+ },
521
+ ready,
522
+ dispose: () => {
523
+ if (disposePromise) return disposePromise;
524
+ closed = true;
525
+ this.subscriptions.delete(subscription);
526
+ disposePromise = Promise.resolve();
527
+ return disposePromise;
528
+ }
529
+ };
530
+ this.subscriptions.add(subscription);
531
+ return subscription;
532
+ }
533
+ dispose() {
534
+ if (this.disposePromise) return this.disposePromise;
535
+ this.disposed = true;
536
+ this.disposePromise = (async () => {
537
+ try {
538
+ await Promise.allSettled([...this.inFlight]);
539
+ await Promise.all([...this.subscriptions].map((subscription) => subscription.dispose()));
540
+ } finally {
541
+ if (this.closeStoreOnDispose && !this.storeClosed) {
542
+ this.storeClosed = true;
543
+ this.store.close?.();
544
+ }
545
+ }
546
+ })();
547
+ return this.disposePromise;
548
+ }
549
+ /** List unresolved, byte-preserving conflicts found during v1 migration. */
550
+ async listLegacyMigrationConflicts() {
551
+ this.assertOpen("read");
552
+ await this.ensureInitialized("read");
553
+ return this.transaction("readonly", { operation: "read" }, async (tx) => {
554
+ const keys = (await tx.keys()).filter((key) => key.startsWith(LEGACY_CONFLICT_PREFIX));
555
+ const conflicts = [];
556
+ for (const key of keys) {
557
+ const raw = await tx.get(key);
558
+ conflicts.push(toPublicLegacyConflict(validateLegacyConflict(raw)));
559
+ }
560
+ conflicts.sort((left, right) => compareText(left.path, right.path));
561
+ return Object.freeze(conflicts);
562
+ });
563
+ }
564
+ /** Resolve quarantined legacy data by choosing v2 or one legacy byte payload. */
565
+ async resolveLegacyMigrationConflict(path, resolution) {
566
+ this.assertOpen("write");
567
+ const canonical = this.canonicalNonRoot(path, "write");
568
+ await this.ensureInitialized("write");
569
+ return this.withMutationLock(
570
+ () => this.transaction("readwrite", { operation: "write", path: canonical }, async (tx) => {
571
+ const key = legacyConflictKey(canonical);
572
+ const conflict = validateLegacyConflict(await tx.get(key));
573
+ if (conflict.path !== canonical) throw corrupt("Legacy conflict key mismatch.", canonical);
574
+ const mutation = new EntryMutation(tx);
575
+ const existing = await mutation.get(canonical);
576
+ if (resolution === "v2") {
577
+ if (!existing) {
578
+ throw this.error(
579
+ "type-mismatch",
580
+ "write",
581
+ canonical,
582
+ "There is no v2 authority to keep at this path."
583
+ );
584
+ }
585
+ await tx.delete(key);
586
+ return toEntrySnapshot(existing);
587
+ }
588
+ const selected = selectLegacyPayload(conflict, resolution, canonical);
589
+ if (selected.kind === "directory") {
590
+ if (existing?.kind === "file") {
591
+ throw this.error(
592
+ "type-mismatch",
593
+ "write",
594
+ canonical,
595
+ "Move or remove the v2 file before restoring a legacy directory at this path."
596
+ );
597
+ }
598
+ if (existing) {
599
+ await tx.delete(key);
600
+ return toDirectorySnapshot(existing);
601
+ }
602
+ const missingParents2 = await this.planParents(mutation, canonical, true, "write");
603
+ const next2 = await this.nextMutationVersion(tx);
604
+ this.addDirectories(mutation, [...missingParents2, canonical], next2);
605
+ await this.touchParents(mutation, canonical, next2);
606
+ await mutation.commit(next2.state);
607
+ await tx.delete(key);
608
+ const restored = await mutation.require(canonical);
609
+ if (restored.kind !== "directory") {
610
+ throw corrupt("Restored legacy directory was not staged as a directory.", canonical);
611
+ }
612
+ return toDirectorySnapshot(restored);
613
+ }
614
+ if (existing?.kind === "directory") {
615
+ throw this.error(
616
+ "type-mismatch",
617
+ "write",
618
+ canonical,
619
+ "Move or remove the v2 directory before restoring a legacy file at this path."
620
+ );
621
+ }
622
+ const missingParents = await this.planParents(mutation, canonical, true, "write");
623
+ const next = await this.nextMutationVersion(tx);
624
+ this.addDirectories(mutation, missingParents, next);
625
+ const file = {
626
+ schemaVersion: ENTRY_SCHEMA_VERSION,
627
+ kind: "file",
628
+ path: canonical,
629
+ data: copyBytes(selected.data),
630
+ version: next.version,
631
+ lastModified: next.lastModified
632
+ };
633
+ mutation.set(canonical, file);
634
+ await this.touchParents(mutation, canonical, next);
635
+ await mutation.commit(next.state);
636
+ await tx.delete(key);
637
+ return toFileSnapshot(file);
638
+ })
639
+ );
640
+ }
641
+ /** Explicitly discard every quarantined legacy candidate at a path. */
642
+ async discardLegacyMigrationConflict(path) {
643
+ this.assertOpen("remove");
644
+ const canonical = this.canonicalNonRoot(path, "remove");
645
+ await this.ensureInitialized("remove");
646
+ return this.withMutationLock(
647
+ () => this.transaction("readwrite", { operation: "remove", path: canonical }, async (tx) => {
648
+ const key = legacyConflictKey(canonical);
649
+ if (await tx.get(key) === null) return false;
650
+ await tx.delete(key);
651
+ return true;
652
+ })
653
+ );
654
+ }
655
+ async ensureInitialized(operation) {
656
+ this.assertOpen(operation);
657
+ if (this.initialization) return this.initialization;
658
+ const attempt = this.withMutationLock(
659
+ () => this.runSharedTransaction("readwrite", operation, (tx) => this.migrateLegacy(tx))
660
+ );
661
+ this.initialization = attempt.catch((error) => {
662
+ this.initialization = null;
663
+ throw fsErrorFromUnknown(error, { operation });
664
+ });
665
+ return this.initialization;
666
+ }
667
+ async migrateLegacy(tx) {
668
+ const existingState = await readState(tx);
669
+ const keys = await tx.keys();
670
+ if (existingState) {
671
+ const root = await readEntry(tx, WORKSPACE_ROOT);
672
+ if (!root || root.kind !== "directory") {
673
+ throw corrupt("Initialized v2 filesystem has no root directory.", WORKSPACE_ROOT);
674
+ }
675
+ if (existingState.migrationVersion === 1 || keys.some((key) => key.startsWith(LEGACY_PREFIX))) {
676
+ await this.quarantineExistingLegacy(
677
+ tx,
678
+ keys,
679
+ existingState,
680
+ existingState.migrationVersion === 1 ? "authority-collision" : "stale-client-write"
681
+ );
682
+ }
683
+ return;
684
+ }
685
+ const now = this.now().toISOString();
686
+ const version = this.makeVersion(1);
687
+ const entries = /* @__PURE__ */ new Map();
688
+ for (const path of entryPathsFromKeys(keys)) {
689
+ entries.set(path, await readRequiredEntry(tx, path));
690
+ }
691
+ if (entries.get(WORKSPACE_ROOT)?.kind === "file") {
692
+ throw corrupt(
693
+ "Pre-release v2 authority stored a file at the workspace root.",
694
+ WORKSPACE_ROOT
695
+ );
696
+ }
697
+ entries.set(
698
+ WORKSPACE_ROOT,
699
+ entries.get(WORKSPACE_ROOT) ?? directoryRecord(WORKSPACE_ROOT, version, now)
700
+ );
701
+ const legacyDirectories = await readLegacyDirectories(tx);
702
+ const legacyFilePaths = legacyPathsFromKeys(keys);
703
+ for (const directory of legacyDirectories) {
704
+ const existing = entries.get(directory);
705
+ if (existing?.kind === "file" || hasFileAncestor(entries, directory)) {
706
+ await appendLegacyConflict(
707
+ tx,
708
+ directory,
709
+ directoryRecoveryCandidate("authority-collision", now)
710
+ );
711
+ continue;
712
+ }
713
+ addParentDirectories(entries, directory, version, now);
714
+ if (!existing) entries.set(directory, directoryRecord(directory, version, now));
715
+ }
716
+ for (const path of legacyFilePaths) {
717
+ const legacy = await readLegacyFile(tx, path);
718
+ const existing = entries.get(path);
719
+ if (existing?.kind === "file") {
720
+ if (!legacyPayloadsMatchEntry(legacy, existing)) {
721
+ await appendLegacyConflict(
722
+ tx,
723
+ path,
724
+ fileRecoveryCandidate(legacy, "authority-collision", now)
725
+ );
726
+ }
727
+ continue;
728
+ }
729
+ const kindCollision = existing?.kind === "directory" || hasFileAncestor(entries, path);
730
+ const payloadsDisagree = legacy.content !== null && legacy.binary !== null && !bytesEqual(new TextEncoder().encode(legacy.content), legacy.binary);
731
+ if (payloadsDisagree || kindCollision || legacy.content === null && legacy.binary === null) {
732
+ await appendLegacyConflict(
733
+ tx,
734
+ path,
735
+ fileRecoveryCandidate(
736
+ legacy,
737
+ kindCollision ? "kind-collision" : payloadsDisagree ? "dual-payload" : "authority-collision",
738
+ now
739
+ )
740
+ );
741
+ continue;
742
+ }
743
+ addParentDirectories(entries, path, version, now);
744
+ const data = legacy.content !== null ? new TextEncoder().encode(legacy.content).buffer : copyBytes(legacy.binary);
745
+ entries.set(path, {
746
+ schemaVersion: ENTRY_SCHEMA_VERSION,
747
+ kind: "file",
748
+ path,
749
+ data: copyBytes(data),
750
+ version,
751
+ lastModified: legacy.metadata?.lastModified ?? now
752
+ });
753
+ }
754
+ for (const entry of entries.values()) {
755
+ await tx.put(entryKey(entry.path), entry);
756
+ }
757
+ await deleteLegacyNamespace(tx, keys);
758
+ const state = {
759
+ schemaVersion: STATE_SCHEMA_VERSION,
760
+ migrationVersion: 2,
761
+ mutationCounter: 1,
762
+ treeVersion: version
763
+ };
764
+ await tx.put(STATE_KEY, state);
765
+ }
766
+ async quarantineExistingLegacy(tx, keys, state, reason) {
767
+ const now = this.now().toISOString();
768
+ const ignored = await readMarkerPaths(
769
+ tx,
770
+ keys,
771
+ LEGACY_IGNORED_PREFIX,
772
+ validateIgnoredLegacyPath
773
+ );
774
+ const legacyDirectories = await readLegacyDirectories(tx);
775
+ for (const path of legacyDirectories) {
776
+ if (ignored.has(path)) continue;
777
+ const entry = await readEntry(tx, path);
778
+ if (entry?.kind === "directory") continue;
779
+ await appendLegacyConflict(
780
+ tx,
781
+ path,
782
+ directoryRecoveryCandidate(entry ? "kind-collision" : reason, now)
783
+ );
784
+ }
785
+ for (const path of legacyPathsFromKeys(keys)) {
786
+ if (ignored.has(path)) continue;
787
+ const legacy = await readLegacyFile(tx, path);
788
+ const entry = await readEntry(tx, path);
789
+ if (entry?.kind === "file" && legacyPayloadsMatchEntry(legacy, entry)) continue;
790
+ await appendLegacyConflict(
791
+ tx,
792
+ path,
793
+ fileRecoveryCandidate(legacy, entry?.kind === "directory" ? "kind-collision" : reason, now)
794
+ );
795
+ }
796
+ await deleteLegacyNamespace(tx, keys);
797
+ for (const key of keys) {
798
+ if (key.startsWith(LEGACY_IGNORED_PREFIX)) await tx.delete(key);
799
+ }
800
+ if (state.migrationVersion === 1) {
801
+ await tx.put(STATE_KEY, { ...state, migrationVersion: 2 });
802
+ }
803
+ }
804
+ async transaction(mode, context, operation) {
805
+ this.assertOpen(context.operation ?? "read");
806
+ for (let attempt = 0; attempt < 3; attempt += 1) {
807
+ try {
808
+ const outcome = await this.runSharedTransaction(
809
+ mode,
810
+ context.operation ?? "read",
811
+ async (transaction) => {
812
+ if (await transaction.hasKeyWithPrefix(LEGACY_PREFIX)) {
813
+ if (mode === "readonly") throw new LegacyNamespaceDetectedError();
814
+ const state = await readRequiredState(transaction);
815
+ await this.quarantineExistingLegacy(
816
+ transaction,
817
+ await transaction.keys(),
818
+ state,
819
+ "stale-client-write"
820
+ );
821
+ return { retry: true };
822
+ }
823
+ return { retry: false, value: await operation(transaction) };
824
+ }
825
+ );
826
+ if (outcome.retry) continue;
827
+ return outcome.value;
828
+ } catch (error) {
829
+ if (!(error instanceof LegacyNamespaceDetectedError)) {
830
+ throw fsErrorFromUnknown(error, context);
831
+ }
832
+ try {
833
+ await this.withMutationLock(
834
+ () => this.runSharedTransaction(
835
+ "readwrite",
836
+ context.operation ?? "read",
837
+ async (transaction) => {
838
+ if (!await transaction.hasKeyWithPrefix(LEGACY_PREFIX)) return;
839
+ const state = await readRequiredState(transaction);
840
+ await this.quarantineExistingLegacy(
841
+ transaction,
842
+ await transaction.keys(),
843
+ state,
844
+ "stale-client-write"
845
+ );
846
+ }
847
+ )
848
+ );
849
+ } catch (quarantineError) {
850
+ throw fsErrorFromUnknown(quarantineError, context);
851
+ }
852
+ }
853
+ }
854
+ throw new FsError("busy", "A stale IndexedDB client is continuously writing legacy records.", {
855
+ ...context,
856
+ retryable: true
857
+ });
858
+ }
859
+ withMutationLock(operation) {
860
+ return withFileCommitLock(`indexeddb:${this.id}:workspace`, operation);
861
+ }
862
+ async nextMutationVersion(tx) {
863
+ return this.advanceState(await readRequiredState(tx));
864
+ }
865
+ advanceState(state) {
866
+ if (state.mutationCounter >= Number.MAX_SAFE_INTEGER) {
867
+ throw corrupt("IndexedDB filesystem mutation counter overflowed.");
868
+ }
869
+ const mutationCounter = state.mutationCounter + 1;
870
+ const version = this.makeVersion(mutationCounter);
871
+ const nextState = {
872
+ ...state,
873
+ mutationCounter,
874
+ treeVersion: version
875
+ };
876
+ return { state: nextState, version, lastModified: this.now().toISOString() };
877
+ }
878
+ makeVersion(counter) {
879
+ return parseFileSystemVersion(`${this.versionPrefix}:${counter}`);
880
+ }
881
+ async planParents(mutation, path, createParents, operation) {
882
+ const missing = [];
883
+ for (const parent of parentChain(path)) {
884
+ const entry = await mutation.get(parent);
885
+ if (entry?.kind === "file") {
886
+ throw this.error(
887
+ "type-mismatch",
888
+ operation,
889
+ parent,
890
+ "A parent path is a file, not a directory."
891
+ );
892
+ }
893
+ if (!entry) {
894
+ if (!createParents) {
895
+ throw this.error("not-found", operation, parent, "Parent directory does not exist.");
896
+ }
897
+ missing.push(parent);
898
+ }
899
+ }
900
+ return missing;
901
+ }
902
+ addDirectories(mutation, paths, next) {
903
+ for (const path of paths) {
904
+ mutation.set(path, directoryRecord(path, next.version, next.lastModified));
905
+ }
906
+ }
907
+ async touchParents(mutation, path, next) {
908
+ for (const parent of parentChain(path)) {
909
+ const entry = await mutation.get(parent);
910
+ if (entry?.kind === "directory") {
911
+ mutation.set(parent, directoryRecord(parent, next.version, next.lastModified));
912
+ }
913
+ }
914
+ }
915
+ canonicalNonRoot(path, operation) {
916
+ const canonical = parseWorkspacePath(path);
917
+ if (!canonical) {
918
+ throw this.error(
919
+ "invalid-path",
920
+ operation,
921
+ canonical,
922
+ "The workspace root is not a file entry."
923
+ );
924
+ }
925
+ return canonical;
926
+ }
927
+ assertExpectedVersion(entry, expected, operation, path) {
928
+ if (expected === void 0) return;
929
+ if (expected === null ? !entry : entry?.version === expected) return;
930
+ throw this.error(
931
+ "conflict",
932
+ operation,
933
+ path,
934
+ "Entry version does not match the expected version."
935
+ );
936
+ }
937
+ assertOpen(operation) {
938
+ if (!this.disposed) return;
939
+ throw new FsError("disposed", "Filesystem provider is disposed.", { operation });
940
+ }
941
+ error(code, operation, path, message) {
942
+ return new FsError(code, message, { operation, path });
943
+ }
944
+ };
945
+ var EntryMutation = class {
946
+ constructor(transaction) {
947
+ this.transaction = transaction;
948
+ this.cache = /* @__PURE__ */ new Map();
949
+ this.changed = /* @__PURE__ */ new Map();
950
+ this.deleted = /* @__PURE__ */ new Set();
951
+ }
952
+ async get(path) {
953
+ if (this.deleted.has(path)) return null;
954
+ const cached = this.cache.get(path);
955
+ if (cached !== void 0) return cached;
956
+ const entry = await readEntry(this.transaction, path);
957
+ this.cache.set(path, entry);
958
+ return entry;
959
+ }
960
+ async require(path) {
961
+ const entry = await this.get(path);
962
+ if (!entry) throw corrupt(`Missing staged entry: ${path}.`, path);
963
+ return entry;
964
+ }
965
+ set(path, entry) {
966
+ const owned = copyEntry(entry);
967
+ this.cache.set(path, owned);
968
+ this.changed.set(path, owned);
969
+ this.deleted.delete(path);
970
+ }
971
+ delete(path) {
972
+ this.cache.set(path, null);
973
+ this.changed.delete(path);
974
+ this.deleted.add(path);
975
+ }
976
+ async paths() {
977
+ const paths = new Set(await listEntryPaths(this.transaction));
978
+ for (const path of this.deleted) paths.delete(path);
979
+ for (const path of this.changed.keys()) paths.add(path);
980
+ return [...paths];
981
+ }
982
+ async commit(state) {
983
+ for (const path of this.deleted) await this.transaction.delete(entryKey(path));
984
+ for (const [path, entry] of this.changed) await this.transaction.put(entryKey(path), entry);
985
+ await this.transaction.put(STATE_KEY, state);
986
+ }
987
+ };
988
+ function entryKey(path) {
989
+ return `${ENTRY_PREFIX}${path}`;
990
+ }
991
+ function legacyConflictKey(path) {
992
+ return `${LEGACY_CONFLICT_PREFIX}${path}`;
993
+ }
994
+ function entryPathsFromKeys(keys) {
995
+ return keys.filter((key) => key.startsWith(ENTRY_PREFIX)).map((key) => {
996
+ const rawPath = key.slice(ENTRY_PREFIX.length);
997
+ const path = parseWorkspacePath(rawPath);
998
+ if (path !== rawPath) throw corrupt(`Non-canonical v2 entry key: ${key}.`);
999
+ return path;
1000
+ });
1001
+ }
1002
+ async function listEntryPaths(tx) {
1003
+ return entryPathsFromKeys(await tx.keys());
1004
+ }
1005
+ async function readEntry(tx, path) {
1006
+ const raw = await tx.get(entryKey(path));
1007
+ return raw === null ? null : validateEntry(raw, path);
1008
+ }
1009
+ async function readRequiredEntry(tx, path) {
1010
+ const entry = await readEntry(tx, path);
1011
+ if (!entry) throw corrupt(`Missing v2 entry record: ${path}.`, path);
1012
+ return entry;
1013
+ }
1014
+ function validateEntry(value, expectedPath) {
1015
+ if (!isRecord(value)) throw corrupt(`Corrupt v2 entry record: ${expectedPath}.`, expectedPath);
1016
+ const path = parseWorkspacePath(readString(value.path, "entry path"));
1017
+ if (path !== expectedPath) throw corrupt(`V2 entry path does not match key: ${expectedPath}.`);
1018
+ if (value.schemaVersion !== ENTRY_SCHEMA_VERSION) {
1019
+ throw corrupt(`Unsupported v2 entry schema at ${expectedPath}.`, expectedPath);
1020
+ }
1021
+ const version = parseFileSystemVersion(value.version);
1022
+ const lastModified = readString(value.lastModified, "lastModified");
1023
+ if (value.kind === "directory") {
1024
+ return { schemaVersion: ENTRY_SCHEMA_VERSION, kind: "directory", path, version, lastModified };
1025
+ }
1026
+ if (value.kind !== "file") throw corrupt(`Unknown entry kind at ${expectedPath}.`, expectedPath);
1027
+ return {
1028
+ schemaVersion: ENTRY_SCHEMA_VERSION,
1029
+ kind: "file",
1030
+ path,
1031
+ version,
1032
+ lastModified,
1033
+ data: validateBytes(value.data, expectedPath)
1034
+ };
1035
+ }
1036
+ async function readState(tx) {
1037
+ const raw = await tx.get(STATE_KEY);
1038
+ return raw === null ? null : validateState(raw);
1039
+ }
1040
+ async function readRequiredState(tx) {
1041
+ const state = await readState(tx);
1042
+ if (!state) throw corrupt("IndexedDB v2 state record is missing.");
1043
+ return state;
1044
+ }
1045
+ function validateState(value) {
1046
+ if (!isRecord(value) || value.schemaVersion !== STATE_SCHEMA_VERSION || value.migrationVersion !== 1 && value.migrationVersion !== 2 || typeof value.mutationCounter !== "number" || !Number.isSafeInteger(value.mutationCounter) || value.mutationCounter < 0) {
1047
+ throw corrupt("IndexedDB v2 state record is corrupt.");
1048
+ }
1049
+ return {
1050
+ schemaVersion: STATE_SCHEMA_VERSION,
1051
+ migrationVersion: value.migrationVersion,
1052
+ mutationCounter: value.mutationCounter,
1053
+ treeVersion: parseFileSystemVersion(value.treeVersion)
1054
+ };
1055
+ }
1056
+ async function readLegacyDirectories(tx) {
1057
+ const raw = await tx.get(LEGACY_DIRS_KEY);
1058
+ if (raw === null) return [];
1059
+ if (!Array.isArray(raw)) throw corrupt("Legacy directory index is corrupt.");
1060
+ const directories = [];
1061
+ for (const value of raw) {
1062
+ if (typeof value !== "string") throw corrupt("Legacy directory index is corrupt.");
1063
+ const path = parseWorkspacePath(value);
1064
+ if (path) directories.push(path);
1065
+ }
1066
+ return directories;
1067
+ }
1068
+ function legacyPathsFromKeys(keys) {
1069
+ const paths = /* @__PURE__ */ new Set();
1070
+ for (const key of keys) {
1071
+ if (!key.startsWith(LEGACY_PREFIX) || key === LEGACY_DIRS_KEY) continue;
1072
+ const suffix = [LEGACY_CONTENT_SUFFIX, LEGACY_BINARY_SUFFIX, LEGACY_META_SUFFIX].find(
1073
+ (candidate) => key.endsWith(candidate)
1074
+ );
1075
+ if (!suffix) continue;
1076
+ const rawPath = key.slice(LEGACY_PREFIX.length, -suffix.length);
1077
+ try {
1078
+ const path = parseWorkspacePath(rawPath);
1079
+ if (path) paths.add(path);
1080
+ } catch {
1081
+ }
1082
+ }
1083
+ return [...paths];
1084
+ }
1085
+ async function readLegacyFile(tx, path) {
1086
+ const [rawContent, rawBinary, rawMetadata] = await Promise.all([
1087
+ tx.get(`${LEGACY_PREFIX}${path}${LEGACY_CONTENT_SUFFIX}`),
1088
+ tx.get(`${LEGACY_PREFIX}${path}${LEGACY_BINARY_SUFFIX}`),
1089
+ tx.get(`${LEGACY_PREFIX}${path}${LEGACY_META_SUFFIX}`)
1090
+ ]);
1091
+ if (rawContent !== null && typeof rawContent !== "string") {
1092
+ throw corrupt(`Legacy text payload is corrupt: ${path}.`, path);
1093
+ }
1094
+ return {
1095
+ content: rawContent,
1096
+ binary: rawBinary === null ? null : validateBytes(rawBinary, path),
1097
+ metadata: validateLegacyMetadata(rawMetadata, path)
1098
+ };
1099
+ }
1100
+ async function readMarkerPaths(transaction, keys, prefix, validate) {
1101
+ const paths = /* @__PURE__ */ new Set();
1102
+ for (const key of keys) {
1103
+ if (!key.startsWith(prefix)) continue;
1104
+ const expectedPath = parseWorkspacePath(key.slice(prefix.length));
1105
+ if (!expectedPath) throw corrupt(`Recovery marker cannot target the workspace root: ${key}.`);
1106
+ const value = await transaction.get(key);
1107
+ const path = validate(value);
1108
+ if (path !== expectedPath)
1109
+ throw corrupt(`Recovery marker path does not match its key: ${key}.`);
1110
+ paths.add(path);
1111
+ }
1112
+ return paths;
1113
+ }
1114
+ function validateIgnoredLegacyPath(value) {
1115
+ if (!isRecord(value) || value.schemaVersion !== 1 || typeof value.ignoredAt !== "string") {
1116
+ throw corrupt("Ignored legacy recovery marker is corrupt.");
1117
+ }
1118
+ const path = parseWorkspacePath(readString(value.path, "ignored legacy path"));
1119
+ if (!path) throw corrupt("Ignored legacy recovery marker cannot target the workspace root.");
1120
+ return path;
1121
+ }
1122
+ function bytesEqual(left, right) {
1123
+ const leftBytes = ArrayBuffer.isView(left) ? new Uint8Array(left.buffer, left.byteOffset, left.byteLength) : new Uint8Array(left);
1124
+ const rightBytes = ArrayBuffer.isView(right) ? new Uint8Array(right.buffer, right.byteOffset, right.byteLength) : new Uint8Array(right);
1125
+ if (leftBytes.byteLength !== rightBytes.byteLength) return false;
1126
+ return leftBytes.every((byte, index) => byte === rightBytes[index]);
1127
+ }
1128
+ async function assertNoLegacyConflict(transaction, path, operation) {
1129
+ if (await transaction.get(legacyConflictKey(path)) === null) return;
1130
+ throw new FsError(
1131
+ "conflict",
1132
+ "Resolve the legacy migration conflict before mutating this path.",
1133
+ {
1134
+ operation,
1135
+ path,
1136
+ retryable: false
1137
+ }
1138
+ );
1139
+ }
1140
+ function validateLegacyMetadata(value, path) {
1141
+ if (value === null) return null;
1142
+ if (!isRecord(value) || typeof value.name !== "string" || typeof value.path !== "string" || typeof value.size !== "number" || !Number.isFinite(value.size) || typeof value.lastModified !== "string") {
1143
+ throw corrupt(`Legacy metadata is corrupt: ${path}.`, path);
1144
+ }
1145
+ return {
1146
+ name: value.name,
1147
+ path: value.path,
1148
+ size: value.size,
1149
+ lastModified: value.lastModified
1150
+ };
1151
+ }
1152
+ function validateLegacyConflict(value) {
1153
+ if (!isRecord(value)) {
1154
+ throw corrupt("Legacy migration conflict record is corrupt.");
1155
+ }
1156
+ const path = parseWorkspacePath(readString(value.path, "legacy conflict path"));
1157
+ if (!path) throw corrupt("Legacy migration conflict cannot target the workspace root.");
1158
+ if (value.schemaVersion === LEGACY_CONFLICT_SCHEMA_VERSION_V1) {
1159
+ const reason = validateLegacyConflictReason(value.reason);
1160
+ const candidate = {
1161
+ kind: "file",
1162
+ reason,
1163
+ text: value.text === null ? null : readString(value.text, "legacy conflict text"),
1164
+ binary: value.binary === null ? null : validateBytes(value.binary, path),
1165
+ metadata: validateLegacyMetadata(value.metadata, path),
1166
+ detectedAt: readString(value.detectedAt, "legacy conflict detectedAt")
1167
+ };
1168
+ return {
1169
+ schemaVersion: LEGACY_CONFLICT_SCHEMA_VERSION,
1170
+ path,
1171
+ candidates: Object.freeze([candidate])
1172
+ };
1173
+ }
1174
+ if (value.schemaVersion !== LEGACY_CONFLICT_SCHEMA_VERSION || !Array.isArray(value.candidates)) {
1175
+ throw corrupt("Legacy migration conflict record is corrupt.");
1176
+ }
1177
+ const candidates = value.candidates.map(
1178
+ (candidate) => validateLegacyRecoveryCandidate(candidate, path)
1179
+ );
1180
+ if (candidates.length === 0) throw corrupt("Legacy migration conflict has no candidates.", path);
1181
+ return {
1182
+ schemaVersion: LEGACY_CONFLICT_SCHEMA_VERSION,
1183
+ path,
1184
+ candidates: Object.freeze(candidates)
1185
+ };
1186
+ }
1187
+ function toPublicLegacyConflict(conflict) {
1188
+ const candidates = conflict.candidates.map(toPublicLegacyCandidate);
1189
+ const latest = candidates[candidates.length - 1];
1190
+ return Object.freeze({
1191
+ path: parseWorkspacePath(conflict.path),
1192
+ reason: latest.reason,
1193
+ kind: latest.kind,
1194
+ text: latest.text,
1195
+ binary: latest.binary,
1196
+ metadata: latest.metadata,
1197
+ detectedAt: latest.detectedAt,
1198
+ candidates: Object.freeze(candidates)
1199
+ });
1200
+ }
1201
+ function validateLegacyRecoveryCandidate(value, path) {
1202
+ if (!isRecord(value) || value.kind !== "file" && value.kind !== "directory") {
1203
+ throw corrupt("Legacy recovery candidate is corrupt.", path);
1204
+ }
1205
+ const kind = value.kind;
1206
+ const text = value.text === null ? null : readString(value.text, "legacy recovery text");
1207
+ const binary = value.binary === null ? null : validateBytes(value.binary, path);
1208
+ const metadata = validateLegacyMetadata(value.metadata, path);
1209
+ if (kind === "directory" && (text !== null || binary !== null || metadata !== null)) {
1210
+ throw corrupt("A legacy directory recovery candidate contains file data.", path);
1211
+ }
1212
+ return {
1213
+ kind,
1214
+ reason: validateLegacyConflictReason(value.reason),
1215
+ text,
1216
+ binary,
1217
+ metadata,
1218
+ detectedAt: readString(value.detectedAt, "legacy recovery detectedAt")
1219
+ };
1220
+ }
1221
+ function validateLegacyConflictReason(value) {
1222
+ if (value === "dual-payload" || value === "kind-collision" || value === "authority-collision" || value === "stale-client-write") {
1223
+ return value;
1224
+ }
1225
+ throw corrupt("Legacy recovery reason is invalid.");
1226
+ }
1227
+ function toPublicLegacyCandidate(candidate) {
1228
+ return Object.freeze({
1229
+ kind: candidate.kind,
1230
+ reason: candidate.reason,
1231
+ text: candidate.text,
1232
+ binary: candidate.binary ? copyBytes(candidate.binary) : null,
1233
+ metadata: candidate.metadata ? Object.freeze({ ...candidate.metadata }) : null,
1234
+ detectedAt: candidate.detectedAt
1235
+ });
1236
+ }
1237
+ async function appendLegacyConflict(transaction, path, candidate) {
1238
+ const key = legacyConflictKey(path);
1239
+ const raw = await transaction.get(key);
1240
+ const existing = raw === null ? null : validateLegacyConflict(raw);
1241
+ if (existing?.candidates.some((item) => recoveryCandidatesEqual(item, candidate))) return;
1242
+ const candidates = [...existing?.candidates ?? [], copyRecoveryCandidate(candidate)];
1243
+ await transaction.put(key, {
1244
+ schemaVersion: LEGACY_CONFLICT_SCHEMA_VERSION,
1245
+ path,
1246
+ candidates
1247
+ });
1248
+ }
1249
+ function fileRecoveryCandidate(legacy, reason, detectedAt) {
1250
+ return {
1251
+ kind: "file",
1252
+ reason,
1253
+ text: legacy.content,
1254
+ binary: legacy.binary ? copyBytes(legacy.binary) : null,
1255
+ metadata: legacy.metadata ? { ...legacy.metadata } : null,
1256
+ detectedAt
1257
+ };
1258
+ }
1259
+ function directoryRecoveryCandidate(reason, detectedAt) {
1260
+ return { kind: "directory", reason, text: null, binary: null, metadata: null, detectedAt };
1261
+ }
1262
+ function copyRecoveryCandidate(candidate) {
1263
+ return {
1264
+ ...candidate,
1265
+ binary: candidate.binary ? copyBytes(candidate.binary) : null,
1266
+ metadata: candidate.metadata ? { ...candidate.metadata } : null
1267
+ };
1268
+ }
1269
+ function recoveryCandidatesEqual(left, right) {
1270
+ return left.kind === right.kind && left.text === right.text && nullableBytesEqual(left.binary, right.binary) && nullableMetadataEqual(left.metadata, right.metadata);
1271
+ }
1272
+ function nullableBytesEqual(left, right) {
1273
+ if (left === null || right === null) return left === right;
1274
+ return bytesEqual(left, right);
1275
+ }
1276
+ function nullableMetadataEqual(left, right) {
1277
+ if (left === null || right === null) return left === right;
1278
+ return left.name === right.name && left.path === right.path && left.size === right.size && left.lastModified === right.lastModified;
1279
+ }
1280
+ function legacyPayloadsMatchEntry(legacy, entry) {
1281
+ if (legacy.content === null && legacy.binary === null) return false;
1282
+ return (legacy.content === null || bytesEqual(new TextEncoder().encode(legacy.content), entry.data)) && (legacy.binary === null || bytesEqual(legacy.binary, entry.data));
1283
+ }
1284
+ function hasFileAncestor(entries, path) {
1285
+ return parentChain(path).some((parent) => entries.get(parent)?.kind === "file");
1286
+ }
1287
+ async function deleteLegacyNamespace(transaction, keys) {
1288
+ for (const key of keys) {
1289
+ if (key.startsWith(LEGACY_PREFIX) && !isRecognizedLegacyKey(key)) {
1290
+ const archiveKey = `${LEGACY_OPAQUE_PREFIX}${encodeURIComponent(key)}`;
1291
+ const existing = await transaction.get(archiveKey);
1292
+ const values = isRecord(existing) && existing.schemaVersion === 1 && existing.key === key && Array.isArray(existing.values) ? existing.values : [];
1293
+ await transaction.put(archiveKey, {
1294
+ schemaVersion: 1,
1295
+ key,
1296
+ values: [...values, await transaction.get(key)]
1297
+ });
1298
+ }
1299
+ if (key.startsWith(LEGACY_PREFIX) || key.startsWith(LEGACY_IGNORED_PREFIX))
1300
+ await transaction.delete(key);
1301
+ }
1302
+ }
1303
+ function isRecognizedLegacyKey(key) {
1304
+ if (key === LEGACY_DIRS_KEY) return true;
1305
+ const suffix = [LEGACY_CONTENT_SUFFIX, LEGACY_BINARY_SUFFIX, LEGACY_META_SUFFIX].find(
1306
+ (item) => key.endsWith(item)
1307
+ );
1308
+ if (!suffix) return false;
1309
+ const rawPath = key.slice(LEGACY_PREFIX.length, -suffix.length);
1310
+ try {
1311
+ const path = parseWorkspacePath(rawPath);
1312
+ return path !== WORKSPACE_ROOT && path === rawPath;
1313
+ } catch {
1314
+ return false;
1315
+ }
1316
+ }
1317
+ function selectLegacyPayload(conflict, resolution, path) {
1318
+ const payload = typeof resolution === "string" ? resolution : resolution.payload;
1319
+ const eligible = typeof resolution === "string" ? conflict.candidates.filter(
1320
+ (candidate2) => candidate2.kind === "file" && (payload === "text" ? candidate2.text !== null : candidate2.binary !== null)
1321
+ ) : [conflict.candidates[resolution.candidate]].filter(
1322
+ (candidate2) => candidate2 !== void 0
1323
+ );
1324
+ if (eligible.length !== 1) {
1325
+ throw new FsError(
1326
+ "conflict",
1327
+ "Choose an explicit recovery candidate because multiple legacy versions are quarantined.",
1328
+ { operation: "write", path, retryable: false }
1329
+ );
1330
+ }
1331
+ const candidate = eligible[0];
1332
+ if (payload === "directory") {
1333
+ if (candidate.kind !== "directory") {
1334
+ throw new FsError("type-mismatch", "The selected recovery candidate is not a directory.", {
1335
+ operation: "write",
1336
+ path
1337
+ });
1338
+ }
1339
+ return { kind: "directory" };
1340
+ }
1341
+ if (candidate.kind !== "file") {
1342
+ throw new FsError("type-mismatch", "The selected recovery candidate is a directory.", {
1343
+ operation: "write",
1344
+ path
1345
+ });
1346
+ }
1347
+ const data = payload === "text" ? candidate.text === null ? null : new TextEncoder().encode(candidate.text).buffer : candidate.binary;
1348
+ if (data === null) {
1349
+ throw new FsError("type-mismatch", `The selected candidate has no ${payload} payload.`, {
1350
+ operation: "write",
1351
+ path
1352
+ });
1353
+ }
1354
+ return { kind: "file", data: copyBytes(data) };
1355
+ }
1356
+ function addParentDirectories(entries, path, version, lastModified) {
1357
+ for (const parent of parentChain(path)) {
1358
+ if (!entries.has(parent)) entries.set(parent, directoryRecord(parent, version, lastModified));
1359
+ }
1360
+ }
1361
+ function parentChain(path) {
1362
+ const parents = [];
1363
+ let current = workspacePathDirname(path);
1364
+ while (true) {
1365
+ parents.unshift(current);
1366
+ if (!current) return parents;
1367
+ current = workspacePathDirname(current);
1368
+ }
1369
+ }
1370
+ function directoryRecord(path, version, lastModified) {
1371
+ return {
1372
+ schemaVersion: ENTRY_SCHEMA_VERSION,
1373
+ kind: "directory",
1374
+ path,
1375
+ version,
1376
+ lastModified
1377
+ };
1378
+ }
1379
+ function withPathAndVersion(entry, path, next) {
1380
+ return entry.kind === "file" ? {
1381
+ schemaVersion: ENTRY_SCHEMA_VERSION,
1382
+ kind: "file",
1383
+ path,
1384
+ data: copyBytes(entry.data),
1385
+ version: next.version,
1386
+ lastModified: next.lastModified
1387
+ } : directoryRecord(path, next.version, next.lastModified);
1388
+ }
1389
+ function copyEntry(entry) {
1390
+ return entry.kind === "file" ? { ...entry, data: copyBytes(entry.data) } : { ...entry };
1391
+ }
1392
+ function toEntrySnapshot(entry) {
1393
+ return entry.kind === "file" ? toFileSnapshot(entry) : toDirectorySnapshot(entry);
1394
+ }
1395
+ function toFileSnapshot(entry) {
1396
+ return Object.freeze({
1397
+ kind: "file",
1398
+ path: parseWorkspacePath(entry.path),
1399
+ name: workspacePathBasename(parseWorkspacePath(entry.path)),
1400
+ size: entry.data.byteLength,
1401
+ version: parseFileSystemVersion(entry.version),
1402
+ lastModified: entry.lastModified
1403
+ });
1404
+ }
1405
+ function toDirectorySnapshot(entry) {
1406
+ const path = parseWorkspacePath(entry.path);
1407
+ return Object.freeze({
1408
+ kind: "directory",
1409
+ path,
1410
+ name: workspacePathBasename(path),
1411
+ size: null,
1412
+ version: parseFileSystemVersion(entry.version),
1413
+ lastModified: entry.lastModified
1414
+ });
1415
+ }
1416
+ function compareSnapshots(left, right) {
1417
+ if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
1418
+ return compareText(left.name, right.name);
1419
+ }
1420
+ function compareText(left, right) {
1421
+ return left < right ? -1 : left > right ? 1 : 0;
1422
+ }
1423
+ function copyBytes(data) {
1424
+ const source = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
1425
+ const copy = new Uint8Array(source.byteLength);
1426
+ copy.set(source);
1427
+ return copy.buffer;
1428
+ }
1429
+ function validateBytes(value, path) {
1430
+ if (value instanceof ArrayBuffer) return copyBytes(value);
1431
+ if (value instanceof Uint8Array) return copyBytes(value);
1432
+ throw corrupt(`Stored file bytes are corrupt: ${path}.`, path);
1433
+ }
1434
+ function readString(value, label) {
1435
+ if (typeof value !== "string") throw corrupt(`Invalid ${label}.`);
1436
+ return value;
1437
+ }
1438
+ function isRecord(value) {
1439
+ return typeof value === "object" && value !== null;
1440
+ }
1441
+ function corrupt(message, path) {
1442
+ return new FsError("io", message, { operation: "read", path, retryable: false });
1443
+ }
1444
+
1445
+ // src/filesystem/indexeddb-provider.ts
1446
+ var IndexedDBFileSystemProvider = class {
1447
+ constructor(id, label, options = {}) {
1448
+ this.disposed = false;
1449
+ this.id = id;
1450
+ this.label = label;
1451
+ const store = options.store ?? new RawIndexedDBFileSystemStore(`docblocks-fs-${id}`, "files");
1452
+ this.v2 = new IndexedDBFileSystemProviderV2(id, label, {
1453
+ store,
1454
+ now: options.now,
1455
+ closeStoreOnDispose: true
1456
+ });
1457
+ }
1458
+ /** Release the underlying database connection after accepted work settles. */
1459
+ dispose() {
1460
+ this.disposed = true;
1461
+ return this.v2.dispose();
1462
+ }
1463
+ async readFile(path) {
1464
+ this.assertOpen("read");
1465
+ const read = await this.v2.readFile(this.path(path, false, "read"));
1466
+ return read ? new TextDecoder().decode(read.data) : null;
1467
+ }
1468
+ async writeFile(path, content) {
1469
+ this.assertOpen("write");
1470
+ await this.v2.writeFile(this.path(path, false, "write"), new TextEncoder().encode(content), {
1471
+ createParents: true
1472
+ });
1473
+ }
1474
+ async commitFile(path, content, expectedContent) {
1475
+ this.assertOpen("write");
1476
+ const canonical = this.path(path, false, "write");
1477
+ const current = await this.v2.readFile(canonical);
1478
+ const currentContent = current ? new TextDecoder().decode(current.data) : null;
1479
+ if (currentContent !== expectedContent && currentContent !== content) {
1480
+ return {
1481
+ status: "conflict",
1482
+ content: currentContent,
1483
+ version: current?.entry.version ?? null
1484
+ };
1485
+ }
1486
+ try {
1487
+ const committed = await this.v2.writeFile(canonical, new TextEncoder().encode(content), {
1488
+ createParents: true,
1489
+ expectedVersion: current?.entry.version ?? null
1490
+ });
1491
+ return { status: "committed", version: committed.version };
1492
+ } catch (error) {
1493
+ if (!(error instanceof FsError) || error.code !== "conflict") throw error;
1494
+ const latest = await this.v2.readFile(canonical);
1495
+ return {
1496
+ status: "conflict",
1497
+ content: latest ? new TextDecoder().decode(latest.data) : null,
1498
+ version: latest?.entry.version ?? null
1499
+ };
1500
+ }
1501
+ }
1502
+ async delete(path) {
1503
+ this.assertOpen("remove");
1504
+ await this.v2.remove(this.path(path, false, "remove"), {
1505
+ recursive: true,
1506
+ missing: "ignore"
1507
+ });
1508
+ }
1509
+ async rename(oldPath, newPath) {
1510
+ this.assertOpen("move");
1511
+ await this.v2.move(this.path(oldPath, false, "move"), this.path(newPath, false, "move"), {
1512
+ createParents: true
1513
+ });
1514
+ }
1515
+ async readDirectory(path) {
1516
+ this.assertOpen("list");
1517
+ try {
1518
+ const entries = await this.v2.readDirectory(this.path(path, true, "list"));
1519
+ return entries.map((entry) => ({
1520
+ kind: entry.kind,
1521
+ name: entry.name,
1522
+ path: entry.path
1523
+ }));
1524
+ } catch (error) {
1525
+ if (error instanceof FsError && error.code === "not-found") return [];
1526
+ throw error;
1527
+ }
1528
+ }
1529
+ async exists(path) {
1530
+ this.assertOpen("stat");
1531
+ return await this.v2.stat(this.path(path, true, "stat")) !== null;
1532
+ }
1533
+ async createDirectory(path) {
1534
+ this.assertOpen("create-directory");
1535
+ await this.v2.createDirectory(this.path(path, false, "create-directory"), {
1536
+ createParents: true,
1537
+ mode: "ensure"
1538
+ });
1539
+ }
1540
+ async stat(path) {
1541
+ this.assertOpen("stat");
1542
+ const entry = await this.v2.stat(this.path(path, false, "stat"));
1543
+ if (!entry || entry.kind !== "file") return null;
1544
+ return {
1545
+ name: entry.name,
1546
+ path: entry.path,
1547
+ size: entry.size,
1548
+ lastModified: entry.lastModified
1549
+ };
1550
+ }
1551
+ async readBinary(path) {
1552
+ this.assertOpen("read");
1553
+ return (await this.v2.readFile(this.path(path, false, "read")))?.data ?? null;
1554
+ }
1555
+ async writeBinary(path, data) {
1556
+ this.assertOpen("write");
1557
+ await this.v2.writeFile(this.path(path, false, "write"), data, { createParents: true });
1558
+ }
1559
+ path(path, allowRoot, operation) {
1560
+ const canonical = parseWorkspacePath(path);
1561
+ if (canonical || allowRoot) return canonical;
1562
+ throw new FsError("invalid-path", "The filesystem root cannot be used as a mutation target.", {
1563
+ operation,
1564
+ path
1565
+ });
1566
+ }
1567
+ assertOpen(operation) {
1568
+ if (!this.disposed) return;
1569
+ throw new FsError("closed", "The IndexedDB filesystem provider is disposed.", { operation });
1570
+ }
1571
+ };
1572
+
1573
+ // src/filesystem/indexeddb-content-container.ts
1574
+ import { findDocumentPath } from "@bendyline/squisq/storage";
1575
+ var EXTENSION_MIME_MAP = {
1576
+ ".md": "text/markdown",
1577
+ ".txt": "text/plain",
1578
+ ".json": "application/json",
1579
+ ".jpg": "image/jpeg",
1580
+ ".jpeg": "image/jpeg",
1581
+ ".png": "image/png",
1582
+ ".gif": "image/gif",
1583
+ ".svg": "image/svg+xml",
1584
+ ".webp": "image/webp",
1585
+ ".avif": "image/avif",
1586
+ ".mp4": "video/mp4",
1587
+ ".webm": "video/webm",
1588
+ ".mp3": "audio/mpeg",
1589
+ ".wav": "audio/wav",
1590
+ ".ogg": "audio/ogg"
1591
+ };
1592
+ function guessMimeType(path) {
1593
+ const dot = path.lastIndexOf(".");
1594
+ if (dot === -1) return "application/octet-stream";
1595
+ const ext = path.slice(dot).toLowerCase();
1596
+ return EXTENSION_MIME_MAP[ext] ?? "application/octet-stream";
1597
+ }
1598
+ var IndexedDBContentContainer = class {
1599
+ constructor(workspaceId) {
1600
+ this.provider = new IndexedDBFileSystemProviderV2(`${workspaceId}-media`, "Media Storage");
1601
+ }
1602
+ async readFile(path) {
1603
+ return (await this.provider.readFile(parseWorkspacePath(path)))?.data ?? null;
1604
+ }
1605
+ async writeFile(path, data, _mimeType) {
1606
+ await this.provider.writeFile(parseWorkspacePath(path), data, {
1607
+ mode: "upsert",
1608
+ createParents: true
1609
+ });
1610
+ }
1611
+ async removeFile(path) {
1612
+ await this.provider.remove(parseWorkspacePath(path), { missing: "ignore" });
1613
+ }
1614
+ async listFiles(prefix) {
1615
+ const entries = [];
1616
+ const walk = async (dir) => {
1617
+ const children = await this.provider.readDirectory(parseWorkspacePath(dir));
1618
+ for (const child of children) {
1619
+ if (child.kind === "directory") {
1620
+ await walk(child.path);
1621
+ } else {
1622
+ const filePath = child.path;
1623
+ if (prefix && !filePath.startsWith(prefix)) continue;
1624
+ entries.push({
1625
+ path: filePath,
1626
+ mimeType: guessMimeType(filePath),
1627
+ size: child.size
1628
+ });
1629
+ }
1630
+ }
1631
+ };
1632
+ await walk(WORKSPACE_ROOT);
1633
+ return entries;
1634
+ }
1635
+ async exists(path) {
1636
+ return await this.provider.stat(parseWorkspacePath(path)) !== null;
1637
+ }
1638
+ async getDocumentPath() {
1639
+ return findDocumentPath(await this.listFiles());
1640
+ }
1641
+ async readDocument() {
1642
+ const docPath = await this.getDocumentPath();
1643
+ if (!docPath) return null;
1644
+ const data = await this.readFile(docPath);
1645
+ if (!data) return null;
1646
+ return new TextDecoder().decode(data);
1647
+ }
1648
+ async writeDocument(markdown, filename) {
1649
+ const name = filename ?? "index.md";
1650
+ const data = new TextEncoder().encode(markdown);
1651
+ await this.writeFile(name, data, "text/markdown");
1652
+ }
1653
+ /** Release the IndexedDB connection when a long-lived container is retired. */
1654
+ async dispose() {
1655
+ await this.provider.dispose();
1656
+ }
1657
+ };
1658
+
1659
+ export {
1660
+ IndexedDBFileSystemProviderV2,
1661
+ IndexedDBFileSystemProvider,
1662
+ IndexedDBContentContainer
1663
+ };
1664
+ //# sourceMappingURL=chunk-J32GU3F3.js.map