@bendyline/docblocks 2.0.0 → 2.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 (286) hide show
  1. package/dist/{chunk-J32GU3F3.js → chunk-6IYMTYMQ.js} +249 -221
  2. package/dist/chunk-6IYMTYMQ.js.map +1 -0
  3. package/dist/{chunk-75UUF6C4.js → chunk-B56KWCSE.js} +2 -2
  4. package/dist/{chunk-RVQHY3XU.js → chunk-BNKV7MTO.js} +1 -1
  5. package/dist/{chunk-RVQHY3XU.js.map → chunk-BNKV7MTO.js.map} +1 -1
  6. package/dist/chunk-BXMF5YQJ.js +149 -0
  7. package/dist/chunk-BXMF5YQJ.js.map +1 -0
  8. package/dist/{chunk-PGR7LK6D.js → chunk-COYQXQK3.js} +14 -5
  9. package/dist/chunk-COYQXQK3.js.map +1 -0
  10. package/dist/{chunk-RQJRQF46.js → chunk-DN7A33LL.js} +42 -49
  11. package/dist/chunk-DN7A33LL.js.map +1 -0
  12. package/dist/chunk-FA7UNOXK.js +20 -0
  13. package/dist/chunk-FA7UNOXK.js.map +1 -0
  14. package/dist/chunk-GM5YXU4L.js +29 -0
  15. package/dist/chunk-GM5YXU4L.js.map +1 -0
  16. package/dist/{chunk-SI2LU2RP.js → chunk-JS6XFWQK.js} +2 -1
  17. package/dist/chunk-JS6XFWQK.js.map +1 -0
  18. package/dist/{chunk-EGNDADXB.js → chunk-MNSZ7EZN.js} +2 -2
  19. package/dist/chunk-P3DVZQ6S.js +228 -0
  20. package/dist/chunk-P3DVZQ6S.js.map +1 -0
  21. package/dist/chunk-P4WATND4.js +52 -0
  22. package/dist/chunk-P4WATND4.js.map +1 -0
  23. package/dist/chunk-RVPGQBWR.js +169 -0
  24. package/dist/chunk-RVPGQBWR.js.map +1 -0
  25. package/dist/chunk-U722UUFN.js +9 -0
  26. package/dist/chunk-U722UUFN.js.map +1 -0
  27. package/dist/{chunk-GCPBEMMH.js → chunk-VTCSF7VS.js} +11 -21
  28. package/dist/chunk-VTCSF7VS.js.map +1 -0
  29. package/dist/{chunk-2D7Y4IR3.js → chunk-WJKYY5QR.js} +123 -66
  30. package/dist/chunk-WJKYY5QR.js.map +1 -0
  31. package/dist/{chunk-5MYAICRE.js → chunk-XUML3MKV.js} +44 -56
  32. package/dist/chunk-XUML3MKV.js.map +1 -0
  33. package/dist/document/index.d.ts +358 -6
  34. package/dist/document/index.js +24 -3
  35. package/dist/document/index.js.map +1 -1
  36. package/dist/filesystem/electron.d.ts +73 -4
  37. package/dist/filesystem/electron.js +16 -3
  38. package/dist/filesystem/electron.js.map +1 -1
  39. package/dist/filesystem/index.d.ts +228 -22
  40. package/dist/filesystem/index.js +133 -19
  41. package/dist/filesystem/index.js.map +1 -1
  42. package/dist/filesystem/indexeddb.d.ts +284 -5
  43. package/dist/filesystem/indexeddb.js +22 -4
  44. package/dist/filesystem/indexeddb.js.map +1 -1
  45. package/dist/filesystem/memory.d.ts +152 -4
  46. package/dist/filesystem/memory.js +12 -3
  47. package/dist/filesystem/memory.js.map +1 -1
  48. package/dist/filesystem/native.d.ts +136 -4
  49. package/dist/filesystem/native.js +27 -3
  50. package/dist/filesystem/native.js.map +1 -1
  51. package/dist/host/index.d.ts +32 -9
  52. package/dist/host/index.js +26 -29
  53. package/dist/host/index.js.map +1 -1
  54. package/dist/index.d.ts +14 -10
  55. package/dist/index.js +204 -9
  56. package/dist/index.js.map +1 -1
  57. package/dist/mcp/index.d.ts +33 -4
  58. package/dist/mcp/index.js +31 -3
  59. package/dist/mcp/index.js.map +1 -1
  60. package/dist/mcp/zod.d.ts +637 -635
  61. package/dist/mcp/zod.js +700 -884
  62. package/dist/mcp/zod.js.map +1 -1
  63. package/dist/share/index.d.ts +43 -0
  64. package/dist/share/index.js +13 -0
  65. package/dist/share/index.js.map +1 -0
  66. package/dist/{types-CsX6EN2t.d.ts → types-BEg5SQ1_.d.ts} +1 -1
  67. package/dist/{types-eOlt9J7n.d.ts → types-CueK0tvU.d.ts} +34 -11
  68. package/dist/vscode/index.d.ts +220 -3
  69. package/dist/vscode/index.js +382 -2
  70. package/dist/vscode/index.js.map +1 -1
  71. package/dist/workspace/index.d.ts +141 -4
  72. package/dist/workspace/index.js +28 -2
  73. package/dist/workspace/index.js.map +1 -1
  74. package/package.json +9 -5
  75. package/dist/chunk-2D7Y4IR3.js.map +0 -1
  76. package/dist/chunk-5MYAICRE.js.map +0 -1
  77. package/dist/chunk-AYZ4HN5M.js +0 -20
  78. package/dist/chunk-AYZ4HN5M.js.map +0 -1
  79. package/dist/chunk-GCPBEMMH.js.map +0 -1
  80. package/dist/chunk-J32GU3F3.js.map +0 -1
  81. package/dist/chunk-PGR7LK6D.js.map +0 -1
  82. package/dist/chunk-RQJRQF46.js.map +0 -1
  83. package/dist/chunk-SI2LU2RP.js.map +0 -1
  84. package/dist/chunk-W5UUUII5.js +0 -142
  85. package/dist/chunk-W5UUUII5.js.map +0 -1
  86. package/dist/document/document-session.d.ts +0 -127
  87. package/dist/document/document-session.d.ts.map +0 -1
  88. package/dist/document/document-session.js +0 -749
  89. package/dist/document/document-session.js.map +0 -1
  90. package/dist/document/filesystem-target.d.ts +0 -17
  91. package/dist/document/filesystem-target.d.ts.map +0 -1
  92. package/dist/document/filesystem-target.js +0 -70
  93. package/dist/document/filesystem-target.js.map +0 -1
  94. package/dist/document/index.d.ts.map +0 -1
  95. package/dist/document/recovery-journal.d.ts +0 -125
  96. package/dist/document/recovery-journal.d.ts.map +0 -1
  97. package/dist/document/recovery-journal.js +0 -385
  98. package/dist/document/recovery-journal.js.map +0 -1
  99. package/dist/document/types.d.ts +0 -89
  100. package/dist/document/types.d.ts.map +0 -1
  101. package/dist/document/types.js +0 -9
  102. package/dist/document/types.js.map +0 -1
  103. package/dist/filesystem/commit-lock.d.ts +0 -3
  104. package/dist/filesystem/commit-lock.d.ts.map +0 -1
  105. package/dist/filesystem/commit-lock.js +0 -20
  106. package/dist/filesystem/commit-lock.js.map +0 -1
  107. package/dist/filesystem/dbk-workspace.d.ts +0 -34
  108. package/dist/filesystem/dbk-workspace.d.ts.map +0 -1
  109. package/dist/filesystem/dbk-workspace.js +0 -85
  110. package/dist/filesystem/dbk-workspace.js.map +0 -1
  111. package/dist/filesystem/electron-provider-v2.d.ts +0 -36
  112. package/dist/filesystem/electron-provider-v2.d.ts.map +0 -1
  113. package/dist/filesystem/electron-provider-v2.js +0 -503
  114. package/dist/filesystem/electron-provider-v2.js.map +0 -1
  115. package/dist/filesystem/electron-provider.d.ts +0 -35
  116. package/dist/filesystem/electron-provider.d.ts.map +0 -1
  117. package/dist/filesystem/electron-provider.js +0 -69
  118. package/dist/filesystem/electron-provider.js.map +0 -1
  119. package/dist/filesystem/electron.d.ts.map +0 -1
  120. package/dist/filesystem/file-media-provider.d.ts +0 -23
  121. package/dist/filesystem/file-media-provider.d.ts.map +0 -1
  122. package/dist/filesystem/file-media-provider.js +0 -85
  123. package/dist/filesystem/file-media-provider.js.map +0 -1
  124. package/dist/filesystem/filesystem-content-container.d.ts +0 -24
  125. package/dist/filesystem/filesystem-content-container.d.ts.map +0 -1
  126. package/dist/filesystem/filesystem-content-container.js +0 -153
  127. package/dist/filesystem/filesystem-content-container.js.map +0 -1
  128. package/dist/filesystem/fs-error.d.ts +0 -46
  129. package/dist/filesystem/fs-error.d.ts.map +0 -1
  130. package/dist/filesystem/fs-error.js +0 -170
  131. package/dist/filesystem/fs-error.js.map +0 -1
  132. package/dist/filesystem/index.d.ts.map +0 -1
  133. package/dist/filesystem/indexeddb-content-container.d.ts +0 -22
  134. package/dist/filesystem/indexeddb-content-container.d.ts.map +0 -1
  135. package/dist/filesystem/indexeddb-content-container.js +0 -100
  136. package/dist/filesystem/indexeddb-content-container.js.map +0 -1
  137. package/dist/filesystem/indexeddb-provider-v2.d.ts +0 -96
  138. package/dist/filesystem/indexeddb-provider-v2.d.ts.map +0 -1
  139. package/dist/filesystem/indexeddb-provider-v2.js +0 -1241
  140. package/dist/filesystem/indexeddb-provider-v2.js.map +0 -1
  141. package/dist/filesystem/indexeddb-provider.d.ts +0 -39
  142. package/dist/filesystem/indexeddb-provider.d.ts.map +0 -1
  143. package/dist/filesystem/indexeddb-provider.js +0 -145
  144. package/dist/filesystem/indexeddb-provider.js.map +0 -1
  145. package/dist/filesystem/indexeddb-store.d.ts +0 -34
  146. package/dist/filesystem/indexeddb-store.d.ts.map +0 -1
  147. package/dist/filesystem/indexeddb-store.js +0 -165
  148. package/dist/filesystem/indexeddb-store.js.map +0 -1
  149. package/dist/filesystem/indexeddb.d.ts.map +0 -1
  150. package/dist/filesystem/legacy-v2-adapter.d.ts +0 -62
  151. package/dist/filesystem/legacy-v2-adapter.d.ts.map +0 -1
  152. package/dist/filesystem/legacy-v2-adapter.js +0 -499
  153. package/dist/filesystem/legacy-v2-adapter.js.map +0 -1
  154. package/dist/filesystem/memory-provider-v2.d.ts +0 -92
  155. package/dist/filesystem/memory-provider-v2.d.ts.map +0 -1
  156. package/dist/filesystem/memory-provider-v2.js +0 -615
  157. package/dist/filesystem/memory-provider-v2.js.map +0 -1
  158. package/dist/filesystem/memory-provider.d.ts +0 -54
  159. package/dist/filesystem/memory-provider.d.ts.map +0 -1
  160. package/dist/filesystem/memory-provider.js +0 -132
  161. package/dist/filesystem/memory-provider.js.map +0 -1
  162. package/dist/filesystem/memory.d.ts.map +0 -1
  163. package/dist/filesystem/move-entry.d.ts +0 -9
  164. package/dist/filesystem/move-entry.d.ts.map +0 -1
  165. package/dist/filesystem/move-entry.js +0 -103
  166. package/dist/filesystem/move-entry.js.map +0 -1
  167. package/dist/filesystem/move-error.d.ts +0 -53
  168. package/dist/filesystem/move-error.d.ts.map +0 -1
  169. package/dist/filesystem/move-error.js +0 -99
  170. package/dist/filesystem/move-error.js.map +0 -1
  171. package/dist/filesystem/native-provider-v2.d.ts +0 -75
  172. package/dist/filesystem/native-provider-v2.d.ts.map +0 -1
  173. package/dist/filesystem/native-provider-v2.js +0 -849
  174. package/dist/filesystem/native-provider-v2.js.map +0 -1
  175. package/dist/filesystem/native-provider.d.ts +0 -53
  176. package/dist/filesystem/native-provider.d.ts.map +0 -1
  177. package/dist/filesystem/native-provider.js +0 -471
  178. package/dist/filesystem/native-provider.js.map +0 -1
  179. package/dist/filesystem/native.d.ts.map +0 -1
  180. package/dist/filesystem/types.d.ts +0 -83
  181. package/dist/filesystem/types.d.ts.map +0 -1
  182. package/dist/filesystem/types.js +0 -16
  183. package/dist/filesystem/types.js.map +0 -1
  184. package/dist/filesystem/v2.d.ts +0 -172
  185. package/dist/filesystem/v2.d.ts.map +0 -1
  186. package/dist/filesystem/v2.js +0 -16
  187. package/dist/filesystem/v2.js.map +0 -1
  188. package/dist/filesystem/workspace-path.d.ts +0 -21
  189. package/dist/filesystem/workspace-path.d.ts.map +0 -1
  190. package/dist/filesystem/workspace-path.js +0 -80
  191. package/dist/filesystem/workspace-path.js.map +0 -1
  192. package/dist/host/filesystem-v2.d.ts +0 -45
  193. package/dist/host/filesystem-v2.d.ts.map +0 -1
  194. package/dist/host/filesystem-v2.js +0 -13
  195. package/dist/host/filesystem-v2.js.map +0 -1
  196. package/dist/host/git.d.ts +0 -200
  197. package/dist/host/git.d.ts.map +0 -1
  198. package/dist/host/git.js +0 -15
  199. package/dist/host/git.js.map +0 -1
  200. package/dist/host/index.d.ts.map +0 -1
  201. package/dist/host/types.d.ts +0 -232
  202. package/dist/host/types.d.ts.map +0 -1
  203. package/dist/host/types.js +0 -10
  204. package/dist/host/types.js.map +0 -1
  205. package/dist/host/wire-policy.d.ts +0 -22
  206. package/dist/host/wire-policy.d.ts.map +0 -1
  207. package/dist/host/wire-policy.js +0 -92
  208. package/dist/host/wire-policy.js.map +0 -1
  209. package/dist/index.d.ts.map +0 -1
  210. package/dist/mcp/index.d.ts.map +0 -1
  211. package/dist/mcp/types.d.ts +0 -466
  212. package/dist/mcp/types.d.ts.map +0 -1
  213. package/dist/mcp/types.js +0 -25
  214. package/dist/mcp/types.js.map +0 -1
  215. package/dist/mcp/wire.d.ts +0 -29
  216. package/dist/mcp/wire.d.ts.map +0 -1
  217. package/dist/mcp/wire.js +0 -1046
  218. package/dist/mcp/wire.js.map +0 -1
  219. package/dist/mcp/zod.d.ts.map +0 -1
  220. package/dist/vscode/index.d.ts.map +0 -1
  221. package/dist/vscode/messages.d.ts +0 -204
  222. package/dist/vscode/messages.d.ts.map +0 -1
  223. package/dist/vscode/messages.js +0 -494
  224. package/dist/vscode/messages.js.map +0 -1
  225. package/dist/workspace/index.d.ts.map +0 -1
  226. package/dist/workspace/reconcile-electron.d.ts +0 -19
  227. package/dist/workspace/reconcile-electron.d.ts.map +0 -1
  228. package/dist/workspace/reconcile-electron.js +0 -42
  229. package/dist/workspace/reconcile-electron.js.map +0 -1
  230. package/dist/workspace/types.d.ts +0 -69
  231. package/dist/workspace/types.d.ts.map +0 -1
  232. package/dist/workspace/types.js +0 -2
  233. package/dist/workspace/types.js.map +0 -1
  234. package/dist/workspace/workspace-manager.d.ts +0 -47
  235. package/dist/workspace/workspace-manager.d.ts.map +0 -1
  236. package/dist/workspace/workspace-manager.js +0 -126
  237. package/dist/workspace/workspace-manager.js.map +0 -1
  238. package/src/document/document-session.ts +0 -834
  239. package/src/document/filesystem-target.ts +0 -109
  240. package/src/document/index.ts +0 -41
  241. package/src/document/recovery-journal.ts +0 -536
  242. package/src/document/types.ts +0 -116
  243. package/src/filesystem/commit-lock.ts +0 -29
  244. package/src/filesystem/dbk-workspace.ts +0 -135
  245. package/src/filesystem/electron-provider-v2.ts +0 -630
  246. package/src/filesystem/electron-provider.ts +0 -99
  247. package/src/filesystem/electron.ts +0 -3
  248. package/src/filesystem/file-media-provider.ts +0 -103
  249. package/src/filesystem/filesystem-content-container.ts +0 -164
  250. package/src/filesystem/fs-error.ts +0 -234
  251. package/src/filesystem/index.ts +0 -143
  252. package/src/filesystem/indexeddb-content-container.ts +0 -112
  253. package/src/filesystem/indexeddb-provider-v2.ts +0 -1740
  254. package/src/filesystem/indexeddb-provider.ts +0 -171
  255. package/src/filesystem/indexeddb-store.ts +0 -207
  256. package/src/filesystem/indexeddb.ts +0 -12
  257. package/src/filesystem/legacy-v2-adapter.ts +0 -779
  258. package/src/filesystem/memory-provider-v2.ts +0 -896
  259. package/src/filesystem/memory-provider.ts +0 -180
  260. package/src/filesystem/memory.ts +0 -15
  261. package/src/filesystem/move-entry.ts +0 -142
  262. package/src/filesystem/move-error.ts +0 -159
  263. package/src/filesystem/native-provider-v2.ts +0 -1165
  264. package/src/filesystem/native-provider.ts +0 -537
  265. package/src/filesystem/native.ts +0 -16
  266. package/src/filesystem/types.ts +0 -114
  267. package/src/filesystem/v2.ts +0 -231
  268. package/src/filesystem/workspace-path.ts +0 -92
  269. package/src/host/filesystem-v2.ts +0 -104
  270. package/src/host/git.ts +0 -246
  271. package/src/host/index.ts +0 -93
  272. package/src/host/types.ts +0 -274
  273. package/src/host/wire-policy.ts +0 -103
  274. package/src/index.ts +0 -10
  275. package/src/mcp/index.ts +0 -3
  276. package/src/mcp/types.ts +0 -594
  277. package/src/mcp/wire.ts +0 -1280
  278. package/src/mcp/zod.ts +0 -1000
  279. package/src/vscode/index.ts +0 -2
  280. package/src/vscode/messages.ts +0 -704
  281. package/src/workspace/index.ts +0 -17
  282. package/src/workspace/reconcile-electron.ts +0 -65
  283. package/src/workspace/types.ts +0 -67
  284. package/src/workspace/workspace-manager.ts +0 -151
  285. /package/dist/{chunk-75UUF6C4.js.map → chunk-B56KWCSE.js.map} +0 -0
  286. /package/dist/{chunk-EGNDADXB.js.map → chunk-MNSZ7EZN.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/filesystem/fs-error.ts","../src/filesystem/workspace-path.ts"],"sourcesContent":["/** Stable error codes shared by every filesystem transport and backend. */\nexport type FsErrorCode =\n | 'invalid-path'\n | 'path-escape'\n | 'not-found'\n | 'already-exists'\n | 'type-mismatch'\n | 'not-empty'\n | 'permission-denied'\n | 'conflict'\n | 'corrupt'\n | 'quota-exceeded'\n | 'not-supported'\n | 'busy'\n | 'disposed'\n | 'closed'\n | 'aborted'\n | 'io';\n\nexport type FsOperation =\n | 'parse-path'\n | 'read'\n | 'stat'\n | 'list'\n | 'write'\n | 'create-directory'\n | 'remove'\n | 'move'\n | 'snapshot'\n | 'watch'\n | 'dispose';\n\n/** Plain-data form safe to send over IPC, postMessage, or JSON. */\nexport interface SerializedFsError {\n readonly name: 'FsError';\n readonly code: FsErrorCode;\n readonly message: string;\n readonly operation: FsOperation | null;\n readonly path: string | null;\n readonly destinationPath: string | null;\n readonly retryable: boolean;\n}\n\nexport interface FsErrorContext {\n readonly operation?: FsOperation;\n readonly path?: string;\n readonly destinationPath?: string;\n readonly retryable?: boolean;\n readonly defaultCode?: FsErrorCode;\n}\n\nconst FS_ERROR_CODES: ReadonlySet<string> = new Set<FsErrorCode>([\n 'invalid-path',\n 'path-escape',\n 'not-found',\n 'already-exists',\n 'type-mismatch',\n 'not-empty',\n 'permission-denied',\n 'conflict',\n 'corrupt',\n 'quota-exceeded',\n 'not-supported',\n 'busy',\n 'disposed',\n 'closed',\n 'aborted',\n 'io',\n]);\n\nconst FS_OPERATIONS: ReadonlySet<string> = new Set<FsOperation>([\n 'parse-path',\n 'read',\n 'stat',\n 'list',\n 'write',\n 'create-directory',\n 'remove',\n 'move',\n 'snapshot',\n 'watch',\n 'dispose',\n]);\n\n/** Error with a stable code and a deliberately serializable public shape. */\nexport class FsError extends Error {\n public readonly code: FsErrorCode;\n public readonly operation: FsOperation | null;\n public readonly path: string | null;\n public readonly destinationPath: string | null;\n public readonly retryable: boolean;\n\n public constructor(code: FsErrorCode, message: string, context: FsErrorContext = {}) {\n super(message);\n this.name = 'FsError';\n this.code = code;\n this.operation = context.operation ?? null;\n this.path = context.path ?? null;\n this.destinationPath = context.destinationPath ?? null;\n this.retryable = context.retryable ?? (code === 'io' || code === 'aborted' || code === 'busy');\n }\n\n public toJSON(): SerializedFsError {\n return {\n name: 'FsError',\n code: this.code,\n message: this.message,\n operation: this.operation,\n path: this.path,\n destinationPath: this.destinationPath,\n retryable: this.retryable,\n };\n }\n}\n\nexport function mapDomExceptionToFsErrorCode(name: string): FsErrorCode {\n switch (name) {\n case 'NotFoundError':\n return 'not-found';\n case 'ConstraintError':\n return 'already-exists';\n case 'TypeMismatchError':\n return 'type-mismatch';\n case 'InvalidModificationError':\n return 'not-empty';\n case 'NotAllowedError':\n case 'SecurityError':\n case 'NoModificationAllowedError':\n return 'permission-denied';\n case 'QuotaExceededError':\n return 'quota-exceeded';\n case 'NotSupportedError':\n return 'not-supported';\n case 'InvalidStateError':\n return 'disposed';\n case 'AbortError':\n return 'aborted';\n default:\n return 'io';\n }\n}\n\nexport function mapNodeErrorCodeToFsErrorCode(code: string): FsErrorCode {\n switch (code) {\n case 'ENOENT':\n return 'not-found';\n case 'ENOTDIR':\n return 'type-mismatch';\n case 'EEXIST':\n return 'already-exists';\n case 'EISDIR':\n return 'type-mismatch';\n case 'ENOTEMPTY':\n return 'not-empty';\n case 'EACCES':\n case 'EPERM':\n return 'permission-denied';\n case 'ENOSPC':\n case 'EDQUOT':\n return 'quota-exceeded';\n case 'EBUSY':\n return 'busy';\n case 'EXDEV':\n case 'ENOTSUP':\n case 'EOPNOTSUPP':\n return 'not-supported';\n default:\n return 'io';\n }\n}\n\nexport function isSerializedFsError(value: unknown): value is SerializedFsError {\n if (!value || typeof value !== 'object') return false;\n const candidate = value as Partial<Record<keyof SerializedFsError, unknown>>;\n return (\n candidate.name === 'FsError' &&\n typeof candidate.code === 'string' &&\n FS_ERROR_CODES.has(candidate.code) &&\n typeof candidate.message === 'string' &&\n (candidate.operation === null ||\n (typeof candidate.operation === 'string' && FS_OPERATIONS.has(candidate.operation))) &&\n (candidate.path === null || typeof candidate.path === 'string') &&\n (candidate.destinationPath === null || typeof candidate.destinationPath === 'string') &&\n typeof candidate.retryable === 'boolean'\n );\n}\n\nexport function deserializeFsError(value: SerializedFsError): FsError {\n return new FsError(value.code, value.message, {\n operation: value.operation ?? undefined,\n path: value.path ?? undefined,\n destinationPath: value.destinationPath ?? undefined,\n retryable: value.retryable,\n });\n}\n\nexport function fsErrorFromUnknown(error: unknown, context: FsErrorContext = {}): FsError {\n if (error instanceof FsError) return error;\n if (isSerializedFsError(error)) return deserializeFsError(error);\n\n const name = getErrorStringField(error, 'name');\n const nodeCode = getErrorStringField(error, 'code');\n const message = getErrorStringField(error, 'message') ?? String(error);\n const code =\n context.defaultCode ??\n (nodeCode\n ? mapNodeErrorCodeToFsErrorCode(nodeCode)\n : name\n ? mapDomExceptionToFsErrorCode(name)\n : 'io');\n return new FsError(code, message, context);\n}\n\nexport function serializeFsError(error: unknown, context: FsErrorContext = {}): SerializedFsError {\n return fsErrorFromUnknown(error, context).toJSON();\n}\n\n/**\n * True when a storage backend rejected an operation because the origin or\n * device is out of space. Covers typed `FsError`s (`'quota-exceeded'`, from\n * DOMException `QuotaExceededError` or Node `ENOSPC`/`EDQUOT`), serialized\n * transport copies, and raw DOMExceptions from backends that do not wrap\n * their errors (e.g. the File System Access API) — including cross-realm\n * instances where `instanceof DOMException` would fail.\n */\nexport function isQuotaExceededError(error: unknown): boolean {\n if (error instanceof FsError) return error.code === 'quota-exceeded';\n if (isSerializedFsError(error)) return error.code === 'quota-exceeded';\n return getErrorStringField(error, 'name') === 'QuotaExceededError';\n}\n\nfunction getErrorStringField(error: unknown, key: 'name' | 'message' | 'code'): string | null {\n if (!error || typeof error !== 'object') return null;\n const value = (error as Record<string, unknown>)[key];\n return typeof value === 'string' ? value : null;\n}\n","import { FsError } from './fs-error.js';\n\ndeclare const workspacePathBrand: unique symbol;\n\n/** Canonical, root-relative, forward-slash path within one workspace. */\nexport type WorkspacePath = string & { readonly [workspacePathBrand]: 'WorkspacePath' };\n\nexport const WORKSPACE_ROOT = '' as WorkspacePath;\n\n/**\n * Parse a legacy/user path into its one canonical workspace representation.\n * Leading/trailing and repeated separators are normalized; traversal and NUL\n * segments are rejected rather than resolved.\n */\nexport function parseWorkspacePath(input: string): WorkspacePath {\n if (\n [...input].some((character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint <= 0x1f || codePoint === 0x7f;\n })\n ) {\n throw invalidPath(input, 'Workspace paths must not contain NUL or other control characters.');\n }\n if (/^[a-zA-Z]:/u.test(input)) {\n throw new FsError('path-escape', 'Workspace paths must not be OS drive-qualified paths.', {\n operation: 'parse-path',\n path: input,\n });\n }\n\n const normalized = input\n .replace(/\\\\/g, '/')\n .replace(/\\/+/g, '/')\n .replace(/^\\/|\\/$/g, '');\n if (!normalized) return WORKSPACE_ROOT;\n\n const segments = normalized.split('/');\n if (segments.some((segment) => segment === '.' || segment === '..')) {\n throw new FsError('path-escape', 'Workspace paths must not contain \".\" or \"..\" segments.', {\n operation: 'parse-path',\n path: input,\n });\n }\n return normalized as WorkspacePath;\n}\n\nexport function tryParseWorkspacePath(input: unknown): WorkspacePath | null {\n if (typeof input !== 'string') return null;\n try {\n return parseWorkspacePath(input);\n } catch {\n return null;\n }\n}\n\n/** Convert a canonical path to the slash-prefixed form accepted by v1 providers. */\nexport function workspacePathToLegacy(path: WorkspacePath): string {\n return path ? `/${path}` : '/';\n}\n\nexport function workspacePathJoin(parent: WorkspacePath, child: string): WorkspacePath {\n const childPath = parseWorkspacePath(child);\n if (!parent) return childPath;\n if (!childPath) return parent;\n return parseWorkspacePath(`${parent}/${childPath}`);\n}\n\nexport function workspacePathDirname(path: WorkspacePath): WorkspacePath {\n if (!path) return WORKSPACE_ROOT;\n const slash = path.lastIndexOf('/');\n return slash < 0 ? WORKSPACE_ROOT : (path.slice(0, slash) as WorkspacePath);\n}\n\nexport function workspacePathBasename(path: WorkspacePath): string {\n if (!path) return '';\n const slash = path.lastIndexOf('/');\n return slash < 0 ? path : path.slice(slash + 1);\n}\n\nexport function workspacePathContains(\n parent: WorkspacePath,\n candidate: WorkspacePath,\n inclusive = true,\n): boolean {\n if (parent === candidate) return inclusive;\n if (!parent) return candidate !== WORKSPACE_ROOT;\n return candidate.startsWith(`${parent}/`);\n}\n\nfunction invalidPath(path: string, message: string): FsError {\n return new FsError('invalid-path', message, { operation: 'parse-path', path });\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseWorkspacePath
3
- } from "./chunk-SI2LU2RP.js";
3
+ } from "./chunk-JS6XFWQK.js";
4
4
  import {
5
5
  getFileSystemProviderV2
6
6
  } from "./chunk-EZKJZUNJ.js";
