@fgv/ts-json-base 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 (42) hide show
  1. package/dist/packlets/converters/converters.js +36 -14
  2. package/dist/packlets/file-tree/directoryItem.js +35 -4
  3. package/dist/packlets/file-tree/fileItem.js +37 -9
  4. package/dist/packlets/file-tree/fileTreeAccessors.js +24 -1
  5. package/dist/packlets/file-tree/filterSpec.js +74 -0
  6. package/dist/packlets/file-tree/fsTree.js +73 -12
  7. package/dist/packlets/file-tree/in-memory/inMemoryTree.js +204 -21
  8. package/dist/packlets/file-tree/in-memory/treeBuilder.js +23 -0
  9. package/dist/packlets/file-tree/index.browser.js +1 -0
  10. package/dist/packlets/file-tree/index.js +1 -0
  11. package/dist/packlets/json-file/file.js +1 -1
  12. package/dist/packlets/json-file/jsonFsHelper.js +1 -1
  13. package/dist/packlets/validators/validators.js +8 -8
  14. package/dist/ts-json-base.d.ts +290 -61
  15. package/dist/tsdoc-metadata.json +1 -1
  16. package/lib/packlets/converters/converters.d.ts +20 -13
  17. package/lib/packlets/converters/converters.js +36 -13
  18. package/lib/packlets/file-tree/directoryItem.d.ts +13 -5
  19. package/lib/packlets/file-tree/directoryItem.js +34 -3
  20. package/lib/packlets/file-tree/fileItem.d.ts +26 -13
  21. package/lib/packlets/file-tree/fileItem.js +36 -8
  22. package/lib/packlets/file-tree/fileTreeAccessors.d.ts +141 -1
  23. package/lib/packlets/file-tree/fileTreeAccessors.js +26 -0
  24. package/lib/packlets/file-tree/filterSpec.d.ts +10 -0
  25. package/lib/packlets/file-tree/filterSpec.js +77 -0
  26. package/lib/packlets/file-tree/fsTree.d.ts +29 -13
  27. package/lib/packlets/file-tree/fsTree.js +72 -11
  28. package/lib/packlets/file-tree/in-memory/inMemoryTree.d.ts +29 -13
  29. package/lib/packlets/file-tree/in-memory/inMemoryTree.js +203 -20
  30. package/lib/packlets/file-tree/in-memory/treeBuilder.d.ts +9 -0
  31. package/lib/packlets/file-tree/in-memory/treeBuilder.js +23 -0
  32. package/lib/packlets/file-tree/index.browser.d.ts +1 -0
  33. package/lib/packlets/file-tree/index.browser.js +1 -0
  34. package/lib/packlets/file-tree/index.d.ts +1 -0
  35. package/lib/packlets/file-tree/index.js +1 -0
  36. package/lib/packlets/json-file/file.d.ts +1 -1
  37. package/lib/packlets/json-file/file.js +1 -1
  38. package/lib/packlets/json-file/jsonFsHelper.d.ts +1 -1
  39. package/lib/packlets/json-file/jsonFsHelper.js +1 -1
  40. package/lib/packlets/validators/validators.d.ts +9 -9
  41. package/lib/packlets/validators/validators.js +8 -8
  42. package/package.json +18 -18
@@ -1,6 +1,42 @@
1
- import { Result } from '@fgv/ts-utils';
1
+ import { DetailedResult, Result } from '@fgv/ts-utils';
2
2
  import { Converter, Validator } from '@fgv/ts-utils';
3
3
  import { JsonValue } from '../json';
