@fgv/ts-web-extras 5.0.2 → 5.1.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 (208) hide show
  1. package/.rush/temp/81e0881271ff236956b2f52e8ca99da6574c6e1e.tar.log +223 -0
  2. package/.rush/temp/chunked-rush-logs/ts-web-extras.build.chunks.jsonl +35 -25
  3. package/.rush/temp/operation/build/all.log +35 -25
  4. package/.rush/temp/operation/build/log-chunks.jsonl +35 -25
  5. package/.rush/temp/operation/build/state.json +1 -1
  6. package/.rush/temp/shrinkwrap-deps.json +175 -163
  7. package/config/jest.config.json +4 -1
  8. package/config/typedoc.json +6 -0
  9. package/dist/index.js +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/packlets/crypto-utils/browserCryptoProvider.js +254 -0
  12. package/dist/packlets/crypto-utils/browserCryptoProvider.js.map +1 -0
  13. package/dist/packlets/crypto-utils/browserHashProvider.js.map +1 -0
  14. package/dist/packlets/{crypto → crypto-utils}/index.js +1 -0
  15. package/dist/packlets/crypto-utils/index.js.map +1 -0
  16. package/dist/packlets/file-api-types/index.js +27 -3
  17. package/dist/packlets/file-api-types/index.js.map +1 -1
  18. package/dist/packlets/file-tree/directoryHandleStore.js +124 -0
  19. package/dist/packlets/file-tree/directoryHandleStore.js.map +1 -0
  20. package/dist/packlets/file-tree/fileApiTreeAccessors.js +76 -0
  21. package/dist/packlets/file-tree/fileApiTreeAccessors.js.map +1 -1
  22. package/dist/packlets/file-tree/fileSystemAccessTreeAccessors.js +345 -0
  23. package/dist/packlets/file-tree/fileSystemAccessTreeAccessors.js.map +1 -0
  24. package/dist/packlets/file-tree/index.js +3 -0
  25. package/dist/packlets/file-tree/index.js.map +1 -1
  26. package/dist/packlets/file-tree/localStorageTreeAccessors.js +308 -0
  27. package/dist/packlets/file-tree/localStorageTreeAccessors.js.map +1 -0
  28. package/dist/test/mocks/idb-keyval.js +6 -0
  29. package/dist/test/mocks/idb-keyval.js.map +1 -0
  30. package/dist/test/unit/browserHashProvider.test.js +1 -1
  31. package/dist/test/unit/browserHashProvider.test.js.map +1 -1
  32. package/dist/test/unit/directoryHandleStore.test.js +190 -0
  33. package/dist/test/unit/directoryHandleStore.test.js.map +1 -0
  34. package/dist/test/unit/fileApiTypes.test.js +30 -0
  35. package/dist/test/unit/fileApiTypes.test.js.map +1 -1
  36. package/dist/test/unit/fileSystemAccessTreeAccessors.test.js +517 -0
  37. package/dist/test/unit/fileSystemAccessTreeAccessors.test.js.map +1 -0
  38. package/dist/test/unit/localStorageTreeAccessors.test.js +595 -0
  39. package/dist/test/unit/localStorageTreeAccessors.test.js.map +1 -0
  40. package/dist/test/utils/fileSystemAccessMocks.js +271 -0
  41. package/dist/test/utils/fileSystemAccessMocks.js.map +1 -0
  42. package/dist/ts-web-extras.d.ts +460 -1
  43. package/dist/tsdoc-metadata.json +1 -1
  44. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider._constructor_.md +50 -0
  45. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.decrypt.md +104 -0
  46. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.derivekey.md +88 -0
  47. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.encrypt.md +72 -0
  48. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.frombase64.md +56 -0
  49. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.generatekey.md +19 -0
  50. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.generaterandombytes.md +56 -0
  51. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.md +169 -0
  52. package/docs/ts-web-extras.cryptoutils.browsercryptoprovider.tobase64.md +56 -0
  53. package/docs/{ts-web-extras.browserhashprovider.hashparts.md → ts-web-extras.cryptoutils.browserhashprovider.hashparts.md} +2 -2
  54. package/docs/{ts-web-extras.browserhashprovider.hashstring.md → ts-web-extras.cryptoutils.browserhashprovider.hashstring.md} +2 -2
  55. package/docs/{ts-web-extras.browserhashprovider.md → ts-web-extras.cryptoutils.browserhashprovider.md} +4 -4
  56. package/docs/ts-web-extras.cryptoutils.createbrowsercryptoprovider.md +19 -0
  57. package/docs/ts-web-extras.cryptoutils.md +71 -0
  58. package/docs/ts-web-extras.fileapitreeaccessors.createfromlocalstorage.md +74 -0
  59. package/docs/ts-web-extras.fileapitreeaccessors.createpersistent.md +76 -0
  60. package/docs/ts-web-extras.fileapitreeaccessors.md +32 -0
  61. package/docs/ts-web-extras.filesystemaccesstreeaccessors._constructor_.md +114 -0
  62. package/docs/ts-web-extras.filesystemaccesstreeaccessors.fileismutable.md +52 -0
  63. package/docs/ts-web-extras.filesystemaccesstreeaccessors.fromdirectoryhandle.md +72 -0
  64. package/docs/ts-web-extras.filesystemaccesstreeaccessors.getdirtypaths.md +17 -0
  65. package/docs/ts-web-extras.filesystemaccesstreeaccessors.isdirty.md +17 -0
  66. package/docs/ts-web-extras.filesystemaccesstreeaccessors.md +159 -0
  67. package/docs/ts-web-extras.filesystemaccesstreeaccessors.savefilecontents.md +66 -0
  68. package/docs/ts-web-extras.filesystemaccesstreeaccessors.synctodisk.md +17 -0
  69. package/docs/ts-web-extras.ifilesystemaccesstreeparams.autosync.md +13 -0
  70. package/docs/ts-web-extras.ifilesystemaccesstreeparams.md +78 -0
  71. package/docs/ts-web-extras.ifilesystemaccesstreeparams.requirewritepermission.md +13 -0
  72. package/docs/ts-web-extras.ilocalstoragetreeparams.autosync.md +13 -0
  73. package/docs/ts-web-extras.ilocalstoragetreeparams.md +97 -0
  74. package/docs/ts-web-extras.ilocalstoragetreeparams.pathtokeymap.md +13 -0
  75. package/docs/ts-web-extras.ilocalstoragetreeparams.storage.md +13 -0
  76. package/docs/ts-web-extras.localstoragetreeaccessors.fileismutable.md +56 -0
  77. package/docs/ts-web-extras.localstoragetreeaccessors.fromstorage.md +56 -0
  78. package/docs/ts-web-extras.localstoragetreeaccessors.getdirtypaths.md +19 -0
  79. package/docs/ts-web-extras.localstoragetreeaccessors.isdirty.md +19 -0
  80. package/docs/ts-web-extras.localstoragetreeaccessors.md +131 -0
  81. package/docs/ts-web-extras.localstoragetreeaccessors.savefilecontents.md +72 -0
  82. package/docs/ts-web-extras.localstoragetreeaccessors.synctodisk.md +19 -0
  83. package/docs/ts-web-extras.md +50 -4
  84. package/etc/ts-web-extras.api.md +91 -1
  85. package/lib/index.d.ts +2 -1
  86. package/lib/index.d.ts.map +1 -1
  87. package/lib/index.js +25 -2
  88. package/lib/index.js.map +1 -1
  89. package/lib/packlets/crypto-utils/browserCryptoProvider.d.ts +77 -0
  90. package/lib/packlets/crypto-utils/browserCryptoProvider.d.ts.map +1 -0
  91. package/lib/packlets/crypto-utils/browserCryptoProvider.js +259 -0
  92. package/lib/packlets/crypto-utils/browserCryptoProvider.js.map +1 -0
  93. package/lib/packlets/crypto-utils/browserHashProvider.d.ts.map +1 -0
  94. package/lib/packlets/crypto-utils/browserHashProvider.js.map +1 -0
  95. package/lib/packlets/{crypto → crypto-utils}/index.d.ts +1 -0
  96. package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
  97. package/lib/packlets/{crypto → crypto-utils}/index.js +1 -0
  98. package/lib/packlets/crypto-utils/index.js.map +1 -0
  99. package/lib/packlets/file-api-types/index.d.ts.map +1 -1
  100. package/lib/packlets/file-api-types/index.js +27 -3
  101. package/lib/packlets/file-api-types/index.js.map +1 -1
  102. package/lib/packlets/file-tree/directoryHandleStore.d.ts +59 -0
  103. package/lib/packlets/file-tree/directoryHandleStore.d.ts.map +1 -0
  104. package/lib/packlets/file-tree/directoryHandleStore.js +128 -0
  105. package/lib/packlets/file-tree/directoryHandleStore.js.map +1 -0
  106. package/lib/packlets/file-tree/fileApiTreeAccessors.d.ts +57 -0
  107. package/lib/packlets/file-tree/fileApiTreeAccessors.d.ts.map +1 -1
  108. package/lib/packlets/file-tree/fileApiTreeAccessors.js +76 -0
  109. package/lib/packlets/file-tree/fileApiTreeAccessors.js.map +1 -1
  110. package/lib/packlets/file-tree/fileSystemAccessTreeAccessors.d.ts +136 -0
  111. package/lib/packlets/file-tree/fileSystemAccessTreeAccessors.d.ts.map +1 -0
  112. package/lib/packlets/file-tree/fileSystemAccessTreeAccessors.js +349 -0
  113. package/lib/packlets/file-tree/fileSystemAccessTreeAccessors.js.map +1 -0
  114. package/lib/packlets/file-tree/index.d.ts +3 -0
  115. package/lib/packlets/file-tree/index.d.ts.map +1 -1
  116. package/lib/packlets/file-tree/index.js +3 -0
  117. package/lib/packlets/file-tree/index.js.map +1 -1
  118. package/lib/packlets/file-tree/localStorageTreeAccessors.d.ts +129 -0
  119. package/lib/packlets/file-tree/localStorageTreeAccessors.d.ts.map +1 -0
  120. package/lib/packlets/file-tree/localStorageTreeAccessors.js +312 -0
  121. package/lib/packlets/file-tree/localStorageTreeAccessors.js.map +1 -0
  122. package/lib/test/mocks/idb-keyval.d.ts +6 -0
  123. package/lib/test/mocks/idb-keyval.d.ts.map +1 -0
  124. package/lib/test/mocks/idb-keyval.js +9 -0
  125. package/lib/test/mocks/idb-keyval.js.map +1 -0
  126. package/lib/test/unit/browserHashProvider.test.js +21 -21
  127. package/lib/test/unit/browserHashProvider.test.js.map +1 -1
  128. package/lib/test/unit/directoryHandleStore.test.d.ts +2 -0
  129. package/lib/test/unit/directoryHandleStore.test.d.ts.map +1 -0
  130. package/lib/test/unit/directoryHandleStore.test.js +192 -0
  131. package/lib/test/unit/directoryHandleStore.test.js.map +1 -0
  132. package/lib/test/unit/fileApiTypes.test.js +30 -0
  133. package/lib/test/unit/fileApiTypes.test.js.map +1 -1
  134. package/lib/test/unit/fileSystemAccessTreeAccessors.test.d.ts +2 -0
  135. package/lib/test/unit/fileSystemAccessTreeAccessors.test.d.ts.map +1 -0
  136. package/lib/test/unit/fileSystemAccessTreeAccessors.test.js +519 -0
  137. package/lib/test/unit/fileSystemAccessTreeAccessors.test.js.map +1 -0
  138. package/lib/test/unit/localStorageTreeAccessors.test.d.ts +2 -0
  139. package/lib/test/unit/localStorageTreeAccessors.test.d.ts.map +1 -0
  140. package/lib/test/unit/localStorageTreeAccessors.test.js +597 -0
  141. package/lib/test/unit/localStorageTreeAccessors.test.js.map +1 -0
  142. package/lib/test/utils/fileSystemAccessMocks.d.ts +53 -0
  143. package/lib/test/utils/fileSystemAccessMocks.d.ts.map +1 -0
  144. package/lib/test/utils/fileSystemAccessMocks.js +277 -0
  145. package/lib/test/utils/fileSystemAccessMocks.js.map +1 -0
  146. package/package.json +27 -20
  147. package/rush-logs/ts-web-extras.build.cache.log +3 -1
  148. package/rush-logs/ts-web-extras.build.log +35 -25
  149. package/src/index.ts +2 -2
  150. package/src/packlets/crypto-utils/browserCryptoProvider.ts +311 -0
  151. package/src/packlets/{crypto → crypto-utils}/index.ts +1 -0
  152. package/src/packlets/file-api-types/index.ts +24 -3
  153. package/src/packlets/file-tree/directoryHandleStore.ts +136 -0
  154. package/src/packlets/file-tree/fileApiTreeAccessors.ts +90 -0
  155. package/src/packlets/file-tree/fileSystemAccessTreeAccessors.ts +427 -0
  156. package/src/packlets/file-tree/index.ts +3 -0
  157. package/src/packlets/file-tree/localStorageTreeAccessors.ts +377 -0
  158. package/src/test/mocks/idb-keyval.ts +5 -0
  159. package/src/test/unit/browserHashProvider.test.ts +1 -1
  160. package/src/test/unit/directoryHandleStore.test.ts +251 -0
  161. package/src/test/unit/fileApiTypes.test.ts +36 -0
  162. package/src/test/unit/fileSystemAccessTreeAccessors.test.ts +732 -0
  163. package/src/test/unit/localStorageTreeAccessors.test.ts +746 -0
  164. package/src/test/utils/fileSystemAccessMocks.ts +353 -0
  165. package/temp/build/typescript/ts_8nwakTlr.json +1 -0
  166. package/temp/coverage/crypto-utils/browserCryptoProvider.ts.html +1018 -0
  167. package/temp/coverage/{crypto → crypto-utils}/browserHashProvider.ts.html +3 -3
  168. package/temp/coverage/{lcov-report/crypto → crypto-utils}/index.html +21 -6
  169. package/temp/coverage/file-tree/directoryHandleStore.ts.html +493 -0
  170. package/temp/coverage/file-tree/fileApiTreeAccessors.ts.html +276 -6
  171. package/temp/coverage/file-tree/fileSystemAccessTreeAccessors.ts.html +1366 -0
  172. package/temp/coverage/file-tree/index.html +55 -10
  173. package/temp/coverage/file-tree/localStorageTreeAccessors.ts.html +1216 -0
  174. package/temp/coverage/helpers/fileTreeHelpers.ts.html +1 -1
  175. package/temp/coverage/helpers/index.html +1 -1
  176. package/temp/coverage/index.html +15 -15
  177. package/temp/coverage/lcov-report/crypto-utils/browserCryptoProvider.ts.html +1018 -0
  178. package/temp/coverage/lcov-report/{crypto → crypto-utils}/browserHashProvider.ts.html +3 -3
  179. package/temp/coverage/{crypto → lcov-report/crypto-utils}/index.html +21 -6
  180. package/temp/coverage/lcov-report/file-tree/directoryHandleStore.ts.html +493 -0
  181. package/temp/coverage/lcov-report/file-tree/fileApiTreeAccessors.ts.html +276 -6
  182. package/temp/coverage/lcov-report/file-tree/fileSystemAccessTreeAccessors.ts.html +1366 -0
  183. package/temp/coverage/lcov-report/file-tree/index.html +55 -10
  184. package/temp/coverage/lcov-report/file-tree/localStorageTreeAccessors.ts.html +1216 -0
  185. package/temp/coverage/lcov-report/helpers/fileTreeHelpers.ts.html +1 -1
  186. package/temp/coverage/lcov-report/helpers/index.html +1 -1
  187. package/temp/coverage/lcov-report/index.html +15 -15
  188. package/temp/coverage/lcov-report/url-utils/index.html +1 -1
  189. package/temp/coverage/lcov-report/url-utils/urlParams.ts.html +1 -1
  190. package/temp/coverage/lcov.info +2128 -451
  191. package/temp/coverage/url-utils/index.html +1 -1
  192. package/temp/coverage/url-utils/urlParams.ts.html +1 -1
  193. package/temp/test/jest/haste-map-7492f1b44480e0cdd1f220078fb3afd8-c8dd6c3430605adeb2f1cadf4f75e791-8c9336785555d572065b28c111982ba4 +0 -0
  194. package/temp/test/jest/perf-cache-7492f1b44480e0cdd1f220078fb3afd8-da39a3ee5e6b4b0d3255bfef95601890 +1 -1
  195. package/temp/ts-web-extras.api.json +3236 -385
  196. package/temp/ts-web-extras.api.md +91 -1
  197. package/dist/packlets/crypto/browserHashProvider.js.map +0 -1
  198. package/dist/packlets/crypto/index.js.map +0 -1
  199. package/lib/packlets/crypto/browserHashProvider.d.ts.map +0 -1
  200. package/lib/packlets/crypto/browserHashProvider.js.map +0 -1
  201. package/lib/packlets/crypto/index.d.ts.map +0 -1
  202. package/lib/packlets/crypto/index.js.map +0 -1
  203. package/temp/build/typescript/ts_vnCx6LlY.json +0 -1
  204. /package/dist/packlets/{crypto → crypto-utils}/browserHashProvider.js +0 -0
  205. /package/lib/packlets/{crypto → crypto-utils}/browserHashProvider.d.ts +0 -0
  206. /package/lib/packlets/{crypto → crypto-utils}/browserHashProvider.js +0 -0
  207. /package/src/packlets/{crypto → crypto-utils}/browserHashProvider.ts +0 -0
  208. /package/temp/test/jest/jest-transform-cache-7492f1b44480e0cdd1f220078fb3afd8-79ef2876fae7ca75eedb2aa53dc48338/{0e/package_0eb6535f5987849d93ea51ef33a14cf6 → 8d/package_8dcbedef69e4299f0f51fcda8f4f1c8e} +0 -0