@@ -108,4 +108,4 @@ export {
108
108
  describePartialMove,
109
109
  describeEntryMove
110
110
  };
111
- //# sourceMappingURL=chunk-EGNDADXB.js.map
111
+ //# sourceMappingURL=chunk-MNSZ7EZN.js.map
@@ -0,0 +1,228 @@
1
+ import {
2
+ RawIndexedDBFileSystemStore
3
+ } from "./chunk-RVPGQBWR.js";
4
+ import {
5
+ getFileSystemProviderV2
6
+ } from "./chunk-EZKJZUNJ.js";
7
+
8
+ // src/workspace/workspace-schema.ts
9
+ var MAX_IDENTIFIER_CHARACTERS = 256;
10
+ var MAX_LABEL_CHARACTERS = 1024;
11
+ var MAX_PATH_CHARACTERS = 4096;
12
+ var MAX_WORKSPACES = 1e4;
13
+ function parsePersistedWorkspaceList(value) {
14
+ if (!Array.isArray(value)) throw new Error("Persisted workspaces must be an array.");
15
+ if (value.length > MAX_WORKSPACES) throw new Error("Persisted workspace list is too large.");
16
+ const parsed = value.map(parsePersistedWorkspace);
17
+ if (new Set(parsed.map((workspace) => workspace.id)).size !== parsed.length) {
18
+ throw new Error("Persisted workspace IDs must be unique.");
19
+ }
20
+ return parsed;
21
+ }
22
+ function parsePersistedWorkspace(value, index) {
23
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
24
+ throw new Error(`Persisted workspace ${index} must be an object.`);
25
+ }
26
+ const record = value;
27
+ const allowed = ["id", "name", "type", "lastOpened", "rootPath", "versioningOverride"];
28
+ const unknownKey = Object.keys(record).find((key) => !allowed.includes(key));
29
+ if (unknownKey) throw new Error(`Persisted workspace ${index} has unknown field ${unknownKey}.`);
30
+ const id = boundedString(record.id, MAX_IDENTIFIER_CHARACTERS, `workspace ${index} id`);
31
+ const name = boundedString(record.name, MAX_LABEL_CHARACTERS, `workspace ${index} name`);
32
+ if (record.type !== "indexeddb" && record.type !== "native" && record.type !== "electron-native") {
33
+ throw new Error(`Persisted workspace ${index} has an invalid provider type.`);
34
+ }
35
+ const lastOpened = boundedString(
36
+ record.lastOpened,
37
+ MAX_IDENTIFIER_CHARACTERS,
38
+ `workspace ${index} lastOpened`
39
+ );
40
+ if (!Number.isFinite(Date.parse(lastOpened)) || new Date(lastOpened).toISOString() !== lastOpened) {
41
+ throw new Error(`Persisted workspace ${index} has an invalid lastOpened timestamp.`);
42
+ }
43
+ const descriptor = { id, name, type: record.type, lastOpened };
44
+ if (record.rootPath !== void 0) {
45
+ descriptor.rootPath = boundedString(
46
+ record.rootPath,
47
+ MAX_PATH_CHARACTERS,
48
+ `workspace ${index} rootPath`
49
+ );
50
+ }
51
+ if (record.type === "electron-native" && descriptor.rootPath === void 0) {
52
+ throw new Error(`Persisted Electron workspace ${index} is missing rootPath.`);
53
+ }
54
+ if (record.type !== "electron-native" && descriptor.rootPath !== void 0) {
55
+ throw new Error(`Persisted workspace ${index} must not contain rootPath.`);
56
+ }
57
+ if (record.versioningOverride !== void 0) {
58
+ if (record.versioningOverride !== "inherit" && record.versioningOverride !== "on" && record.versioningOverride !== "off") {
59
+ throw new Error(`Persisted workspace ${index} has an invalid versioning override.`);
60
+ }
61
+ descriptor.versioningOverride = record.versioningOverride;
62
+ }
63
+ return descriptor;
64
+ }
65
+ function boundedString(value, maximum, label) {
66
+ if (typeof value !== "string" || value.length < 1 || value.length > maximum || value.includes("\0")) {
67
+ throw new Error(`Persisted ${label} must be a bounded non-empty string.`);
68
+ }
69
+ return value;
70
+ }
71
+
72
+ // src/workspace/workspace-manager.ts
73
+ var DB_NAME = "docblocks-workspaces";
74
+ var STORE_NAME = "workspaces";
75
+ var LIST_KEY = "workspace-list";
76
+ var DEFAULT_WORKSPACE_ID = "default";
77
+ var registryStore = null;
78
+ function store() {
79
+ registryStore ?? (registryStore = new RawIndexedDBFileSystemStore(DB_NAME, STORE_NAME));
80
+ return registryStore;
81
+ }
82
+ var transientWorkspaces = /* @__PURE__ */ new Map();
83
+ var persistedMutationTail = Promise.resolve();
84
+ function registerTransientWorkspace(descriptor, provider) {
85
+ const previous = transientWorkspaces.get(descriptor.id);
86
+ if (previous && previous.provider !== provider) {
87
+ void getFileSystemProviderV2(previous.provider)?.dispose();
88
+ }
89
+ transientWorkspaces.set(descriptor.id, { descriptor, provider });
90
+ }
91
+ function getTransientWorkspace(id) {
92
+ return transientWorkspaces.get(id) ?? null;
93
+ }
94
+ async function unregisterTransientWorkspace(id) {
95
+ const entry = transientWorkspaces.get(id);
96
+ if (!entry) return;
97
+ transientWorkspaces.delete(id);
98
+ await getFileSystemProviderV2(entry.provider)?.dispose();
99
+ }
100
+ async function readPersistedIn(transaction) {
101
+ const list = await transaction.get(LIST_KEY);
102
+ return list == null ? [] : parsePersistedWorkspaceList(list);
103
+ }
104
+ async function readPersisted() {
105
+ return store().transaction("readonly", readPersistedIn);
106
+ }
107
+ async function listPersisted() {
108
+ await persistedMutationTail;
109
+ return readPersisted();
110
+ }
111
+ function mutatePersisted(mutation) {
112
+ const operation = persistedMutationTail.then(async () => {
113
+ await store().transaction("readwrite", async (transaction) => {
114
+ const current = await readPersistedIn(transaction);
115
+ const next = parsePersistedWorkspaceList(mutation([...current]));
116
+ await transaction.put(LIST_KEY, next);
117
+ });
118
+ });
119
+ persistedMutationTail = operation.then(
120
+ () => void 0,
121
+ () => void 0
122
+ );
123
+ return operation;
124
+ }
125
+ async function listWorkspaces() {
126
+ const persisted = await listPersisted();
127
+ const transient = [...transientWorkspaces.values()].map((t) => t.descriptor);
128
+ return [...persisted, ...transient];
129
+ }
130
+ async function getWorkspace(id) {
131
+ const transient = transientWorkspaces.get(id);
132
+ if (transient) return transient.descriptor;
133
+ const list = await listPersisted();
134
+ return list.find((w) => w.id === id) ?? null;
135
+ }
136
+ async function saveWorkspace(workspace) {
137
+ if (workspace.type === "transient") return;
138
+ const persisted = parsePersistedWorkspaceList([workspace])[0];
139
+ await mutatePersisted((list) => {
140
+ const idx = list.findIndex((candidate) => candidate.id === persisted.id);
141
+ if (idx >= 0) list[idx] = persisted;
142
+ else list.push(persisted);
143
+ return list;
144
+ });
145
+ }
146
+ async function removeWorkspace(id) {
147
+ if (transientWorkspaces.has(id)) {
148
+ await unregisterTransientWorkspace(id);
149
+ return;
150
+ }
151
+ await mutatePersisted((list) => list.filter((workspace) => workspace.id !== id));
152
+ }
153
+ async function touchWorkspace(id) {
154
+ const transient = transientWorkspaces.get(id);
155
+ if (transient) {
156
+ transient.descriptor.lastOpened = (/* @__PURE__ */ new Date()).toISOString();
157
+ return;
158
+ }
159
+ await mutatePersisted((list) => {
160
+ const workspace = list.find((candidate) => candidate.id === id);
161
+ if (workspace) workspace.lastOpened = (/* @__PURE__ */ new Date()).toISOString();
162
+ return list;
163
+ });
164
+ }
165
+ async function ensureDefaultWorkspace() {
166
+ const existing = await getWorkspace(DEFAULT_WORKSPACE_ID);
167
+ if (existing) return existing;
168
+ const descriptor = {
169
+ id: DEFAULT_WORKSPACE_ID,
170
+ name: "My Documents",
171
+ type: "indexeddb",
172
+ lastOpened: (/* @__PURE__ */ new Date()).toISOString()
173
+ };
174
+ await saveWorkspace(descriptor);
175
+ return descriptor;
176
+ }
177
+
178
+ // src/workspace/reconcile-electron.ts
179
+ function comparableRoot(rootPath, platform) {
180
+ const normalized = rootPath.replace(/\\/gu, "/").replace(/\/+$/gu, "");
181
+ return platform === "win32" ? normalized.toLowerCase() : normalized;
182
+ }
183
+ function reconcileElectronWorkspaceDescriptors(local, hostWorkspaces, platform, now) {
184
+ const electron = local.filter((workspace) => workspace.type === "electron-native");
185
+ const matchedLocalIds = /* @__PURE__ */ new Set();
186
+ const idRemap = {};
187
+ const upsert = hostWorkspaces.map((hostWorkspace) => {
188
+ const root = comparableRoot(hostWorkspace.rootPath, platform);
189
+ const candidates = electron.filter(
190
+ (workspace) => workspace.rootPath && comparableRoot(workspace.rootPath, platform) === root
191
+ );
192
+ const matching = candidates.sort(
193
+ (left, right) => right.lastOpened.localeCompare(left.lastOpened)
194
+ )[0];
195
+ if (matching) {
196
+ matchedLocalIds.add(matching.id);
197
+ if (matching.id !== hostWorkspace.id) idRemap[matching.id] = hostWorkspace.id;
198
+ }
199
+ return {
200
+ id: hostWorkspace.id,
201
+ name: matching?.name ?? hostWorkspace.name,
202
+ type: "electron-native",
203
+ rootPath: hostWorkspace.rootPath,
204
+ lastOpened: matching?.lastOpened ?? now,
205
+ ...matching?.versioningOverride ? { versioningOverride: matching.versioningOverride } : {}
206
+ };
207
+ });
208
+ const canonicalIds = new Set(hostWorkspaces.map((workspace) => workspace.id));
209
+ const removeIds = electron.filter(
210
+ (workspace) => !canonicalIds.has(workspace.id) || matchedLocalIds.has(workspace.id) && !upsert.some((candidate) => candidate.id === workspace.id)
211
+ ).map((workspace) => workspace.id);
212
+ return { removeIds, upsert, idRemap };
213
+ }
214
+
215
+ export {
216
+ parsePersistedWorkspaceList,
217
+ registerTransientWorkspace,
218
+ getTransientWorkspace,
219
+ unregisterTransientWorkspace,
220
+ listWorkspaces,
221
+ getWorkspace,
222
+ saveWorkspace,
223
+ removeWorkspace,
224
+ touchWorkspace,
225
+ ensureDefaultWorkspace,
226
+ reconcileElectronWorkspaceDescriptors
227
+ };
228
+ //# sourceMappingURL=chunk-P3DVZQ6S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workspace/workspace-schema.ts","../src/workspace/workspace-manager.ts","../src/workspace/reconcile-electron.ts"],"sourcesContent":["import type { WorkspaceDescriptor } from './types.js';\n\nconst MAX_IDENTIFIER_CHARACTERS = 256;\nconst MAX_LABEL_CHARACTERS = 1_024;\nconst MAX_PATH_CHARACTERS = 4_096;\nconst MAX_WORKSPACES = 10_000;\n\n/** Parse the complete persisted workspace list before it reaches startup code. */\nexport function parsePersistedWorkspaceList(value: unknown): WorkspaceDescriptor[] {\n if (!Array.isArray(value)) throw new Error('Persisted workspaces must be an array.');\n if (value.length > MAX_WORKSPACES) throw new Error('Persisted workspace list is too large.');\n const parsed = value.map(parsePersistedWorkspace);\n if (new Set(parsed.map((workspace) => workspace.id)).size !== parsed.length) {\n throw new Error('Persisted workspace IDs must be unique.');\n }\n return parsed;\n}\n\nfunction parsePersistedWorkspace(value: unknown, index: number): WorkspaceDescriptor {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Persisted workspace ${index} must be an object.`);\n }\n const record = value as Record<string, unknown>;\n const allowed = ['id', 'name', 'type', 'lastOpened', 'rootPath', 'versioningOverride'];\n const unknownKey = Object.keys(record).find((key) => !allowed.includes(key));\n if (unknownKey) throw new Error(`Persisted workspace ${index} has unknown field ${unknownKey}.`);\n\n const id = boundedString(record.id, MAX_IDENTIFIER_CHARACTERS, `workspace ${index} id`);\n const name = boundedString(record.name, MAX_LABEL_CHARACTERS, `workspace ${index} name`);\n if (\n record.type !== 'indexeddb' &&\n record.type !== 'native' &&\n record.type !== 'electron-native'\n ) {\n throw new Error(`Persisted workspace ${index} has an invalid provider type.`);\n }\n const lastOpened = boundedString(\n record.lastOpened,\n MAX_IDENTIFIER_CHARACTERS,\n `workspace ${index} lastOpened`,\n );\n if (\n !Number.isFinite(Date.parse(lastOpened)) ||\n new Date(lastOpened).toISOString() !== lastOpened\n ) {\n throw new Error(`Persisted workspace ${index} has an invalid lastOpened timestamp.`);\n }\n\n const descriptor: WorkspaceDescriptor = { id, name, type: record.type, lastOpened };\n if (record.rootPath !== undefined) {\n descriptor.rootPath = boundedString(\n record.rootPath,\n MAX_PATH_CHARACTERS,\n `workspace ${index} rootPath`,\n );\n }\n if (record.type === 'electron-native' && descriptor.rootPath === undefined) {\n throw new Error(`Persisted Electron workspace ${index} is missing rootPath.`);\n }\n if (record.type !== 'electron-native' && descriptor.rootPath !== undefined) {\n throw new Error(`Persisted workspace ${index} must not contain rootPath.`);\n }\n if (record.versioningOverride !== undefined) {\n if (\n record.versioningOverride !== 'inherit' &&\n record.versioningOverride !== 'on' &&\n record.versioningOverride !== 'off'\n ) {\n throw new Error(`Persisted workspace ${index} has an invalid versioning override.`);\n }\n descriptor.versioningOverride = record.versioningOverride;\n }\n return descriptor;\n}\n\nfunction boundedString(value: unknown, maximum: number, label: string): string {\n if (\n typeof value !== 'string' ||\n value.length < 1 ||\n value.length > maximum ||\n value.includes('\\0')\n ) {\n throw new Error(`Persisted ${label} must be a bounded non-empty string.`);\n }\n return value;\n}\n","/**\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 {\n RawIndexedDBFileSystemStore,\n type IndexedDBFileSystemStore,\n type IndexedDBFileSystemTransaction,\n} from '../filesystem/indexeddb-store.js';\nimport { getFileSystemProviderV2, type FileSystemProvider } from '../filesystem/types.js';\nimport type { WorkspaceDescriptor } from './types.js';\nimport { parsePersistedWorkspaceList } from './workspace-schema.js';\n\nconst DB_NAME = 'docblocks-workspaces';\nconst STORE_NAME = 'workspaces';\nconst LIST_KEY = 'workspace-list';\nconst DEFAULT_WORKSPACE_ID = 'default';\n\n/**\n * The registry deliberately uses the error-propagating store rather than the\n * Squisq LocalForage adapter: that adapter converts a failed read into `null`,\n * which this module cannot tell apart from \"no workspaces yet\". A single\n * transient IndexedDB failure therefore read as an empty registry, and the next\n * mutation persisted that emptiness over every workspace the user had.\n *\n * The database and object store names are unchanged, and the raw store uses the\n * same out-of-line keys LocalForage did, so existing registries load as-is.\n */\nlet registryStore: IndexedDBFileSystemStore | null = null;\n\nfunction store(): IndexedDBFileSystemStore {\n registryStore ??= new RawIndexedDBFileSystemStore(DB_NAME, STORE_NAME);\n return registryStore;\n}\n\n/**\n * Replace the registry's storage backend, or restore the default with `null`.\n * Mirrors the `store` option the IndexedDB providers accept; tests inject an\n * in-memory store through it and production code never calls it.\n */\nexport function setWorkspaceRegistryStore(next: IndexedDBFileSystemStore | null): void {\n registryStore = next;\n}\n\n/**\n * Session-only transient workspaces (a loose file/`.dbk` opened from the OS,\n * or a document copied from a shared URL). Kept purely in memory — never\n * written to the persisted list — with their pre-built provider registered\n * alongside the descriptor. Lost on reload, which is the intended\n * \"transient for the app session\" behaviour.\n */\ninterface TransientEntry {\n descriptor: WorkspaceDescriptor;\n provider: FileSystemProvider;\n}\nconst transientWorkspaces = new Map<string, TransientEntry>();\nlet persistedMutationTail: Promise<void> = Promise.resolve();\n\n/** Register a transient workspace and its (already-built) provider. */\nexport function registerTransientWorkspace(\n descriptor: WorkspaceDescriptor,\n provider: FileSystemProvider,\n): void {\n const previous = transientWorkspaces.get(descriptor.id);\n if (previous && previous.provider !== provider) {\n void getFileSystemProviderV2(previous.provider)?.dispose();\n }\n transientWorkspaces.set(descriptor.id, { descriptor, provider });\n}\n\n/** Look up a transient workspace (descriptor + provider) by id, if any. */\nexport function getTransientWorkspace(\n id: string,\n): { descriptor: WorkspaceDescriptor; provider: FileSystemProvider } | null {\n return transientWorkspaces.get(id) ?? null;\n}\n\n/** Forget a transient workspace (e.g. when it is closed). */\nexport async function unregisterTransientWorkspace(id: string): Promise<void> {\n const entry = transientWorkspaces.get(id);\n if (!entry) return;\n transientWorkspaces.delete(id);\n await getFileSystemProviderV2(entry.provider)?.dispose();\n}\n\n/**\n * The persisted (on-disk) workspace list only — excludes transient ones.\n *\n * An unreadable registry throws. Only a store that positively reports \"no such\n * key\" is an empty registry; anything else is an unknown registry, and callers\n * must not act as though the user has no workspaces.\n */\nasync function readPersistedIn(\n transaction: IndexedDBFileSystemTransaction,\n): Promise<WorkspaceDescriptor[]> {\n const list = await transaction.get<unknown>(LIST_KEY);\n return list == null ? [] : parsePersistedWorkspaceList(list);\n}\n\nasync function readPersisted(): Promise<WorkspaceDescriptor[]> {\n return store().transaction('readonly', readPersistedIn);\n}\n\nasync function listPersisted(): Promise<WorkspaceDescriptor[]> {\n await persistedMutationTail;\n return readPersisted();\n}\n\nfunction mutatePersisted(\n mutation: (workspaces: WorkspaceDescriptor[]) => WorkspaceDescriptor[],\n): Promise<void> {\n const operation = persistedMutationTail.then(async () => {\n // Read and write in one transaction. A failed read aborts the mutation\n // instead of rebuilding the registry from a phantom empty list, and no\n // concurrent writer (another tab) can land between the read and the write\n // only to be clobbered by this mutation's stale copy.\n await store().transaction('readwrite', async (transaction) => {\n const current = await readPersistedIn(transaction);\n const next = parsePersistedWorkspaceList(mutation([...current]));\n await transaction.put(LIST_KEY, next);\n });\n });\n persistedMutationTail = operation.then(\n () => undefined,\n () => undefined,\n );\n return operation;\n}\n\n/**\n * Get all known workspace descriptors — persisted plus any session-only\n * transient ones (so they show in the picker and resolve for the session).\n */\nexport async function listWorkspaces(): Promise<WorkspaceDescriptor[]> {\n const persisted = await listPersisted();\n const transient = [...transientWorkspaces.values()].map((t) => t.descriptor);\n return [...persisted, ...transient];\n}\n\n/**\n * Get a specific workspace descriptor by id (transient or persisted).\n */\nexport async function getWorkspace(id: string): Promise<WorkspaceDescriptor | null> {\n const transient = transientWorkspaces.get(id);\n if (transient) return transient.descriptor;\n const list = await listPersisted();\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. Transient workspaces are never persisted.\n */\nexport async function saveWorkspace(workspace: WorkspaceDescriptor): Promise<void> {\n if (workspace.type === 'transient') return;\n const persisted = parsePersistedWorkspaceList([workspace])[0];\n await mutatePersisted((list) => {\n const idx = list.findIndex((candidate) => candidate.id === persisted.id);\n if (idx >= 0) list[idx] = persisted;\n else list.push(persisted);\n return list;\n });\n}\n\n/**\n * Remove a workspace descriptor by id (transient or persisted).\n */\nexport async function removeWorkspace(id: string): Promise<void> {\n if (transientWorkspaces.has(id)) {\n await unregisterTransientWorkspace(id);\n return;\n }\n await mutatePersisted((list) => list.filter((workspace) => workspace.id !== id));\n}\n\n/**\n * Touch the lastOpened timestamp on a workspace. For transient workspaces this\n * updates the in-memory descriptor only (nothing is persisted).\n */\nexport async function touchWorkspace(id: string): Promise<void> {\n const transient = transientWorkspaces.get(id);\n if (transient) {\n transient.descriptor.lastOpened = new Date().toISOString();\n return;\n }\n await mutatePersisted((list) => {\n const workspace = list.find((candidate) => candidate.id === id);\n if (workspace) workspace.lastOpened = new Date().toISOString();\n return 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","import type { ElectronWorkspaceInfo, HostEnvironment } from '../host/types.js';\nimport type { WorkspaceDescriptor } from './types.js';\n\nexport interface ElectronWorkspaceReconciliation {\n /** Obsolete renderer descriptors to remove before attempting restoration. */\n readonly removeIds: readonly string[];\n /** Canonical main-authorized descriptors to insert or replace. */\n readonly upsert: readonly WorkspaceDescriptor[];\n /** Safe navigation migration for a local descriptor matched by root. */\n readonly idRemap: Readonly<Record<string, string>>;\n}\n\nfunction comparableRoot(rootPath: string, platform: HostEnvironment['platform']): string {\n const normalized = rootPath.replace(/\\\\/gu, '/').replace(/\\/+$/gu, '');\n return platform === 'win32' ? normalized.toLowerCase() : normalized;\n}\n\n/**\n * Reconcile renderer IndexedDB metadata with the main process authority list.\n * `hostWorkspaces` is authoritative: local Electron descriptors absent from\n * it cannot be opened safely and are removed. Root-path matching carries user\n * metadata across an id migration without treating that display path as\n * authority.\n */\nexport function reconcileElectronWorkspaceDescriptors(\n local: readonly WorkspaceDescriptor[],\n hostWorkspaces: readonly ElectronWorkspaceInfo[],\n platform: HostEnvironment['platform'],\n now: string,\n): ElectronWorkspaceReconciliation {\n const electron = local.filter((workspace) => workspace.type === 'electron-native');\n const matchedLocalIds = new Set<string>();\n const idRemap: Record<string, string> = {};\n const upsert = hostWorkspaces.map((hostWorkspace): WorkspaceDescriptor => {\n const root = comparableRoot(hostWorkspace.rootPath, platform);\n const candidates = electron.filter(\n (workspace) => workspace.rootPath && comparableRoot(workspace.rootPath, platform) === root,\n );\n const matching = candidates.sort((left, right) =>\n right.lastOpened.localeCompare(left.lastOpened),\n )[0];\n if (matching) {\n matchedLocalIds.add(matching.id);\n if (matching.id !== hostWorkspace.id) idRemap[matching.id] = hostWorkspace.id;\n }\n return {\n id: hostWorkspace.id,\n name: matching?.name ?? hostWorkspace.name,\n type: 'electron-native',\n rootPath: hostWorkspace.rootPath,\n lastOpened: matching?.lastOpened ?? now,\n ...(matching?.versioningOverride ? { versioningOverride: matching.versioningOverride } : {}),\n };\n });\n const canonicalIds = new Set(hostWorkspaces.map((workspace) => workspace.id));\n const removeIds = electron\n .filter(\n (workspace) =>\n !canonicalIds.has(workspace.id) ||\n (matchedLocalIds.has(workspace.id) &&\n !upsert.some((candidate) => candidate.id === workspace.id)),\n )\n .map((workspace) => workspace.id);\n return { removeIds, upsert, idRemap };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,52 @@
1
+ // src/filesystem/v2.ts
2
+ function parseFileSystemVersion(value) {
3
+ if (typeof value !== "string" || !value || value.length > 1024 || value.includes("\0")) {
4
+ throw new TypeError("Filesystem version tokens must be non-empty bounded strings.");
5
+ }
6
+ return value;
7
+ }
8
+ function tryParseFileSystemVersion(value) {
9
+ try {
10
+ return parseFileSystemVersion(value);
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ // src/filesystem/internal/bytes.ts
17
+ function copyBytes(data) {
18
+ const source = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
19
+ const copy = new Uint8Array(source.byteLength);
20
+ copy.set(source);
21
+ return copy.buffer;
22
+ }
23
+ function bytesEqual(left, right) {
24
+ const leftBytes = ArrayBuffer.isView(left) ? new Uint8Array(left.buffer, left.byteOffset, left.byteLength) : new Uint8Array(left);
25
+ const rightBytes = ArrayBuffer.isView(right) ? new Uint8Array(right.buffer, right.byteOffset, right.byteLength) : new Uint8Array(right);
26
+ if (leftBytes.byteLength !== rightBytes.byteLength) return false;
27
+ return leftBytes.every((byte, index) => byte === rightBytes[index]);
28
+ }
29
+
30
+ // src/filesystem/internal/entry-order.ts
31
+ function compareText(left, right) {
32
+ return left < right ? -1 : left > right ? 1 : 0;
33
+ }
34
+ function compareSnapshotsByName(left, right) {
35
+ if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
36
+ return compareText(left.name, right.name);
37
+ }
38
+ function compareSnapshotsByPath(left, right) {
39
+ if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
40
+ return compareText(left.path, right.path);
41
+ }
42
+
43
+ export {
44
+ parseFileSystemVersion,
45
+ tryParseFileSystemVersion,
46
+ copyBytes,
47
+ bytesEqual,
48
+ compareText,
49
+ compareSnapshotsByName,
50
+ compareSnapshotsByPath
51
+ };
52
+ //# sourceMappingURL=chunk-P4WATND4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/filesystem/v2.ts","../src/filesystem/internal/bytes.ts","../src/filesystem/internal/entry-order.ts"],"sourcesContent":["import type { FsError } from './fs-error.js';\nimport type { WorkspacePath } from './workspace-path.js';\n\n/**\n * Opaque provider-issued token representing one observed entry/tree state.\n * Tokens support equality only within the issuing provider; callers must not\n * parse them or assume a move or byte-identical rewrite changes the token.\n * A token need not be a content hash, but it must change whenever the issuing\n * provider observes a potentially different kind, payload, or descendant\n * state. Detection of non-cooperating external changes is bounded by the\n * backend's declared capabilities and metadata precision.\n */\ndeclare const fileSystemVersionBrand: unique symbol;\nexport type FileSystemVersion = string & {\n readonly [fileSystemVersionBrand]: 'FileSystemVersion';\n};\n\n/** Validate/rebrand a version received from a provider transport. */\nexport function parseFileSystemVersion(value: unknown): FileSystemVersion {\n if (typeof value !== 'string' || !value || value.length > 1024 || value.includes('\\0')) {\n throw new TypeError('Filesystem version tokens must be non-empty bounded strings.');\n }\n return value as FileSystemVersion;\n}\n\nexport function tryParseFileSystemVersion(value: unknown): FileSystemVersion | null {\n try {\n return parseFileSystemVersion(value);\n } catch {\n return null;\n }\n}\n\n/** `process` excludes this provider's local peers; `cross-context` also excludes cooperating tabs/processes. */\nexport type FileSystemAtomicity = 'none' | 'process' | 'cross-context';\nexport type FileSystemCaseSensitivity = 'sensitive' | 'insensitive' | 'platform';\nexport type FileSystemDurability = 'volatile' | 'best-effort' | 'durable';\nexport type FileSystemConditionalWriteStrength =\n | 'none'\n | 'process'\n | 'cross-context'\n | 'storage-atomic';\nexport type FileSystemSymlinkPolicy = 'unsupported' | 'reject' | 'follow-contained' | 'preserve';\n\n/** Serializable feature declaration; consumers never need provider instanceof checks. */\nexport interface FileSystemProviderCapabilities {\n /** Isolation of one successful file replacement from cooperating writers. */\n readonly writeAtomicity: FileSystemAtomicity;\n /** Isolation of one successful source-to-destination move. */\n readonly moveAtomicity: FileSystemAtomicity;\n /** Writers excluded while one complete snapshot is captured. */\n readonly snapshotAtomicity: FileSystemAtomicity;\n /** Strongest boundary at which expectedVersion is checked with mutation. */\n readonly conditionalWrite: FileSystemConditionalWriteStrength;\n readonly recursiveRemove: boolean;\n /** True only when watch has a real ready barrier and sequenced events. */\n readonly watch: boolean;\n readonly caseSensitivity: FileSystemCaseSensitivity;\n readonly symlinkPolicy: FileSystemSymlinkPolicy;\n readonly durability: FileSystemDurability;\n}\n\ninterface FileSystemEntrySnapshotBase {\n readonly path: WorkspacePath;\n readonly name: string;\n readonly version: FileSystemVersion;\n /** ISO-8601 timestamp supplied by the backend; versions remain authoritative. */\n readonly lastModified: string;\n}\n\nexport interface FileSystemFileSnapshot extends FileSystemEntrySnapshotBase {\n readonly kind: 'file';\n readonly size: number;\n}\n\nexport interface FileSystemDirectorySnapshot extends FileSystemEntrySnapshotBase {\n readonly kind: 'directory';\n readonly size: null;\n}\n\nexport type FileSystemEntrySnapshot = FileSystemFileSnapshot | FileSystemDirectorySnapshot;\n\n/** A read always couples owned bytes to the exact metadata version they represent. */\nexport interface FileSystemFileRead {\n readonly entry: FileSystemFileSnapshot;\n readonly data: ArrayBuffer;\n}\n\nexport interface FileSystemSnapshotFile extends FileSystemFileSnapshot {\n readonly data: ArrayBuffer;\n}\n\nexport type FileSystemSnapshotEntry = FileSystemSnapshotFile | FileSystemDirectorySnapshot;\n\n/**\n * Complete best-available tree traversal. `snapshotAtomicity` declares\n * whether concurrent local/cross-context mutations are excluded while it is\n * captured. Implementations must never expose aliased buffers.\n */\nexport interface FileSystemSnapshot {\n readonly version: FileSystemVersion;\n readonly entries: readonly FileSystemSnapshotEntry[];\n}\n\nexport type FileSystemWriteMode = 'upsert' | 'create' | 'replace';\n\nexport interface FileSystemWriteOptions {\n /** Defaults to upsert. */\n readonly mode?: FileSystemWriteMode;\n /** Defaults to false so structure creation is never implicit. */\n readonly createParents?: boolean;\n /** null means the entry must not exist; a token requires an exact match. */\n readonly expectedVersion?: FileSystemVersion | null;\n}\n\nexport type FileSystemCreateDirectoryMode = 'ensure' | 'create';\n\nexport interface FileSystemCreateDirectoryOptions {\n /** Defaults to ensure (idempotently return an existing directory). */\n readonly mode?: FileSystemCreateDirectoryMode;\n /** Defaults to false. */\n readonly createParents?: boolean;\n}\n\nexport interface FileSystemRemoveOptions {\n /** Defaults to false; non-empty directories otherwise fail with not-empty. */\n readonly recursive?: boolean;\n /** Defaults to error. */\n readonly missing?: 'ignore' | 'error';\n readonly expectedVersion?: FileSystemVersion;\n}\n\nexport interface FileSystemRemoveResult {\n readonly removed: boolean;\n /** Version of the complete tree after the removal attempt. */\n readonly version: FileSystemVersion;\n}\n\nexport interface FileSystemMoveOptions {\n /** Defaults to false. Destinations are never overwritten. */\n readonly createParents?: boolean;\n readonly expectedVersion?: FileSystemVersion;\n}\n\nexport type FileSystemWatchEventType = 'created' | 'modified' | 'removed' | 'moved' | 'overflow';\nexport type FileSystemWatchEventOrigin = 'local' | 'external' | 'unknown';\n\ninterface FileSystemWatchEventBase {\n readonly sequence: number;\n readonly origin: FileSystemWatchEventOrigin;\n readonly path: WorkspacePath;\n}\n\nexport interface FileSystemEntryWatchEvent extends FileSystemWatchEventBase {\n readonly type: Exclude<FileSystemWatchEventType, 'overflow'>;\n readonly kind: 'file' | 'directory';\n readonly destinationPath: WorkspacePath | null;\n readonly version: FileSystemVersion | null;\n readonly previousVersion: FileSystemVersion | null;\n}\n\nexport interface FileSystemOverflowWatchEvent extends FileSystemWatchEventBase {\n readonly type: 'overflow';\n readonly kind: null;\n readonly destinationPath: null;\n readonly version: null;\n readonly previousVersion: null;\n}\n\nexport type FileSystemWatchEvent = FileSystemEntryWatchEvent | FileSystemOverflowWatchEvent;\n\nexport type FileSystemWatchListener = (event: FileSystemWatchEvent) => void;\nexport type FileSystemWatchErrorListener = (error: FsError) => void;\n\nexport interface FileSystemWatchOptions {\n readonly onError?: FileSystemWatchErrorListener;\n}\n\nexport interface FileSystemWatchSubscription {\n readonly closed: boolean;\n /** Resolves after the backend watch is installed and no initial event can be missed. */\n readonly ready: Promise<void>;\n /** Idempotent and awaitable so resource release can join provider shutdown. */\n dispose(): Promise<void>;\n}\n\n/**\n * Correctness-first filesystem contract. Every logical path has exactly one\n * kind and one byte payload; conditional writes and lifecycle disposal are\n * mandatory rather than optional provider extensions.\n */\nexport interface FileSystemProviderV2 {\n readonly id: string;\n readonly label: string;\n readonly capabilities: FileSystemProviderCapabilities;\n\n /** Absence is represented only by null; permission/I/O/wrong-kind errors throw FsError. */\n stat(path: WorkspacePath): Promise<FileSystemEntrySnapshot | null>;\n /** Absence is represented only by null; a directory at path is type-mismatch. */\n readFile(path: WorkspacePath): Promise<FileSystemFileRead | null>;\n /** Missing paths and file paths throw; results are canonical and deterministic. */\n readDirectory(path: WorkspacePath): Promise<readonly FileSystemEntrySnapshot[]>;\n\n writeFile(\n path: WorkspacePath,\n data: ArrayBuffer | Uint8Array,\n options?: FileSystemWriteOptions,\n ): Promise<FileSystemFileSnapshot>;\n createDirectory(\n path: WorkspacePath,\n options?: FileSystemCreateDirectoryOptions,\n ): Promise<FileSystemDirectorySnapshot>;\n remove(path: WorkspacePath, options?: FileSystemRemoveOptions): Promise<FileSystemRemoveResult>;\n move(\n oldPath: WorkspacePath,\n newPath: WorkspacePath,\n options?: FileSystemMoveOptions,\n ): Promise<FileSystemEntrySnapshot>;\n\n snapshot(): Promise<FileSystemSnapshot>;\n /**\n * Always returns a disposable subscription. When `capabilities.watch` is\n * false, `ready` rejects with `FsError('not-supported')` and `onError`\n * observes the same failure; callers can therefore use one cleanup path.\n */\n watch(\n listener: FileSystemWatchListener,\n options?: FileSystemWatchOptions,\n ): FileSystemWatchSubscription;\n dispose(): Promise<void>;\n}\n","/**\n * Byte helpers shared by every filesystem provider.\n *\n * Providers own their stored buffers, so every value that crosses the provider\n * boundary is copied rather than aliased. Keeping one implementation here stops\n * the copy semantics (notably: honouring a view's byteOffset/byteLength instead\n * of its whole backing buffer) from drifting between backends.\n */\n\n/** Copy only the visible bytes of a buffer or view into a fresh ArrayBuffer. */\nexport function copyBytes(data: ArrayBuffer | Uint8Array): ArrayBuffer {\n const source = ArrayBuffer.isView(data)\n ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n : new Uint8Array(data);\n const copy = new Uint8Array(source.byteLength);\n copy.set(source);\n return copy.buffer as ArrayBuffer;\n}\n\n/** Compare the visible bytes of two buffers or views for exact equality. */\nexport function bytesEqual(\n left: ArrayBuffer | Uint8Array,\n right: ArrayBuffer | Uint8Array,\n): boolean {\n const leftBytes = ArrayBuffer.isView(left)\n ? new Uint8Array(left.buffer, left.byteOffset, left.byteLength)\n : new Uint8Array(left);\n const rightBytes = ArrayBuffer.isView(right)\n ? new Uint8Array(right.buffer, right.byteOffset, right.byteLength)\n : new Uint8Array(right);\n if (leftBytes.byteLength !== rightBytes.byteLength) return false;\n return leftBytes.every((byte, index) => byte === rightBytes[index]);\n}\n","/**\n * Deterministic listing order shared by every filesystem provider.\n *\n * Providers must return byte-stable orderings so snapshots can be compared\n * across backends and across runs. `compareText` is deliberately a raw code\n * unit comparison rather than `localeCompare`, which is locale-dependent.\n */\n\nimport type { FileSystemEntrySnapshot } from '../v2.js';\n\n/** Locale-independent, deterministic string ordering. */\nexport function compareText(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\n/** Directory-first, then by entry name. Used for sibling listings. */\nexport function compareSnapshotsByName(\n left: FileSystemEntrySnapshot,\n right: FileSystemEntrySnapshot,\n): number {\n if (left.kind !== right.kind) return left.kind === 'directory' ? -1 : 1;\n return compareText(left.name, right.name);\n}\n\n/**\n * Directory-first, then by full path.\n *\n * Equivalent to `compareSnapshotsByName` for siblings, and used where entries\n * may not share a parent — the Electron transport re-sorts host-supplied\n * listings defensively and cannot assume a single directory's children.\n */\nexport function compareSnapshotsByPath(\n left: FileSystemEntrySnapshot,\n right: FileSystemEntrySnapshot,\n): number {\n if (left.kind !== right.kind) return left.kind === 'directory' ? -1 : 1;\n return compareText(left.path, right.path);\n}\n"],"mappings":";AAkBO,SAAS,uBAAuB,OAAmC;AACxE,MAAI,OAAO,UAAU,YAAY,CAAC,SAAS,MAAM,SAAS,QAAQ,MAAM,SAAS,IAAI,GAAG;AACtF,UAAM,IAAI,UAAU,8DAA8D;AAAA,EACpF;AACA,SAAO;AACT;AAEO,SAAS,0BAA0B,OAA0C;AAClF,MAAI;AACF,WAAO,uBAAuB,KAAK;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACrBO,SAAS,UAAU,MAA6C;AACrE,QAAM,SAAS,YAAY,OAAO,IAAI,IAClC,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,IAC5D,IAAI,WAAW,IAAI;AACvB,QAAM,OAAO,IAAI,WAAW,OAAO,UAAU;AAC7C,OAAK,IAAI,MAAM;AACf,SAAO,KAAK;AACd;AAGO,SAAS,WACd,MACA,OACS;AACT,QAAM,YAAY,YAAY,OAAO,IAAI,IACrC,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,IAC5D,IAAI,WAAW,IAAI;AACvB,QAAM,aAAa,YAAY,OAAO,KAAK,IACvC,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU,IAC/D,IAAI,WAAW,KAAK;AACxB,MAAI,UAAU,eAAe,WAAW,WAAY,QAAO;AAC3D,SAAO,UAAU,MAAM,CAAC,MAAM,UAAU,SAAS,WAAW,KAAK,CAAC;AACpE;;;ACrBO,SAAS,YAAY,MAAc,OAAuB;AAC/D,SAAO,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI;AAChD;AAGO,SAAS,uBACd,MACA,OACQ;AACR,MAAI,KAAK,SAAS,MAAM,KAAM,QAAO,KAAK,SAAS,cAAc,KAAK;AACtE,SAAO,YAAY,KAAK,MAAM,MAAM,IAAI;AAC1C;AASO,SAAS,uBACd,MACA,OACQ;AACR,MAAI,KAAK,SAAS,MAAM,KAAM,QAAO,KAAK,SAAS,cAAc,KAAK;AACtE,SAAO,YAAY,KAAK,MAAM,MAAM,IAAI;AAC1C;","names":[]}
@@ -0,0 +1,169 @@
1
+ // src/filesystem/indexeddb-store.ts
2
+ function indexedDBWorkspaceDatabaseName(workspaceId) {
3
+ return `docblocks-fs-${workspaceId}`;
4
+ }
5
+ function deleteIndexedDBDatabase(databaseName) {
6
+ if (typeof indexedDB === "undefined") {
7
+ return Promise.reject(new Error("IndexedDB is not available in this environment."));
8
+ }
9
+ return new Promise((resolve, reject) => {
10
+ const request = indexedDB.deleteDatabase(databaseName);
11
+ request.onsuccess = () => resolve();
12
+ request.onerror = () => reject(request.error ?? new Error(`Failed to delete the "${databaseName}" database.`));
13
+ });
14
+ }
15
+ function deleteIndexedDBWorkspaceData(workspaceId) {
16
+ return deleteIndexedDBDatabase(indexedDBWorkspaceDatabaseName(workspaceId));
17
+ }
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
+ export {
165
+ indexedDBWorkspaceDatabaseName,
166
+ deleteIndexedDBWorkspaceData,
167
+ RawIndexedDBFileSystemStore
168
+ };
169
+ //# sourceMappingURL=chunk-RVPGQBWR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/filesystem/indexeddb-store.ts"],"sourcesContent":["/**\n * Minimal, error-propagating IndexedDB store used by the virtual filesystem.\n *\n * The previous Squisq LocalForage adapter intentionally converted failed\n * reads/enumerations/removals into null/empty/success. Those semantics are\n * unsuitable for filesystem mutations: an unavailable database must never be\n * interpreted as an absent file. This layer opens the old database and object\n * store directly so existing data can be migrated in one transaction.\n */\n\nexport type IndexedDBFileSystemTransactionMode = 'readonly' | 'readwrite';\n\n/** The database holding one browser-local workspace's files. */\nexport function indexedDBWorkspaceDatabaseName(workspaceId: string): string {\n return `docblocks-fs-${workspaceId}`;\n}\n\n/**\n * Delete a database and everything in it.\n *\n * Open connections do not have to be closed first: deletion fires\n * `versionchange` on them, and this module's connections close themselves in\n * response, after which the deletion proceeds. A `blocked` event is therefore\n * transient (an in-flight transaction) and is deliberately not treated as a\n * failure — the deletion still completes once that transaction finishes.\n */\nexport function deleteIndexedDBDatabase(databaseName: string): Promise<void> {\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('IndexedDB is not available in this environment.'));\n }\n return new Promise((resolve, reject) => {\n const request = indexedDB.deleteDatabase(databaseName);\n request.onsuccess = () => resolve();\n request.onerror = () =>\n reject(request.error ?? new Error(`Failed to delete the \"${databaseName}\" database.`));\n });\n}\n\n/**\n * Delete a browser-local workspace's stored files.\n *\n * Removing a workspace descriptor alone leaves these bytes behind: invisible to\n * the user, still counted against the origin's quota, and ready to reappear\n * under any later workspace that reuses the same id.\n */\nexport function deleteIndexedDBWorkspaceData(workspaceId: string): Promise<void> {\n return deleteIndexedDBDatabase(indexedDBWorkspaceDatabaseName(workspaceId));\n}\n\nexport interface IndexedDBFileSystemTransaction {\n get<T>(key: string): Promise<T | null>;\n put<T>(key: string, value: T): Promise<void>;\n delete(key: string): Promise<void>;\n keys(): Promise<string[]>;\n /** O(log n) namespace probe in the concrete IndexedDB implementation. */\n hasKeyWithPrefix(prefix: string): Promise<boolean>;\n}\n\nexport interface IndexedDBFileSystemStore {\n transaction<T>(\n mode: IndexedDBFileSystemTransactionMode,\n operation: (transaction: IndexedDBFileSystemTransaction) => Promise<T>,\n ): Promise<T>;\n close?(): void;\n}\n\nexport class RawIndexedDBFileSystemStore implements IndexedDBFileSystemStore {\n private database: IDBDatabase | null = null;\n private opening: Promise<IDBDatabase> | null = null;\n private closed = false;\n\n public constructor(\n private readonly databaseName: string,\n private readonly storeName: string,\n ) {}\n\n public async transaction<T>(\n mode: IndexedDBFileSystemTransactionMode,\n operation: (transaction: IndexedDBFileSystemTransaction) => Promise<T>,\n ): Promise<T> {\n const database = await this.getDatabase();\n const nativeTransaction = database.transaction(this.storeName, mode);\n const completion = waitForTransaction(nativeTransaction);\n const transaction = new RawIndexedDBFileSystemTransaction(\n nativeTransaction.objectStore(this.storeName),\n );\n\n let result: T;\n try {\n result = await operation(transaction);\n } catch (error: unknown) {\n try {\n nativeTransaction.abort();\n } catch {\n // The request failure may already have aborted the transaction.\n }\n await completion.catch(() => undefined);\n throw error;\n }\n\n await completion;\n return result;\n }\n\n public close(): void {\n if (this.closed) return;\n this.closed = true;\n this.database?.close();\n this.database = null;\n this.opening = null;\n }\n\n private getDatabase(): Promise<IDBDatabase> {\n if (this.closed) {\n return Promise.reject(new Error('The IndexedDB filesystem store is closed.'));\n }\n if (this.database) return Promise.resolve(this.database);\n if (this.opening) return this.opening;\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('IndexedDB is not available in this environment.'));\n }\n\n this.opening = openDatabaseWithStore(this.databaseName, this.storeName)\n .then((database) => {\n if (this.closed) {\n database.close();\n throw new Error('The IndexedDB filesystem store is closed.');\n }\n this.database = database;\n this.opening = null;\n database.onversionchange = () => {\n database.close();\n if (this.database === database) this.database = null;\n };\n return database;\n })\n .catch((error: unknown) => {\n this.opening = null;\n throw error;\n });\n return this.opening;\n }\n}\n\nclass RawIndexedDBFileSystemTransaction implements IndexedDBFileSystemTransaction {\n public constructor(private readonly store: IDBObjectStore) {}\n\n public async get<T>(key: string): Promise<T | null> {\n const value = await requestResult(this.store.get(key));\n return value === undefined ? null : (value as T);\n }\n\n public async put<T>(key: string, value: T): Promise<void> {\n await requestResult(this.store.put(value, key));\n }\n\n public async delete(key: string): Promise<void> {\n await requestResult(this.store.delete(key));\n }\n\n public async keys(): Promise<string[]> {\n const keys = await requestResult(this.store.getAllKeys());\n return keys.map((key) => {\n if (typeof key !== 'string') {\n throw new Error('The DocBlocks filesystem store contains a non-string key.');\n }\n return key;\n });\n }\n\n public async hasKeyWithPrefix(prefix: string): Promise<boolean> {\n const cursor = await requestResult(this.store.openKeyCursor(IDBKeyRange.lowerBound(prefix)));\n return cursor !== null && typeof cursor.key === 'string' && cursor.key.startsWith(prefix);\n }\n}\n\nasync function openDatabaseWithStore(\n databaseName: string,\n storeName: string,\n): Promise<IDBDatabase> {\n const database = await openDatabase(databaseName, undefined, (upgradeDatabase) => {\n if (!upgradeDatabase.objectStoreNames.contains(storeName)) {\n upgradeDatabase.createObjectStore(storeName);\n }\n });\n if (database.objectStoreNames.contains(storeName)) return database;\n\n const nextVersion = database.version + 1;\n database.close();\n return openDatabase(databaseName, nextVersion, (upgradeDatabase) => {\n if (!upgradeDatabase.objectStoreNames.contains(storeName)) {\n upgradeDatabase.createObjectStore(storeName);\n }\n });\n}\n\nfunction openDatabase(\n databaseName: string,\n version: number | undefined,\n upgrade: (database: IDBDatabase) => void,\n): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request =\n version === undefined ? indexedDB.open(databaseName) : indexedDB.open(databaseName, version);\n let settled = false;\n\n request.onupgradeneeded = () => upgrade(request.result);\n request.onsuccess = () => {\n if (settled) {\n request.result.close();\n return;\n }\n settled = true;\n resolve(request.result);\n };\n request.onerror = () => {\n if (settled) return;\n settled = true;\n reject(request.error ?? new Error(`Could not open IndexedDB database ${databaseName}.`));\n };\n request.onblocked = () => {\n if (settled) return;\n settled = true;\n reject(new Error(`IndexedDB database ${databaseName} is blocked by another open page.`));\n };\n });\n}\n\nfunction requestResult<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed.'));\n });\n}\n\nfunction waitForTransaction(transaction: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n transaction.oncomplete = () => resolve();\n transaction.onabort = () =>\n reject(transaction.error ?? new Error('IndexedDB transaction was aborted.'));\n transaction.onerror = () =>\n reject(transaction.error ?? new Error('IndexedDB transaction failed.'));\n });\n}\n"],"mappings":";AAaO,SAAS,+BAA+B,aAA6B;AAC1E,SAAO,gBAAgB,WAAW;AACpC;AAWO,SAAS,wBAAwB,cAAqC;AAC3E,MAAI,OAAO,cAAc,aAAa;AACpC,WAAO,QAAQ,OAAO,IAAI,MAAM,iDAAiD,CAAC;AAAA,EACpF;AACA,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UAAU,UAAU,eAAe,YAAY;AACrD,YAAQ,YAAY,MAAM,QAAQ;AAClC,YAAQ,UAAU,MAChB,OAAO,QAAQ,SAAS,IAAI,MAAM,yBAAyB,YAAY,aAAa,CAAC;AAAA,EACzF,CAAC;AACH;AASO,SAAS,6BAA6B,aAAoC;AAC/E,SAAO,wBAAwB,+BAA+B,WAAW,CAAC;AAC5E;AAmBO,IAAM,8BAAN,MAAsE;AAAA,EAKpE,YACY,cACA,WACjB;AAFiB;AACA;AANnB,SAAQ,WAA+B;AACvC,SAAQ,UAAuC;AAC/C,SAAQ,SAAS;AAAA,EAKd;AAAA,EAEH,MAAa,YACX,MACA,WACY;AACZ,UAAM,WAAW,MAAM,KAAK,YAAY;AACxC,UAAM,oBAAoB,SAAS,YAAY,KAAK,WAAW,IAAI;AACnE,UAAM,aAAa,mBAAmB,iBAAiB;AACvD,UAAM,cAAc,IAAI;AAAA,MACtB,kBAAkB,YAAY,KAAK,SAAS;AAAA,IAC9C;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,UAAU,WAAW;AAAA,IACtC,SAAS,OAAgB;AACvB,UAAI;AACF,0BAAkB,MAAM;AAAA,MAC1B,QAAQ;AAAA,MAER;AACA,YAAM,WAAW,MAAM,MAAM,MAAS;AACtC,YAAM;AAAA,IACR;AAEA,UAAM;AACN,WAAO;AAAA,EACT;AAAA,EAEO,QAAc;AACnB,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,UAAU,MAAM;AACrB,SAAK,WAAW;AAChB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEQ,cAAoC;AAC1C,QAAI,KAAK,QAAQ;AACf,aAAO,QAAQ,OAAO,IAAI,MAAM,2CAA2C,CAAC;AAAA,IAC9E;AACA,QAAI,KAAK,SAAU,QAAO,QAAQ,QAAQ,KAAK,QAAQ;AACvD,QAAI,KAAK,QAAS,QAAO,KAAK;AAC9B,QAAI,OAAO,cAAc,aAAa;AACpC,aAAO,QAAQ,OAAO,IAAI,MAAM,iDAAiD,CAAC;AAAA,IACpF;AAEA,SAAK,UAAU,sBAAsB,KAAK,cAAc,KAAK,SAAS,EACnE,KAAK,CAAC,aAAa;AAClB,UAAI,KAAK,QAAQ;AACf,iBAAS,MAAM;AACf,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC7D;AACA,WAAK,WAAW;AAChB,WAAK,UAAU;AACf,eAAS,kBAAkB,MAAM;AAC/B,iBAAS,MAAM;AACf,YAAI,KAAK,aAAa,SAAU,MAAK,WAAW;AAAA,MAClD;AACA,aAAO;AAAA,IACT,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,WAAK,UAAU;AACf,YAAM;AAAA,IACR,CAAC;AACH,WAAO,KAAK;AAAA,EACd;AACF;AAEA,IAAM,oCAAN,MAAkF;AAAA,EACzE,YAA6B,OAAuB;AAAvB;AAAA,EAAwB;AAAA,EAE5D,MAAa,IAAO,KAAgC;AAClD,UAAM,QAAQ,MAAM,cAAc,KAAK,MAAM,IAAI,GAAG,CAAC;AACrD,WAAO,UAAU,SAAY,OAAQ;AAAA,EACvC;AAAA,EAEA,MAAa,IAAO,KAAa,OAAyB;AACxD,UAAM,cAAc,KAAK,MAAM,IAAI,OAAO,GAAG,CAAC;AAAA,EAChD;AAAA,EAEA,MAAa,OAAO,KAA4B;AAC9C,UAAM,cAAc,KAAK,MAAM,OAAO,GAAG,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAa,OAA0B;AACrC,UAAM,OAAO,MAAM,cAAc,KAAK,MAAM,WAAW,CAAC;AACxD,WAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,UAAI,OAAO,QAAQ,UAAU;AAC3B,cAAM,IAAI,MAAM,2DAA2D;AAAA,MAC7E;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,iBAAiB,QAAkC;AAC9D,UAAM,SAAS,MAAM,cAAc,KAAK,MAAM,cAAc,YAAY,WAAW,MAAM,CAAC,CAAC;AAC3F,WAAO,WAAW,QAAQ,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,WAAW,MAAM;AAAA,EAC1F;AACF;AAEA,eAAe,sBACb,cACA,WACsB;AACtB,QAAM,WAAW,MAAM,aAAa,cAAc,QAAW,CAAC,oBAAoB;AAChF,QAAI,CAAC,gBAAgB,iBAAiB,SAAS,SAAS,GAAG;AACzD,sBAAgB,kBAAkB,SAAS;AAAA,IAC7C;AAAA,EACF,CAAC;AACD,MAAI,SAAS,iBAAiB,SAAS,SAAS,EAAG,QAAO;AAE1D,QAAM,cAAc,SAAS,UAAU;AACvC,WAAS,MAAM;AACf,SAAO,aAAa,cAAc,aAAa,CAAC,oBAAoB;AAClE,QAAI,CAAC,gBAAgB,iBAAiB,SAAS,SAAS,GAAG;AACzD,sBAAgB,kBAAkB,SAAS;AAAA,IAC7C;AAAA,EACF,CAAC;AACH;AAEA,SAAS,aACP,cACA,SACA,SACsB;AACtB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,UACJ,YAAY,SAAY,UAAU,KAAK,YAAY,IAAI,UAAU,KAAK,cAAc,OAAO;AAC7F,QAAI,UAAU;AAEd,YAAQ,kBAAkB,MAAM,QAAQ,QAAQ,MAAM;AACtD,YAAQ,YAAY,MAAM;AACxB,UAAI,SAAS;AACX,gBAAQ,OAAO,MAAM;AACrB;AAAA,MACF;AACA,gBAAU;AACV,cAAQ,QAAQ,MAAM;AAAA,IACxB;AACA,YAAQ,UAAU,MAAM;AACtB,UAAI,QAAS;AACb,gBAAU;AACV,aAAO,QAAQ,SAAS,IAAI,MAAM,qCAAqC,YAAY,GAAG,CAAC;AAAA,IACzF;AACA,YAAQ,YAAY,MAAM;AACxB,UAAI,QAAS;AACb,gBAAU;AACV,aAAO,IAAI,MAAM,sBAAsB,YAAY,mCAAmC,CAAC;AAAA,IACzF;AAAA,EACF,CAAC;AACH;AAEA,SAAS,cAAiB,SAAoC;AAC5D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,YAAQ,UAAU,MAAM,OAAO,QAAQ,SAAS,IAAI,MAAM,2BAA2B,CAAC;AAAA,EACxF,CAAC;AACH;AAEA,SAAS,mBAAmB,aAA4C;AACtE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAY,aAAa,MAAM,QAAQ;AACvC,gBAAY,UAAU,MACpB,OAAO,YAAY,SAAS,IAAI,MAAM,oCAAoC,CAAC;AAC7E,gBAAY,UAAU,MACpB,OAAO,YAAY,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,EAC1E,CAAC;AACH;","names":[]}
@@ -0,0 +1,9 @@
1
+ // src/filesystem/internal/records.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null;
4
+ }
5
+
6
+ export {
7
+ isRecord
8
+ };
9
+ //# sourceMappingURL=chunk-U722UUFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/filesystem/internal/records.ts"],"sourcesContent":["/**\n * Structural guard shared by the filesystem providers that validate untrusted\n * payloads — stored IndexedDB records and Electron transport messages.\n */\n\n/** True for any non-null object, including arrays and class instances. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":";AAMO,SAAS,SAAS,OAAkD;AACzE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;","names":[]}