4
+ /**
5
+ * Indicates the persistence capability of a save operation.
6
+ * - `persistent`: Changes are saved to durable storage (e.g., file system).
7
+ * - `transient`: Changes are saved in memory only and will be lost on reload.
8
+ * @public
9
+ */
10
+ export type SaveCapability = 'persistent' | 'transient';
11
+ /**
12
+ * Indicates the reason a save operation cannot be performed.
13
+ * - `not-supported`: The accessors do not support mutation.
14
+ * - `read-only`: The file or file system is read-only.
15
+ * - `not-mutable`: Mutability is disabled in configuration.
16
+ * - `path-excluded`: The path is excluded by the mutability filter.
17
+ * - `permission-denied`: Insufficient permissions to write.
18
+ * @public
19
+ */
20
+ export type SaveFailureReason = 'not-supported' | 'read-only' | 'not-mutable' | 'path-excluded' | 'permission-denied';
21
+ /**
22
+ * Detail type for getIsMutable results.
23
+ * @public
24
+ */
25
+ export type SaveDetail = SaveCapability | SaveFailureReason;
26
+ /**
27
+ * Filter specification for controlling which paths are mutable.
28
+ * @public
29
+ */
30
+ export interface IFilterSpec {
31
+ /**
32
+ * Paths or patterns to include. If specified, only matching paths are mutable.
33
+ */
34
+ include?: (string | RegExp)[];
35
+ /**
36
+ * Paths or patterns to exclude. Matching paths are not mutable.
37
+ */
38
+ exclude?: (string | RegExp)[];
39
+ }
4
40
  /**
5
41
  * Type of item in a file tree.
6
42
  * @public
@@ -18,6 +54,13 @@ export type ContentTypeFactory<TCT extends string = string> = (filePath: string,
18
54
  export interface IFileTreeInitParams<TCT extends string = string> {
19
55
  prefix?: string;
20
56
  inferContentType?: ContentTypeFactory<TCT>;
57
+ /**
58
+ * Controls mutability of the file tree.
59
+ * - `undefined` or `false`: No files are mutable.
60
+ * - `true`: All files are mutable.
61
+ * - `IFilterSpec`: Only files matching the filter are mutable.
62
+ */
63
+ mutable?: boolean | IFilterSpec;
21
64
  }
22
65
  /**
23
66
  * Interface for a file in a file tree.
@@ -68,6 +111,27 @@ export interface IFileTreeFileItem<TCT extends string = string> {
68
111
  * `Failure` with an error message otherwise.
69
112
  */
70
113
  getRawContents(): Result<string>;
114
+ /**
115
+ * Indicates whether this file can be saved.
116
+ * @returns `DetailedSuccess` with {@link FileTree.SaveCapability} if the file can be saved,
117
+ * or `DetailedFailure` with {@link FileTree.SaveFailureReason} if it cannot.
118
+ * @remarks This property is optional. If not present, the file is not mutable.
119
+ */
120
+ getIsMutable(): DetailedResult<boolean, SaveDetail>;
121
+ /**
122
+ * Sets the contents of the file from a JSON value.
123
+ * @param json - The JSON value to serialize and save.
124
+ * @returns `Success` if the file was saved, or `Failure` with an error message.
125
+ * @remarks This method is optional. If not present, the file is not mutable.
126
+ */
127
+ setContents(json: JsonValue): Result<JsonValue>;
128
+ /**
129
+ * Sets the raw contents of the file.
130
+ * @param contents - The string contents to save.
131
+ * @returns `Success` if the file was saved, or `Failure` with an error message.
132
+ * @remarks This method is optional. If not present, the file is not mutable.
133
+ */
134
+ setRawContents(contents: string): Result<string>;
71
135
  }
72
136
  /**
73
137
  * Interface for a directory in a file tree.
@@ -92,6 +156,21 @@ export interface IFileTreeDirectoryItem<TCT extends string = string> {
92
156
  * or `Failure` with an error message otherwise.
93
157
  */
94
158
  getChildren(): Result<ReadonlyArray<FileTreeItem<TCT>>>;