@@ -0,0 +1,519 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ require("@fgv/ts-utils-jest");
25
+ const file_tree_1 = require("../../packlets/file-tree");
26
+ const file_tree_2 = require("../../packlets/file-tree");
27
+ const ts_json_base_1 = require("@fgv/ts-json-base");
28
+ const fileSystemAccessMocks_1 = require("../utils/fileSystemAccessMocks");
29
+ describe('FileSystemAccessTreeAccessors', () => {
30
+ describe('fromDirectoryHandle', () => {
31
+ test('creates accessors from directory handle with write permission', async () => {
32
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
33
+ 'test.txt': { content: 'test content', type: 'text/plain' },
34
+ 'config.json': { content: '{"name":"test"}', type: 'application/json' }
35
+ });
36
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle);
37
+ expect(result).toSucceedAndSatisfy((accessors) => {
38
+ expect(accessors).toBeInstanceOf(file_tree_1.FileSystemAccessTreeAccessors);
39
+ expect(accessors.getFileContents('/test.txt')).toSucceedWith('test content');
40
+ expect(accessors.getFileContents('/config.json')).toSucceedWith('{"name":"test"}');
41
+ });
42
+ });
43
+ test('creates accessors with nested directory structure', async () => {
44
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
45
+ 'src/index.js': { content: 'console.log("hello");', type: 'text/javascript' },
46
+ 'src/utils/helper.js': { content: 'export const help = () => {};', type: 'text/javascript' },
47
+ 'config/app.json': { content: '{"version":"1.0"}', type: 'application/json' }
48
+ });
49
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle);
50
+ expect(result).toSucceed();
51
+ const accessors = result.orThrow();
52
+ expect(accessors.getFileContents('/src/index.js')).toSucceed();
53
+ expect(accessors.getFileContents('/src/utils/helper.js')).toSucceed();
54
+ expect(accessors.getFileContents('/config/app.json')).toSucceed();
55
+ });
56
+ test('fails when write permission required but not granted', async () => {
57
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'test', type: 'text/plain' } }, { hasWritePermission: false });
58
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
59
+ requireWritePermission: true
60
+ });
61
+ expect(result).toFailWith(/write permission required/i);
62
+ });
63
+ test('succeeds with read-only when write permission not required', async () => {
64
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'test', type: 'text/plain' } }, { hasWritePermission: false });
65
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
66
+ requireWritePermission: false
67
+ });
68
+ expect(result).toSucceedAndSatisfy((accessors) => {
69
+ expect(accessors.getFileContents('/test.txt')).toSucceedWith('test');
70
+ });
71
+ });
72
+ test('applies custom prefix to file paths', async () => {
73
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
74
+ 'test.txt': { content: 'test', type: 'text/plain' }
75
+ });
76
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
77
+ prefix: '/myapp'
78
+ });
79
+ expect(result).toSucceed();
80
+ const accessors = result.orThrow();
81
+ // With prefix, files are accessed with the prefix prepended
82
+ expect(accessors.getFileContents('/myapp/test.txt')).toSucceed();
83
+ });
84
+ });
85
+ describe('fromFileHandle', () => {
86
+ test('creates single-file accessors from a file handle', async () => {
87
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', {
88
+ content: 'metadata:\n name: Test\nitems: {}',
89
+ type: 'text/plain'
90
+ });
91
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle);
92
+ expect(result).toSucceedAndSatisfy((accessors) => {
93
+ expect(accessors).toBeInstanceOf(file_tree_1.FileSystemAccessTreeAccessors);
94
+ expect(accessors.getFileContents('/collection.yaml')).toSucceedWith('metadata:\n name: Test\nitems: {}');
95
+ });
96
+ });
97
+ test('fails when write permission required but not granted', async () => {
98
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', { content: 'content', type: 'text/plain' }, undefined, { hasWritePermission: false });
99
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle, {
100
+ requireWritePermission: true
101
+ });
102
+ expect(result).toFailWith(/write permission required/i);
103
+ });
104
+ test('succeeds read-only when write permission not required', async () => {
105
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', { content: 'content', type: 'text/plain' }, undefined, { hasWritePermission: false });
106
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle, {
107
+ requireWritePermission: false
108
+ });
109
+ expect(result).toSucceedAndSatisfy((accessors) => {
110
+ expect(accessors.getFileContents('/collection.yaml')).toSucceedWith('content');
111
+ });
112
+ });
113
+ test('grants permission via prompt', async () => {
114
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', { content: 'content', type: 'text/plain' }, undefined, { permissionStatus: 'prompt', requestGranted: true });
115
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle);
116
+ expect(result).toSucceed();
117
+ });
118
+ test('fails gracefully when queryPermission throws', async () => {
119
+ const fileHandle = {
120
+ kind: 'file',
121
+ name: 'collection.yaml',
122
+ async getFile() {
123
+ return (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', {
124
+ content: 'content',
125
+ type: 'text/plain'
126
+ }).getFile();
127
+ },
128
+ async createWritable() {
129
+ throw new Error('not writable');
130
+ },
131
+ async queryPermission() {
132
+ throw new Error('Permission API unavailable');
133
+ },
134
+ async requestPermission() {
135
+ throw new Error('Permission API unavailable');
136
+ },
137
+ isSameEntry: async () => false
138
+ };
139
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle, {
140
+ requireWritePermission: true
141
+ });
142
+ expect(result).toFailWith(/write permission required/i);
143
+ });
144
+ test('places file at specified filePath when filePath is provided', async () => {
145
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', {
146
+ content: 'items: {}',
147
+ type: 'text/plain'
148
+ });
149
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle, {
150
+ filePath: '/data/confections/collection.yaml'
151
+ });
152
+ expect(result).toSucceedAndSatisfy((accessors) => {
153
+ expect(accessors.getFileContents('/data/confections/collection.yaml')).toSucceedWith('items: {}');
154
+ });
155
+ });
156
+ test('write-back syncs to original file handle', async () => {
157
+ let writtenContent = '';
158
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', { content: 'original', type: 'text/plain' }, (content) => {
159
+ writtenContent = content;
160
+ });
161
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromFileHandle(fileHandle)).orThrow();
162
+ accessors.saveFileContents('/collection.yaml', 'modified').orThrow();
163
+ expect(accessors.isDirty()).toBe(true);
164
+ const syncResult = await accessors.syncToDisk();
165
+ expect(syncResult).toSucceed();
166
+ expect(accessors.isDirty()).toBe(false);
167
+ expect(writtenContent).toBe('modified');
168
+ });
169
+ });
170
+ describe('FileApiTreeAccessors.createPersistentFromFile', () => {
171
+ test('creates a FileTree from a single file handle', async () => {
172
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', {
173
+ content: 'metadata:\n name: Test\nitems: {}',
174
+ type: 'text/plain'
175
+ });
176
+ const result = await file_tree_2.FileApiTreeAccessors.createPersistentFromFile(fileHandle);
177
+ expect(result).toSucceedAndSatisfy((tree) => {
178
+ expect(tree).toBeInstanceOf(ts_json_base_1.FileTree.FileTree);
179
+ expect(tree.getFile('/collection.yaml')).toSucceed();
180
+ });
181
+ });
182
+ test('fails when file handle cannot be opened', async () => {
183
+ const fileHandle = (0, fileSystemAccessMocks_1.createMockFileHandle)('collection.yaml', {
184
+ content: 'content',
185
+ type: 'text/plain',
186
+ failOnRead: true
187
+ });
188
+ const result = await file_tree_2.FileApiTreeAccessors.createPersistentFromFile(fileHandle);
189
+ expect(result).toFail();
190
+ });
191
+ });
192
+ describe('persistence operations', () => {
193
+ describe('isDirty and getDirtyPaths', () => {
194
+ test('returns false when no changes made', async () => {
195
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
196
+ 'test.txt': { content: 'original', type: 'text/plain' }
197
+ });
198
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle)).orThrow();
199
+ expect(accessors.isDirty()).toBe(false);
200
+ expect(accessors.getDirtyPaths()).toEqual([]);
201
+ });
202
+ test('returns true after saving file contents', async () => {
203
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
204
+ 'test.txt': { content: 'original', type: 'text/plain' }
205
+ });
206
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
207
+ accessors.saveFileContents('/test.txt', 'modified').orThrow();
208
+ expect(accessors.isDirty()).toBe(true);
209
+ expect(accessors.getDirtyPaths()).toEqual(['/test.txt']);
210
+ });
211
+ test('tracks multiple dirty files', async () => {
212
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
213
+ 'file1.txt': { content: 'content1', type: 'text/plain' },
214
+ 'file2.txt': { content: 'content2', type: 'text/plain' }
215
+ });
216
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
217
+ accessors.saveFileContents('/file1.txt', 'modified1').orThrow();
218
+ accessors.saveFileContents('/file2.txt', 'modified2').orThrow();
219
+ expect(accessors.isDirty()).toBe(true);
220
+ expect(accessors.getDirtyPaths()).toContain('/file1.txt');
221
+ expect(accessors.getDirtyPaths()).toContain('/file2.txt');
222
+ });
223
+ });
224
+ describe('syncToDisk', () => {
225
+ test('syncs modified files to disk', async () => {
226
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
227
+ 'test.txt': { content: 'original', type: 'text/plain' }
228
+ });
229
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
230
+ accessors.saveFileContents('/test.txt', 'modified').orThrow();
231
+ expect(accessors.isDirty()).toBe(true);
232
+ const syncResult = await accessors.syncToDisk();
233
+ expect(syncResult).toSucceed();
234
+ expect(accessors.isDirty()).toBe(false);
235
+ // Verify the file was actually written
236
+ const fileHandle = await dirHandle.getFileHandle('test.txt');
237
+ const file = await fileHandle.getFile();
238
+ const content = await file.text();
239
+ expect(content).toBe('modified');
240
+ });
241
+ test('syncs multiple files in one operation', async () => {
242
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
243
+ 'file1.txt': { content: 'content1', type: 'text/plain' },
244
+ 'file2.txt': { content: 'content2', type: 'text/plain' }
245
+ });
246
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
247
+ accessors.saveFileContents('/file1.txt', 'modified1').orThrow();
248
+ accessors.saveFileContents('/file2.txt', 'modified2').orThrow();
249
+ const syncResult = await accessors.syncToDisk();
250
+ expect(syncResult).toSucceed();
251
+ expect(accessors.isDirty()).toBe(false);
252
+ });
253
+ test('creates new files when syncing', async () => {
254
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
255
+ 'existing.txt': { content: 'existing', type: 'text/plain' }
256
+ });
257
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
258
+ // Add a new file to the in-memory tree
259
+ accessors.saveFileContents('/newfile.txt', 'new content').orThrow();
260
+ const syncResult = await accessors.syncToDisk();
261
+ expect(syncResult).toSucceed();
262
+ // Verify the new file was created
263
+ const fileHandle = await dirHandle.getFileHandle('newfile.txt');
264
+ expect(fileHandle).toBeDefined();
265
+ const file = await fileHandle.getFile();
266
+ const content = await file.text();
267
+ expect(content).toBe('new content');
268
+ });
269
+ test('creates nested directories when syncing new files', async () => {
270
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
271
+ 'existing.txt': { content: 'existing', type: 'text/plain' }
272
+ });
273
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
274
+ accessors.saveFileContents('/src/utils/helper.js', 'export const help = () => {};').orThrow();
275
+ const syncResult = await accessors.syncToDisk();
276
+ expect(syncResult).toSucceed();
277
+ // Verify nested directories were created
278
+ const srcDir = await dirHandle.getDirectoryHandle('src');
279
+ const utilsDir = await srcDir.getDirectoryHandle('utils');
280
+ const fileHandle = await utilsDir.getFileHandle('helper.js');
281
+ expect(fileHandle).toBeDefined();
282
+ });
283
+ test('fails when write permission not granted', async () => {
284
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'original', type: 'text/plain' } }, { hasWritePermission: false });
285
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
286
+ mutable: true,
287
+ requireWritePermission: false
288
+ })).orThrow();
289
+ accessors.saveFileContents('/test.txt', 'modified').orThrow();
290
+ const syncResult = await accessors.syncToDisk();
291
+ expect(syncResult).toFailWith(/write permission not granted/i);
292
+ });
293
+ test('clears dirty state after successful sync', async () => {
294
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
295
+ 'file1.txt': { content: 'content1', type: 'text/plain' },
296
+ 'file2.txt': { content: 'content2', type: 'text/plain' }
297
+ });
298
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
299
+ accessors.saveFileContents('/file1.txt', 'modified1').orThrow();
300
+ accessors.saveFileContents('/file2.txt', 'modified2').orThrow();
301
+ expect(accessors.isDirty()).toBe(true);
302
+ const syncResult = await accessors.syncToDisk();
303
+ expect(syncResult).toSucceed();
304
+ expect(accessors.isDirty()).toBe(false);
305
+ expect(accessors.getDirtyPaths()).toEqual([]);
306
+ });
307
+ });
308
+ describe('autoSync mode', () => {
309
+ test('automatically syncs changes when autoSync is enabled', async () => {
310
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
311
+ 'test.txt': { content: 'original', type: 'text/plain' }
312
+ });
313
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
314
+ mutable: true,
315
+ autoSync: true
316
+ })).orThrow();
317
+ accessors.saveFileContents('/test.txt', 'modified').orThrow();
318
+ // Give auto-sync a moment to complete (it's fire-and-forget)
319
+ await new Promise((resolve) => setTimeout(resolve, 10));
320
+ // Verify the file was written
321
+ const fileHandle = await dirHandle.getFileHandle('test.txt');
322
+ const file = await fileHandle.getFile();
323
+ const content = await file.text();
324
+ expect(content).toBe('modified');
325
+ });
326
+ test('does not auto-sync when autoSync is disabled', async () => {
327
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
328
+ 'test.txt': { content: 'original', type: 'text/plain' }
329
+ });
330
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
331
+ mutable: true,
332
+ autoSync: false
333
+ })).orThrow();
334
+ accessors.saveFileContents('/test.txt', 'modified').orThrow();
335
+ // Verify file is marked dirty but not synced
336
+ expect(accessors.isDirty()).toBe(true);
337
+ expect(accessors.getDirtyPaths()).toContain('/test.txt');
338
+ // In-memory content should be modified
339
+ expect(accessors.getFileContents('/test.txt')).toSucceedWith('modified');
340
+ });
341
+ });
342
+ });
343
+ describe('fileIsMutable', () => {
344
+ test('returns persistent detail when write permission granted', async () => {
345
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
346
+ 'test.txt': { content: 'test', type: 'text/plain' }
347
+ });
348
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
349
+ const result = accessors.fileIsMutable('/test.txt');
350
+ expect(result).toSucceedWithDetail(true, 'persistent');
351
+ });
352
+ test('returns transient detail when write permission not granted', async () => {
353
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'test', type: 'text/plain' } }, { hasWritePermission: false });
354
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
355
+ mutable: true,
356
+ requireWritePermission: false
357
+ })).orThrow();
358
+ const result = accessors.fileIsMutable('/test.txt');
359
+ expect(result).toSucceedWithDetail(true, 'transient');
360
+ });
361
+ test('returns not-mutable when mutability disabled', async () => {
362
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
363
+ 'test.txt': { content: 'test', type: 'text/plain' }
364
+ });
365
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: false })).orThrow();
366
+ const result = accessors.fileIsMutable('/test.txt');
367
+ expect(result).toFailWithDetail(/mutability is disabled/i, 'not-mutable');
368
+ });
369
+ });
370
+ describe('integration with FileApiTreeAccessors', () => {
371
+ test('createPersistent creates FileTree with persistent accessors', async () => {
372
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
373
+ 'test.txt': { content: 'test content', type: 'text/plain' }
374
+ });
375
+ const result = await file_tree_2.FileApiTreeAccessors.createPersistent(dirHandle, { mutable: true });
376
+ expect(result).toSucceed();
377
+ const tree = result.orThrow();
378
+ expect(tree).toBeInstanceOf(ts_json_base_1.FileTree.FileTree);
379
+ expect(ts_json_base_1.FileTree.isPersistentAccessors(tree.hal)).toBe(true);
380
+ const file = tree.getFile('/test.txt').orThrow();
381
+ expect(file.getIsMutable()).toSucceedWithDetail(true, 'persistent');
382
+ });
383
+ test('createPersistent with autoSync option', async () => {
384
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
385
+ 'test.txt': { content: 'original', type: 'text/plain' }
386
+ });
387
+ const result = await file_tree_2.FileApiTreeAccessors.createPersistent(dirHandle, {
388
+ mutable: true,
389
+ autoSync: true
390
+ });
391
+ expect(result).toSucceedAndSatisfy((tree) => {
392
+ expect(tree).toBeInstanceOf(ts_json_base_1.FileTree.FileTree);
393
+ });
394
+ const tree = result.orThrow();
395
+ const file = tree.getFile('/test.txt').orThrow();
396
+ file.setRawContents('modified').orThrow();
397
+ // Give auto-sync time to complete
398
+ await new Promise((resolve) => setTimeout(resolve, 10));
399
+ // Verify file was written
400
+ const fileHandle = await dirHandle.getFileHandle('test.txt');
401
+ const diskFile = await fileHandle.getFile();
402
+ const content = await diskFile.text();
403
+ expect(content).toBe('modified');
404
+ });
405
+ test('createPersistent fails with appropriate error', async () => {
406
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'test', type: 'text/plain' } }, { hasWritePermission: false });
407
+ const result = await file_tree_2.FileApiTreeAccessors.createPersistent(dirHandle, {
408
+ requireWritePermission: true
409
+ });
410
+ expect(result).toFailWith(/write permission required/i);
411
+ });
412
+ });
413
+ describe('error handling', () => {
414
+ test('handles permission query errors gracefully', async () => {
415
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'test', type: 'text/plain' } }, { permissionError: true });
416
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
417
+ requireWritePermission: false
418
+ });
419
+ // Should succeed but without write permission due to error
420
+ expect(result).toSucceed();
421
+ });
422
+ test('handles permission status "prompt" by requesting permission', async () => {
423
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', { 'test.txt': { content: 'test', type: 'text/plain' } }, { permissionStatus: 'prompt', requestGranted: true });
424
+ const result = await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, {
425
+ requireWritePermission: true
426
+ });
427
+ expect(result).toSucceedAndSatisfy((accessors) => {
428
+ expect(accessors).toBeInstanceOf(file_tree_1.FileSystemAccessTreeAccessors);
429
+ });
430
+ });
431
+ test('handles sync failures with aggregated errors', async () => {
432
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
433
+ 'file1.txt': { content: 'content1', type: 'text/plain' },
434
+ 'file2.txt': { content: 'content2', type: 'text/plain' }
435
+ });
436
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
437
+ // Modify files
438
+ accessors.saveFileContents('/file1.txt', 'modified1').orThrow();
439
+ accessors.saveFileContents('/file2.txt', 'modified2').orThrow();
440
+ // Replace the file handles with ones that fail to write
441
+ const handles = accessors._handles;
442
+ for (const [, handle] of handles) {
443
+ handle.createWritable = jest.fn().mockRejectedValue(new Error('Write permission denied'));
444
+ }
445
+ const syncResult = await accessors.syncToDisk();
446
+ expect(syncResult).toFailWith(/Failed to sync 2 file\(s\)/i);
447
+ });
448
+ test('handles getFileContents failure during sync', async () => {
449
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
450
+ 'test.txt': { content: 'original', type: 'text/plain' }
451
+ });
452
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
453
+ // Add a new file
454
+ accessors.saveFileContents('/newfile.txt', 'new content').orThrow();
455
+ // Override getFileContents to fail
456
+ const originalGetFileContents = accessors.getFileContents.bind(accessors);
457
+ accessors.getFileContents = jest.fn((path) => {
458
+ if (path === '/newfile.txt') {
459
+ return { isSuccess: () => false, isFailure: () => true, message: 'File read error' };
460
+ }
461
+ return originalGetFileContents(path);
462
+ });
463
+ const syncResult = await accessors.syncToDisk();
464
+ expect(syncResult).toFailWith(/File read error/i);
465
+ });
466
+ test('handles file write failure during sync', async () => {
467
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
468
+ 'test.txt': { content: 'original', type: 'text/plain' }
469
+ });
470
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
471
+ accessors.saveFileContents('/test.txt', 'modified').orThrow();
472
+ // Replace the handle with one that fails to write
473
+ const handles = accessors._handles;
474
+ const handle = handles.get('/test.txt');
475
+ handle.createWritable = jest.fn().mockRejectedValue(new Error('Disk full'));
476
+ const syncResult = await accessors.syncToDisk();
477
+ expect(syncResult).toFailWith(/Failed to write file.*Disk full/i);
478
+ });
479
+ test('handles invalid file path with no filename', async () => {
480
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
481
+ 'existing.txt': { content: 'existing', type: 'text/plain' }
482
+ });
483
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
484
+ // Override resolveAbsolutePath to return an empty path to trigger the error
485
+ const originalResolveAbsolutePath = accessors.resolveAbsolutePath.bind(accessors);
486
+ accessors.resolveAbsolutePath = jest.fn((path) => {
487
+ if (path === '/badpath') {
488
+ return ''; // This will result in no parts after split
489
+ }
490
+ return originalResolveAbsolutePath(path);
491
+ });
492
+ // Create a new file with the bad path - don't throw if it fails
493
+ const saveResult = accessors.saveFileContents('/badpath', 'content');
494
+ if (saveResult.isFailure()) {
495
+ // saveFileContents itself might reject the path
496
+ expect(saveResult).toFailWith(/invalid file path/i);
497
+ }
498
+ else {
499
+ // If save succeeded, sync should fail
500
+ const syncResult = await accessors.syncToDisk();
501
+ expect(syncResult).toFailWith(/Invalid file path/i);
502
+ }
503
+ });
504
+ test('handles file creation failure in _createAndWriteFile', async () => {
505
+ const dirHandle = (0, fileSystemAccessMocks_1.createMockDirectoryHandle)('/', {
506
+ 'existing.txt': { content: 'existing', type: 'text/plain' }
507
+ });
508
+ const accessors = (await file_tree_1.FileSystemAccessTreeAccessors.fromDirectoryHandle(dirHandle, { mutable: true })).orThrow();
509
+ // Add a new file that will need to be created
510
+ accessors.saveFileContents('/newdir/newfile.txt', 'new content').orThrow();
511
+ // Mock getDirectoryHandle to fail
512
+ const rootDir = accessors._rootDir;
513
+ rootDir.getDirectoryHandle = jest.fn().mockRejectedValue(new Error('Permission denied'));
514
+ const syncResult = await accessors.syncToDisk();
515
+ expect(syncResult).toFailWith(/Failed to create file.*Permission denied/i);
516
+ });
517
+ });
518
+ });
519
+ //# sourceMappingURL=fileSystemAccessTreeAccessors.test.js.map