159
+ /**
160
+ * Creates a new file as a child of this directory.
161
+ * @param name - The file name to create.
162
+ * @param contents - The string contents to write.
163
+ * @returns `Success` with the new file item, or `Failure` with an error message.
164
+ * @remarks This method is optional. Only available on mutable directory items.
165
+ */
166
+ createChildFile?(name: string, contents: string): Result<IFileTreeFileItem<TCT>>;
167
+ /**
168
+ * Creates a new subdirectory as a child of this directory.
169
+ * @param name - The directory name to create.
170
+ * @returns `Success` with the new directory item, or `Failure` with an error message.
171
+ * @remarks This method is optional. Only available on mutable directory items.
172
+ */
173
+ createChildDirectory?(name: string): Result<IFileTreeDirectoryItem<TCT>>;
95
174
  }
96
175
  /**
97
176
  * Type for an item in a file tree.
@@ -155,4 +234,65 @@ export interface IFileTreeAccessors<TCT extends string = string> {
155
234
  */
156
235
  getChildren(path: string): Result<ReadonlyArray<FileTreeItem<TCT>>>;
157
236
  }
237
+ /**
238
+ * Extended accessors interface that supports mutation operations.
239
+ * @public
240
+ */
241
+ export interface IMutableFileTreeAccessors<TCT extends string = string> extends IFileTreeAccessors<TCT> {
242
+ /**
243
+ * Checks if a file at the given path can be saved.
244
+ * @param path - The path to check.
245
+ * @returns `DetailedSuccess` with {@link FileTree.SaveCapability} if the file can be saved,
246
+ * or `DetailedFailure` with {@link FileTree.SaveFailureReason} if it cannot.
247
+ */
248
+ fileIsMutable(path: string): DetailedResult<boolean, SaveDetail>;
249
+ /**
250
+ * Saves the contents to a file at the given path.
251
+ * @param path - The path of the file to save.
252
+ * @param contents - The string contents to save.
253
+ * @returns `Success` if the file was saved, or `Failure` with an error message.
254
+ */
255
+ saveFileContents(path: string, contents: string): Result<string>;
256
+ /**
257
+ * Creates a directory at the given path, including any missing parent directories.
258
+ * @param path - The path of the directory to create.
259
+ * @returns `Success` with the absolute path if created, or `Failure` with an error message.
260
+ */
261
+ createDirectory?(path: string): Result<string>;
262
+ }
263
+ /**
264
+ * Extended accessors interface that supports persistence operations.
265
+ * @public
266
+ */
267
+ export interface IPersistentFileTreeAccessors<TCT extends string = string> extends IMutableFileTreeAccessors<TCT> {
268
+ /**
269
+ * Synchronize all dirty files to persistent storage.
270
+ * @returns Promise resolving to success or failure
271
+ */
272
+ syncToDisk(): Promise<Result<void>>;
273
+ /**
274
+ * Check if there are unsaved changes.
275
+ * @returns True if there are dirty files
276
+ */
277
+ isDirty(): boolean;
278
+ /**
279
+ * Get paths of all files with unsaved changes.
280
+ * @returns Array of dirty file paths
281
+ */
282
+ getDirtyPaths(): string[];
283
+ }
284
+ /**
285
+ * Type guard to check if accessors support mutation.
286
+ * @param accessors - The accessors to check.
287
+ * @returns `true` if the accessors implement {@link FileTree.IMutableFileTreeAccessors}.
288
+ * @public
289
+ */
290
+ export declare function isMutableAccessors<TCT extends string = string>(accessors: IFileTreeAccessors<TCT>): accessors is IMutableFileTreeAccessors<TCT>;
291
+ /**
292
+ * Type guard to check if accessors support persistence.
293
+ * @param accessors - The accessors to check.
294
+ * @returns `true` if the accessors implement {@link FileTree.IPersistentFileTreeAccessors}.
295
+ * @public
296
+ */
297
+ export declare function isPersistentAccessors<TCT extends string = string>(accessors: IFileTreeAccessors<TCT>): accessors is IPersistentFileTreeAccessors<TCT>;
158
298
  //# sourceMappingURL=fileTreeAccessors.d.ts.map
@@ -21,4 +21,30 @@
21
21
  * SOFTWARE.
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.isMutableAccessors = isMutableAccessors;
25
+ exports.isPersistentAccessors = isPersistentAccessors;
26
+ /**
27
+ * Type guard to check if accessors support mutation.
28
+ * @param accessors - The accessors to check.
29
+ * @returns `true` if the accessors implement {@link FileTree.IMutableFileTreeAccessors}.
30
+ * @public
31
+ */
32
+ function isMutableAccessors(accessors) {
33
+ const mutable = accessors;
34
+ return typeof mutable.fileIsMutable === 'function' && typeof mutable.saveFileContents === 'function';
35
+ }
36
+ /**
37
+ * Type guard to check if accessors support persistence.
38
+ * @param accessors - The accessors to check.
39
+ * @returns `true` if the accessors implement {@link FileTree.IPersistentFileTreeAccessors}.
40
+ * @public
41
+ */
42
+ function isPersistentAccessors(accessors) {
43
+ const persistent = accessors;
44
+ /* c8 ignore next 6 - no current accessor implements IPersistentFileTreeAccessors */
45
+ return (isMutableAccessors(accessors) &&
46
+ typeof persistent.syncToDisk === 'function' &&
47
+ typeof persistent.isDirty === 'function' &&
48
+ typeof persistent.getDirtyPaths === 'function');
49
+ }
24
50
  //# sourceMappingURL=fileTreeAccessors.js.map
@@ -0,0 +1,10 @@
1
+ import { IFilterSpec } from './fileTreeAccessors';
2
+ /**
3
+ * Checks if a path is allowed by a mutability configuration.
4
+ * @param path - The path to check.
5
+ * @param mutable - The mutability configuration.
6
+ * @returns `true` if the path is mutable according to the configuration.
7
+ * @public
8
+ */
9
+ export declare function isPathMutable(path: string, mutable: boolean | IFilterSpec | undefined): boolean;
10
+ //# sourceMappingURL=filterSpec.d.ts.map
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2025 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
+ exports.isPathMutable = isPathMutable;
25
+ /**
26
+ * Checks if a path matches a single pattern (string or RegExp).
27
+ * @param path - The path to check.
28
+ * @param pattern - The pattern to match against.
29
+ * @returns `true` if the path matches the pattern.
30
+ * @internal
31
+ */
32
+ function matchesPattern(path, pattern) {
33
+ if (typeof pattern === 'string') {
34
+ return path === pattern || path.startsWith(pattern + '/') || path.includes(pattern);
35
+ }
36
+ return pattern.test(path);
37
+ }
38
+ /**
39
+ * Checks if a path matches any pattern in an array.
40
+ * @param path - The path to check.
41
+ * @param patterns - The patterns to match against.
42
+ * @returns `true` if the path matches any pattern.
43
+ * @internal
44
+ */
45
+ function matchesAny(path, patterns) {
46
+ if (!patterns || patterns.length === 0) {
47
+ return false;
48
+ }
49
+ return patterns.some((pattern) => matchesPattern(path, pattern));
50
+ }
51
+ /**
52
+ * Checks if a path is allowed by a mutability configuration.
53
+ * @param path - The path to check.
54
+ * @param mutable - The mutability configuration.
55
+ * @returns `true` if the path is mutable according to the configuration.
56
+ * @public
57
+ */
58
+ function isPathMutable(path, mutable) {
59
+ if (mutable === undefined || mutable === false) {
60
+ return false;
61
+ }
62
+ if (mutable === true) {
63
+ return true;
64
+ }
65
+ const { include, exclude } = mutable;
66
+ // If exclude patterns are specified and path matches, it's not mutable
67
+ if (matchesAny(path, exclude)) {
68
+ return false;
69
+ }
70
+ // If include patterns are specified, path must match at least one
71
+ if (include && include.length > 0) {
72
+ return matchesAny(path, include);
73
+ }
74
+ // No include patterns means all paths (not excluded) are mutable
75
+ return true;
76
+ }
77
+ //# sourceMappingURL=filterSpec.js.map
@@ -1,11 +1,11 @@
1
- import { FileTreeItem, IFileTreeAccessors, IFileTreeInitParams } from './fileTreeAccessors';
2
- import { Result } from '@fgv/ts-utils';
1
+ import { FileTreeItem, IFileTreeInitParams, IMutableFileTreeAccessors, SaveDetail } from './fileTreeAccessors';
2
+ import { DetailedResult, Result } from '@fgv/ts-utils';
3
3
  /**
4
- * Implementation of {@link FileTree.IFileTreeAccessors} that uses the
5
- * file system to access files and directories.
4
+ * Implementation of {@link FileTree.IMutableFileTreeAccessors} that uses the
5
+ * file system to access and modify files and directories.
6
6
  * @public
7
7
  */
8
- export declare class FsFileTreeAccessors<TCT extends string = string> implements IFileTreeAccessors<TCT> {
8
+ export declare class FsFileTreeAccessors<TCT extends string = string> implements IMutableFileTreeAccessors<TCT> {
9
9
  /**
10
10
  * Optional path prefix to prepend to all paths.
11
11
  */
@@ -15,6 +15,10 @@ export declare class FsFileTreeAccessors<TCT extends string = string> implements
15
15
  * @public
16
16
  */
17
17
  protected readonly _inferContentType: (filePath: string) => Result<TCT | undefined>;
18
+ /**
19
+ * The mutability configuration.
20
+ */
21
+ private readonly _mutable;
18
22
  /**
19
23
  * Construct a new instance of the {@link FileTree.FsFileTreeAccessors | FsFileTreeAccessors} class.
20
24
  * @param params - Optional {@link FileTree.IFileTreeInitParams | initialization parameters}.
@@ -22,36 +26,48 @@ export declare class FsFileTreeAccessors<TCT extends string = string> implements
22
26
  */
23
27
  constructor(params?: IFileTreeInitParams<TCT>);
24
28
  /**
25
- * {@inheritdoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
29
+ * {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
26
30
  */
27
31
  resolveAbsolutePath(...paths: string[]): string;
28
32
  /**
29
- * {@inheritdoc FileTree.IFileTreeAccessors.getExtension}
33
+ * {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
30
34
  */
31
35
  getExtension(itemPath: string): string;
32
36
  /**
33
- * {@inheritdoc FileTree.IFileTreeAccessors.getBaseName}
37
+ * {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
34
38
  */
35
39
  getBaseName(itemPath: string, suffix?: string): string;
36
40
  /**
37
- * {@inheritdoc FileTree.IFileTreeAccessors.joinPaths}
41
+ * {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
38
42
  */
39
43
  joinPaths(...paths: string[]): string;
40
44
  /**
41
- * {@inheritdoc FileTree.IFileTreeAccessors.getItem}
45
+ * {@inheritDoc FileTree.IFileTreeAccessors.getItem}
42
46
  */
43
47
  getItem(itemPath: string): Result<FileTreeItem<TCT>>;
44
48
  /**
45
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContents}
49
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
46
50
  */
47
51
  getFileContents(filePath: string): Result<string>;
48
52
  /**
49
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContentType}
53
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
50
54
  */
51
55
  getFileContentType(filePath: string, provided?: string): Result<TCT | undefined>;
52
56
  /**
53
- * {@inheritdoc FileTree.IFileTreeAccessors.getChildren}
57
+ * {@inheritDoc FileTree.IFileTreeAccessors.getChildren}
54
58
  */
55
59
  getChildren(dirPath: string): Result<ReadonlyArray<FileTreeItem<TCT>>>;
60
+ /**
61
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.fileIsMutable}
62
+ */
63
+ fileIsMutable(path: string): DetailedResult<boolean, SaveDetail>;
64
+ /**
65
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
66
+ */
67
+ saveFileContents(path: string, contents: string): Result<string>;
68
+ /**
69
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
70
+ */
71
+ createDirectory(dirPath: string): Result<string>;
56
72
  }
57
73
  //# sourceMappingURL=fsTree.d.ts.map
@@ -30,9 +30,10 @@ const fs_1 = __importDefault(require("fs"));
30
30
  const ts_utils_1 = require("@fgv/ts-utils");
31
31
  const directoryItem_1 = require("./directoryItem");
32
32
  const fileItem_1 = require("./fileItem");
33
+ const filterSpec_1 = require("./filterSpec");
33
34
  /**
34
- * Implementation of {@link FileTree.IFileTreeAccessors} that uses the
35
- * file system to access files and directories.
35
+ * Implementation of {@link FileTree.IMutableFileTreeAccessors} that uses the
36
+ * file system to access and modify files and directories.
36
37
  * @public
37
38
  */
38
39
  class FsFileTreeAccessors {
@@ -42,12 +43,14 @@ class FsFileTreeAccessors {
42
43
  * @public
43
44
  */
44
45
  constructor(params) {
45
- var _a;
46
+ var _a, _b;
46
47
  this.prefix = params === null || params === void 0 ? void 0 : params.prefix;
47
48
  this._inferContentType = (_a = params === null || params === void 0 ? void 0 : params.inferContentType) !== null && _a !== void 0 ? _a : fileItem_1.FileItem.defaultInferContentType;
49
+ /* c8 ignore next 1 - defensive default when params is undefined */
50
+ this._mutable = (_b = params === null || params === void 0 ? void 0 : params.mutable) !== null && _b !== void 0 ? _b : false;
48
51
  }
49
52
  /**
50
- * {@inheritdoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
53
+ * {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
51
54
  */
52
55
  resolveAbsolutePath(...paths) {
53
56
  if (this.prefix && !path_1.default.isAbsolute(paths[0])) {
@@ -56,25 +59,25 @@ class FsFileTreeAccessors {
56
59
  return path_1.default.resolve(...paths);
57
60
  }
58
61
  /**
59
- * {@inheritdoc FileTree.IFileTreeAccessors.getExtension}
62
+ * {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
60
63
  */
61
64
  getExtension(itemPath) {
62
65
  return path_1.default.extname(itemPath);
63
66
  }
64
67
  /**
65
- * {@inheritdoc FileTree.IFileTreeAccessors.getBaseName}
68
+ * {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
66
69
  */
67
70
  getBaseName(itemPath, suffix) {
68
71
  return path_1.default.basename(itemPath, suffix);
69
72
  }
70
73
  /**
71
- * {@inheritdoc FileTree.IFileTreeAccessors.joinPaths}
74
+ * {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
72
75
  */
73
76
  joinPaths(...paths) {
74
77
  return path_1.default.join(...paths);
75
78
  }
76
79
  /**
77
- * {@inheritdoc FileTree.IFileTreeAccessors.getItem}
80
+ * {@inheritDoc FileTree.IFileTreeAccessors.getItem}
78
81
  */
79
82
  getItem(itemPath) {
80
83
  return (0, ts_utils_1.captureResult)(() => {
@@ -90,13 +93,13 @@ class FsFileTreeAccessors {
90
93
  });
91
94
  }
92
95
  /**
93
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContents}
96
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
94
97
  */
95
98
  getFileContents(filePath) {
96
99
  return (0, ts_utils_1.captureResult)(() => fs_1.default.readFileSync(this.resolveAbsolutePath(filePath), 'utf8'));
97
100
  }
98
101
  /**
99
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContentType}
102
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
100
103
  */
101
104
  getFileContentType(filePath, provided) {
102
105
  if (provided !== undefined) {
@@ -106,7 +109,7 @@ class FsFileTreeAccessors {
106
109
  return this._inferContentType(filePath);
107
110
  }
108
111
  /**
109
- * {@inheritdoc FileTree.IFileTreeAccessors.getChildren}
112
+ * {@inheritDoc FileTree.IFileTreeAccessors.getChildren}
110
113
  */
111
114
  getChildren(dirPath) {
112
115
  return (0, ts_utils_1.captureResult)(() => {
@@ -124,6 +127,64 @@ class FsFileTreeAccessors {
124
127
  return children;
125
128
  });
126
129
  }
130
+ /**
131
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.fileIsMutable}
132
+ */
133
+ fileIsMutable(path) {
134
+ const absolutePath = this.resolveAbsolutePath(path);
135
+ // Check if mutability is disabled
136
+ if (this._mutable === false) {
137
+ return (0, ts_utils_1.failWithDetail)(`${absolutePath}: mutability is disabled`, 'not-mutable');
138
+ }
139
+ // Check if path is excluded by filter
140
+ if (!(0, filterSpec_1.isPathMutable)(absolutePath, this._mutable)) {
141
+ return (0, ts_utils_1.failWithDetail)(`${absolutePath}: path is excluded by filter`, 'path-excluded');
142
+ }
143
+ // Check file system permissions
144
+ try {
145
+ // Check if file exists
146
+ if (fs_1.default.existsSync(absolutePath)) {
147
+ fs_1.default.accessSync(absolutePath, fs_1.default.constants.W_OK);
148
+ }
149
+ else {
150
+ // Check if parent directory is writable
151
+ const parentDir = absolutePath.substring(0, absolutePath.lastIndexOf('/'));
152
+ if (parentDir && fs_1.default.existsSync(parentDir)) {
153
+ fs_1.default.accessSync(parentDir, fs_1.default.constants.W_OK);
154
+ }
155
+ }
156
+ return (0, ts_utils_1.succeedWithDetail)(true, 'persistent');
157
+ }
158
+ catch (_a) {
159
+ return (0, ts_utils_1.failWithDetail)(`${absolutePath}: permission denied`, 'permission-denied');
160
+ }
161
+ }
162
+ /**
163
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
164
+ */
165
+ saveFileContents(path, contents) {
166
+ return this.fileIsMutable(path).asResult.onSuccess(() => {
167
+ const absolutePath = this.resolveAbsolutePath(path);
168
+ return (0, ts_utils_1.captureResult)(() => {
169
+ fs_1.default.writeFileSync(absolutePath, contents, 'utf8');
170
+ return contents;
171
+ });
172
+ });
173
+ }
174
+ /**
175
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
176
+ */
177
+ createDirectory(dirPath) {
178
+ const absolutePath = this.resolveAbsolutePath(dirPath);
179
+ // Check if mutability is disabled
180
+ if (this._mutable === false) {
181
+ return (0, ts_utils_1.fail)(`${absolutePath}: mutability is disabled`);
182
+ }
183
+ return (0, ts_utils_1.captureResult)(() => {
184
+ fs_1.default.mkdirSync(absolutePath, { recursive: true });
185
+ return absolutePath;
186
+ });
187
+ }
127
188
  }
128
189
  exports.FsFileTreeAccessors = FsFileTreeAccessors;
129
190
  //# sourceMappingURL=fsTree.js.map
@@ -1,5 +1,5 @@
1
- import { Result } from '@fgv/ts-utils';
2
- import { FileTreeItem, IFileTreeAccessors, IFileTreeInitParams } from '../fileTreeAccessors';
1
+ import { DetailedResult, Result } from '@fgv/ts-utils';
2
+ import { FileTreeItem, IFileTreeInitParams, IMutableFileTreeAccessors, SaveDetail } from '../fileTreeAccessors';
3
3
  /**
4
4
  * Represents a single file in an in-memory {@link FileTree | file tree}.
5
5
  * @public
@@ -19,13 +19,16 @@ export interface IInMemoryFile<TCT extends string = string> {
19
19
  readonly contentType?: TCT;
20
20
  }
21
21
  /**
22
- * Implementation of {@link FileTree.IFileTreeAccessors} that uses an in-memory
23
- * tree to access files and directories.
22
+ * Implementation of {@link FileTree.IMutableFileTreeAccessors} that uses an in-memory
23
+ * tree to access and modify files and directories.
24
24
  * @public
25
25
  */
26
- export declare class InMemoryTreeAccessors<TCT extends string = string> implements IFileTreeAccessors<TCT> {
26
+ export declare class InMemoryTreeAccessors<TCT extends string = string> implements IMutableFileTreeAccessors<TCT> {
27
27
  private readonly _tree;
28
28
  private readonly _inferContentType;
29
+ private readonly _mutable;
30
+ private readonly _mutableByPath;
31
+ private readonly _mutableRoot;
29
32
  /**
30
33
  * Protected constructor for derived classes.
31
34
  * @param files - An array of {@link FileTree.IInMemoryFile | in-memory files} to include in the tree.
@@ -48,36 +51,49 @@ export declare class InMemoryTreeAccessors<TCT extends string = string> implemen
48
51
  */
49
52
  static create<TCT extends string = string>(files: IInMemoryFile<TCT>[], params?: IFileTreeInitParams<TCT>): Result<InMemoryTreeAccessors<TCT>>;
50
53
  /**
51
- * {@inheritdoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
54
+ * {@inheritDoc FileTree.IFileTreeAccessors.resolveAbsolutePath}
52
55
  */
53
56
  resolveAbsolutePath(...paths: string[]): string;
54
57
  /**
55
- * {@inheritdoc FileTree.IFileTreeAccessors.getExtension}
58
+ * {@inheritDoc FileTree.IFileTreeAccessors.getExtension}
56
59
  */
57
60
  getExtension(path: string): string;
58
61
  /**
59
- * {@inheritdoc FileTree.IFileTreeAccessors.getBaseName}
62
+ * {@inheritDoc FileTree.IFileTreeAccessors.getBaseName}
60
63
  */
61
64
  getBaseName(path: string, suffix?: string): string;
62
65
  /**
63
- * {@inheritdoc FileTree.IFileTreeAccessors.joinPaths}
66
+ * {@inheritDoc FileTree.IFileTreeAccessors.joinPaths}
64
67
  */
65
68
  joinPaths(...paths: string[]): string;
66
69
  /**
67
- * {@inheritdoc FileTree.IFileTreeAccessors.getItem}
70
+ * {@inheritDoc FileTree.IFileTreeAccessors.getItem}
68
71
  */
69
72
  getItem(itemPath: string): Result<FileTreeItem<TCT>>;
70
73
  /**
71
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContents}
74
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContents}
72
75
  */
73
76
  getFileContents(path: string): Result<string>;
74
77
  /**
75
- * {@inheritdoc FileTree.IFileTreeAccessors.getFileContentType}
78
+ * {@inheritDoc FileTree.IFileTreeAccessors.getFileContentType}
76
79
  */
77
80
  getFileContentType(path: string, provided?: string): Result<TCT | undefined>;
78
81
  /**
79
- * {@inheritdoc FileTree.IFileTreeAccessors.getChildren}
82
+ * {@inheritDoc FileTree.IFileTreeAccessors.getChildren}
80
83
  */
81
84
  getChildren(path: string): Result<ReadonlyArray<FileTreeItem<TCT>>>;
85
+ private _addMutableFile;
86
+ /**
87
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.createDirectory}
88
+ */
89
+ createDirectory(dirPath: string): Result<string>;
90
+ /**
91
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.fileIsMutable}
92
+ */
93
+ fileIsMutable(path: string): DetailedResult<boolean, SaveDetail>;
94
+ /**
95
+ * {@inheritDoc FileTree.IMutableFileTreeAccessors.saveFileContents}
96
+ */
97
+ saveFileContents(path: string, contents: string): Result<string>;
82
98
  }
83
99
  //# sourceMappingURL=inMemoryTree.d.ts.map