@xfcfam/xf-fs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/src/api/A.d.ts +17 -0
- package/dist/src/api/A.d.ts.map +1 -0
- package/dist/src/api/A.js +17 -0
- package/dist/src/api/A.js.map +1 -0
- package/dist/src/business/B.d.ts +17 -0
- package/dist/src/business/B.d.ts.map +1 -0
- package/dist/src/business/B.js +17 -0
- package/dist/src/business/B.js.map +1 -0
- package/dist/src/repository/R.d.ts +17 -0
- package/dist/src/repository/R.d.ts.map +1 -0
- package/dist/src/repository/R.js +17 -0
- package/dist/src/repository/R.js.map +1 -0
- package/dist/src/repository/base/AuditedFileRepository.d.ts +86 -0
- package/dist/src/repository/base/AuditedFileRepository.d.ts.map +1 -0
- package/dist/src/repository/base/AuditedFileRepository.js +202 -0
- package/dist/src/repository/base/AuditedFileRepository.js.map +1 -0
- package/dist/src/repository/base/CachedFileRepository.d.ts +53 -0
- package/dist/src/repository/base/CachedFileRepository.d.ts.map +1 -0
- package/dist/src/repository/base/CachedFileRepository.js +107 -0
- package/dist/src/repository/base/CachedFileRepository.js.map +1 -0
- package/dist/src/repository/base/FileRepository.d.ts +170 -0
- package/dist/src/repository/base/FileRepository.d.ts.map +1 -0
- package/dist/src/repository/base/FileRepository.js +382 -0
- package/dist/src/repository/base/FileRepository.js.map +1 -0
- package/dist/src/repository/general/AuditedFileRepository.d.ts +86 -0
- package/dist/src/repository/general/AuditedFileRepository.d.ts.map +1 -0
- package/dist/src/repository/general/AuditedFileRepository.js +238 -0
- package/dist/src/repository/general/AuditedFileRepository.js.map +1 -0
- package/dist/src/repository/general/CachedFileRepository.d.ts +53 -0
- package/dist/src/repository/general/CachedFileRepository.d.ts.map +1 -0
- package/dist/src/repository/general/CachedFileRepository.js +107 -0
- package/dist/src/repository/general/CachedFileRepository.js.map +1 -0
- package/dist/src/repository/general/FileRepository.d.ts +180 -0
- package/dist/src/repository/general/FileRepository.d.ts.map +1 -0
- package/dist/src/repository/general/FileRepository.js +401 -0
- package/dist/src/repository/general/FileRepository.js.map +1 -0
- package/dist/src/repository/structs/DirectoryNotEmptyException.d.ts +13 -0
- package/dist/src/repository/structs/DirectoryNotEmptyException.d.ts.map +1 -0
- package/dist/src/repository/structs/DirectoryNotEmptyException.js +17 -0
- package/dist/src/repository/structs/DirectoryNotEmptyException.js.map +1 -0
- package/dist/src/repository/structs/FileAccessDeniedException.d.ts +14 -0
- package/dist/src/repository/structs/FileAccessDeniedException.d.ts.map +1 -0
- package/dist/src/repository/structs/FileAccessDeniedException.js +18 -0
- package/dist/src/repository/structs/FileAccessDeniedException.js.map +1 -0
- package/dist/src/repository/structs/FileEntry.d.ts +21 -0
- package/dist/src/repository/structs/FileEntry.d.ts.map +1 -0
- package/dist/src/repository/structs/FileEntry.js +2 -0
- package/dist/src/repository/structs/FileEntry.js.map +1 -0
- package/dist/src/repository/structs/FileNotFoundException.d.ts +13 -0
- package/dist/src/repository/structs/FileNotFoundException.d.ts.map +1 -0
- package/dist/src/repository/structs/FileNotFoundException.js +17 -0
- package/dist/src/repository/structs/FileNotFoundException.js.map +1 -0
- package/dist/src/repository/structs/FileStat.d.ts +24 -0
- package/dist/src/repository/structs/FileStat.d.ts.map +1 -0
- package/dist/src/repository/structs/FileStat.js +2 -0
- package/dist/src/repository/structs/FileStat.js.map +1 -0
- package/dist/src/repository/structs/TempFile.d.ts +19 -0
- package/dist/src/repository/structs/TempFile.d.ts.map +1 -0
- package/dist/src/repository/structs/TempFile.js +2 -0
- package/dist/src/repository/structs/TempFile.js.map +1 -0
- package/dist/src/repository/structs/WatchEvent.d.ts +18 -0
- package/dist/src/repository/structs/WatchEvent.d.ts.map +1 -0
- package/dist/src/repository/structs/WatchEvent.js +2 -0
- package/dist/src/repository/structs/WatchEvent.js.map +1 -0
- package/dist/src/repository/structs/Watcher.d.ts +18 -0
- package/dist/src/repository/structs/Watcher.d.ts.map +1 -0
- package/dist/src/repository/structs/Watcher.js +2 -0
- package/dist/src/repository/structs/Watcher.js.map +1 -0
- package/dist/src/repository/transfers/DirectoryNotEmptyException.d.ts +13 -0
- package/dist/src/repository/transfers/DirectoryNotEmptyException.d.ts.map +1 -0
- package/dist/src/repository/transfers/DirectoryNotEmptyException.js +17 -0
- package/dist/src/repository/transfers/DirectoryNotEmptyException.js.map +1 -0
- package/dist/src/repository/transfers/FileAccessDeniedException.d.ts +14 -0
- package/dist/src/repository/transfers/FileAccessDeniedException.d.ts.map +1 -0
- package/dist/src/repository/transfers/FileAccessDeniedException.js +18 -0
- package/dist/src/repository/transfers/FileAccessDeniedException.js.map +1 -0
- package/dist/src/repository/transfers/FileEntry.d.ts +21 -0
- package/dist/src/repository/transfers/FileEntry.d.ts.map +1 -0
- package/dist/src/repository/transfers/FileEntry.js +2 -0
- package/dist/src/repository/transfers/FileEntry.js.map +1 -0
- package/dist/src/repository/transfers/FileNotFoundException.d.ts +13 -0
- package/dist/src/repository/transfers/FileNotFoundException.d.ts.map +1 -0
- package/dist/src/repository/transfers/FileNotFoundException.js +17 -0
- package/dist/src/repository/transfers/FileNotFoundException.js.map +1 -0
- package/dist/src/repository/transfers/FileStat.d.ts +24 -0
- package/dist/src/repository/transfers/FileStat.d.ts.map +1 -0
- package/dist/src/repository/transfers/FileStat.js +2 -0
- package/dist/src/repository/transfers/FileStat.js.map +1 -0
- package/dist/src/repository/transfers/TempFile.d.ts +19 -0
- package/dist/src/repository/transfers/TempFile.d.ts.map +1 -0
- package/dist/src/repository/transfers/TempFile.js +2 -0
- package/dist/src/repository/transfers/TempFile.js.map +1 -0
- package/dist/src/repository/transfers/WatchEvent.d.ts +25 -0
- package/dist/src/repository/transfers/WatchEvent.d.ts.map +1 -0
- package/dist/src/repository/transfers/WatchEvent.js +2 -0
- package/dist/src/repository/transfers/WatchEvent.js.map +1 -0
- package/dist/src/repository/transfers/Watcher.d.ts +18 -0
- package/dist/src/repository/transfers/Watcher.d.ts.map +1 -0
- package/dist/src/repository/transfers/Watcher.js +2 -0
- package/dist/src/repository/transfers/Watcher.js.map +1 -0
- package/dist/src/repository/utils/EncodingUtils.d.ts +29 -0
- package/dist/src/repository/utils/EncodingUtils.d.ts.map +1 -0
- package/dist/src/repository/utils/EncodingUtils.js +36 -0
- package/dist/src/repository/utils/EncodingUtils.js.map +1 -0
- package/dist/src/repository/utils/PathUtils.d.ts +23 -0
- package/dist/src/repository/utils/PathUtils.d.ts.map +1 -0
- package/dist/src/repository/utils/PathUtils.js +53 -0
- package/dist/src/repository/utils/PathUtils.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { FileRepository } from './FileRepository.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generalization for Access Layer components that need to observe
|
|
4
|
+
* every filesystem operation — for audit logs, metrics, security
|
|
5
|
+
* tracing, or invalidation of higher-level caches.
|
|
6
|
+
*
|
|
7
|
+
* Same protocol as {@link FileRepository}; this subclass adds a
|
|
8
|
+
* cross-cutting **observability policy**. Every public method is
|
|
9
|
+
* intercepted: on success the matching `onX` hook is invoked with
|
|
10
|
+
* the operation's path and result summary; on failure `onError` is
|
|
11
|
+
* invoked with the operation name, path, and the (possibly
|
|
12
|
+
* translated) error before it is re-thrown.
|
|
13
|
+
*
|
|
14
|
+
* Hooks default to no-ops. Subclasses override only the ones they
|
|
15
|
+
* care about. They may be async — the interceptor `await`s them, so
|
|
16
|
+
* a hook can hit a remote logger or block briefly without losing
|
|
17
|
+
* events.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { AuditedFileRepository } from '@xfcfam/xf-fs'
|
|
22
|
+
*
|
|
23
|
+
* export class AuditedConfigRepository extends AuditedFileRepository {
|
|
24
|
+
* constructor() { super({ rootPath: '/etc/app' }) }
|
|
25
|
+
*
|
|
26
|
+
* override async onWrite(path: string, sizeBytes: number) {
|
|
27
|
+
* await this.appendAuditLog(`WRITE ${path} ${sizeBytes}B`)
|
|
28
|
+
* }
|
|
29
|
+
* override async onError(op: FileOperation, path: string, err: unknown) {
|
|
30
|
+
* await this.appendAuditLog(`${op.toUpperCase()} FAIL ${path}: ${String(err)}`)
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* private async appendAuditLog(line: string) { ... }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class AuditedFileRepository extends FileRepository {
|
|
38
|
+
// ─── Hooks (overridable; defaults are no-ops) ─────────────
|
|
39
|
+
async onRead(_path, _sizeBytes) { }
|
|
40
|
+
async onReadBytes(_path, _sizeBytes) { }
|
|
41
|
+
async onWrite(_path, _sizeBytes) { }
|
|
42
|
+
async onAppend(_path, _sizeBytes) { }
|
|
43
|
+
async onDelete(_path) { }
|
|
44
|
+
async onExists(_path, _exists) { }
|
|
45
|
+
async onStat(_path, _stat) { }
|
|
46
|
+
async onList(_path, _entries) { }
|
|
47
|
+
async onWalk(_path, _entries) { }
|
|
48
|
+
async onMkdir(_path, _recursive) { }
|
|
49
|
+
async onRmdir(_path, _recursive) { }
|
|
50
|
+
onReadStream(_path) { }
|
|
51
|
+
onWriteStream(_path) { }
|
|
52
|
+
async onWatch(_path, _watcher) { }
|
|
53
|
+
async onTempFile(_tempFile) { }
|
|
54
|
+
async onError(_operation, _path, _error) { }
|
|
55
|
+
// ─── Intercepted operations ───────────────────────────────
|
|
56
|
+
async read(path) {
|
|
57
|
+
try {
|
|
58
|
+
const content = await super.read(path);
|
|
59
|
+
await this.onRead(this.resolve(path), Buffer.byteLength(content, 'utf-8'));
|
|
60
|
+
return content;
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
try {
|
|
64
|
+
await this.onError('read', this.resolve(path), err);
|
|
65
|
+
}
|
|
66
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async readBytes(path) {
|
|
71
|
+
try {
|
|
72
|
+
const bytes = await super.readBytes(path);
|
|
73
|
+
await this.onReadBytes(this.resolve(path), bytes.byteLength);
|
|
74
|
+
return bytes;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
try {
|
|
78
|
+
await this.onError('readBytes', this.resolve(path), err);
|
|
79
|
+
}
|
|
80
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async write(path, content) {
|
|
85
|
+
try {
|
|
86
|
+
await super.write(path, content);
|
|
87
|
+
await this.onWrite(this.resolve(path), AuditedFileRepository.sizeOf(content));
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
try {
|
|
91
|
+
await this.onError('write', this.resolve(path), err);
|
|
92
|
+
}
|
|
93
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async append(path, content) {
|
|
98
|
+
try {
|
|
99
|
+
await super.append(path, content);
|
|
100
|
+
await this.onAppend(this.resolve(path), AuditedFileRepository.sizeOf(content));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
try {
|
|
104
|
+
await this.onError('append', this.resolve(path), err);
|
|
105
|
+
}
|
|
106
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async delete(path) {
|
|
111
|
+
try {
|
|
112
|
+
await super.delete(path);
|
|
113
|
+
await this.onDelete(this.resolve(path));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
try {
|
|
117
|
+
await this.onError('delete', this.resolve(path), err);
|
|
118
|
+
}
|
|
119
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async exists(path) {
|
|
124
|
+
const result = await super.exists(path);
|
|
125
|
+
await this.onExists(this.resolve(path), result);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
async stat(path) {
|
|
129
|
+
try {
|
|
130
|
+
const s = await super.stat(path);
|
|
131
|
+
await this.onStat(s.path, s);
|
|
132
|
+
return s;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
try {
|
|
136
|
+
await this.onError('stat', this.resolve(path), err);
|
|
137
|
+
}
|
|
138
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async list(path) {
|
|
143
|
+
try {
|
|
144
|
+
const entries = await super.list(path);
|
|
145
|
+
await this.onList(this.resolve(path), entries);
|
|
146
|
+
return entries;
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
try {
|
|
150
|
+
await this.onError('list', this.resolve(path), err);
|
|
151
|
+
}
|
|
152
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async walk(path) {
|
|
157
|
+
try {
|
|
158
|
+
const entries = await super.walk(path);
|
|
159
|
+
await this.onWalk(this.resolve(path), entries);
|
|
160
|
+
return entries;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
try {
|
|
164
|
+
await this.onError('walk', this.resolve(path), err);
|
|
165
|
+
}
|
|
166
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async mkdir(path, options = {}) {
|
|
171
|
+
try {
|
|
172
|
+
await super.mkdir(path, options);
|
|
173
|
+
await this.onMkdir(this.resolve(path), options.recursive ?? false);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
try {
|
|
177
|
+
await this.onError('mkdir', this.resolve(path), err);
|
|
178
|
+
}
|
|
179
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async rmdir(path, options = {}) {
|
|
184
|
+
try {
|
|
185
|
+
await super.rmdir(path, options);
|
|
186
|
+
await this.onRmdir(this.resolve(path), options.recursive ?? false);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
try {
|
|
190
|
+
await this.onError('rmdir', this.resolve(path), err);
|
|
191
|
+
}
|
|
192
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
readStream(path) {
|
|
197
|
+
const stream = super.readStream(path);
|
|
198
|
+
this.onReadStream(this.resolve(path));
|
|
199
|
+
return stream;
|
|
200
|
+
}
|
|
201
|
+
writeStream(path) {
|
|
202
|
+
const stream = super.writeStream(path);
|
|
203
|
+
this.onWriteStream(this.resolve(path));
|
|
204
|
+
return stream;
|
|
205
|
+
}
|
|
206
|
+
async watch(path, callback) {
|
|
207
|
+
try {
|
|
208
|
+
const watcher = await super.watch(path, callback);
|
|
209
|
+
await this.onWatch(this.resolve(path), watcher);
|
|
210
|
+
return watcher;
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
try {
|
|
214
|
+
await this.onError('watch', this.resolve(path), err);
|
|
215
|
+
}
|
|
216
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async tempFile(prefix) {
|
|
221
|
+
try {
|
|
222
|
+
const handle = await super.tempFile(prefix);
|
|
223
|
+
await this.onTempFile(handle);
|
|
224
|
+
return handle;
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
try {
|
|
228
|
+
await this.onError('tempFile', '', err);
|
|
229
|
+
}
|
|
230
|
+
catch { /* a throwing hook must not mask the original error */ }
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
static sizeOf(content) {
|
|
235
|
+
return typeof content === 'string' ? Buffer.byteLength(content, 'utf-8') : content.byteLength;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=AuditedFileRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditedFileRepository.js","sourceRoot":"","sources":["../../../../src/repository/general/AuditedFileRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FileRepository, type FileOptions } from './FileRepository.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generalization for Access Layer components that need an in-memory
|
|
4
|
+
* cache over the local filesystem.
|
|
5
|
+
*
|
|
6
|
+
* Same protocol as {@link FileRepository} — every public method
|
|
7
|
+
* preserves the parent's signature and semantics. This subclass adds
|
|
8
|
+
* a cross-cutting **caching policy**: results of `read()` and
|
|
9
|
+
* `readBytes()` are memoised in a per-instance map and served from
|
|
10
|
+
* memory until the path is mutated via this Repository.
|
|
11
|
+
*
|
|
12
|
+
* **Coherence model: write-through.** Mutations performed through
|
|
13
|
+
* this Repository (`write`, `append`, `delete`, `rmdir`) invalidate
|
|
14
|
+
* the cache entry for the affected path. Mutations performed by
|
|
15
|
+
* other processes (or by other `FileRepository` instances) are NOT
|
|
16
|
+
* detected — callers in those scenarios should invoke
|
|
17
|
+
* {@link clearCache} (or per-path {@link invalidateCache}) to force a
|
|
18
|
+
* fresh read.
|
|
19
|
+
*
|
|
20
|
+
* **What's not cached:** `stat`, `list`, `walk`, `exists`, streams,
|
|
21
|
+
* and `watch` always hit the filesystem. Caching directory listings
|
|
22
|
+
* across mutations is a separate design decision and out of scope
|
|
23
|
+
* for the v0 implementation.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { CachedFileRepository } from '@xfcfam/xf-fs'
|
|
28
|
+
*
|
|
29
|
+
* export class TemplatesFileRepository extends CachedFileRepository {
|
|
30
|
+
* constructor() { super({ rootPath: '/etc/app/templates' }) }
|
|
31
|
+
* loadTemplate(name: string) { return this.read(`${name}.tpl`) }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare abstract class CachedFileRepository extends FileRepository {
|
|
36
|
+
private readonly textCache;
|
|
37
|
+
private readonly bytesCache;
|
|
38
|
+
constructor(options?: FileOptions);
|
|
39
|
+
terminate(): Promise<void>;
|
|
40
|
+
read(path: string): Promise<string>;
|
|
41
|
+
readBytes(path: string): Promise<Uint8Array>;
|
|
42
|
+
write(path: string, content: string | Uint8Array): Promise<void>;
|
|
43
|
+
append(path: string, content: string | Uint8Array): Promise<void>;
|
|
44
|
+
delete(path: string): Promise<void>;
|
|
45
|
+
rmdir(path: string, options?: {
|
|
46
|
+
recursive?: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
/** Drop the cache entry for a single path. */
|
|
49
|
+
protected invalidateCache(path: string): void;
|
|
50
|
+
/** Drop every cache entry. Use after external mutations. */
|
|
51
|
+
protected clearCache(): void;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=CachedFileRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CachedFileRepository.d.ts","sourceRoot":"","sources":["../../../../src/repository/general/CachedFileRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,8BAAsB,oBAAqB,SAAQ,cAAc;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAExC,OAAO,GAAE,WAAgB;IAMtB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAM1B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASnC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAS5C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxF,8CAA8C;IAC9C,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM7C,4DAA4D;IAC5D,SAAS,CAAC,UAAU,IAAI,IAAI;CAI7B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { FileRepository } from './FileRepository.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generalization for Access Layer components that need an in-memory
|
|
4
|
+
* cache over the local filesystem.
|
|
5
|
+
*
|
|
6
|
+
* Same protocol as {@link FileRepository} — every public method
|
|
7
|
+
* preserves the parent's signature and semantics. This subclass adds
|
|
8
|
+
* a cross-cutting **caching policy**: results of `read()` and
|
|
9
|
+
* `readBytes()` are memoised in a per-instance map and served from
|
|
10
|
+
* memory until the path is mutated via this Repository.
|
|
11
|
+
*
|
|
12
|
+
* **Coherence model: write-through.** Mutations performed through
|
|
13
|
+
* this Repository (`write`, `append`, `delete`, `rmdir`) invalidate
|
|
14
|
+
* the cache entry for the affected path. Mutations performed by
|
|
15
|
+
* other processes (or by other `FileRepository` instances) are NOT
|
|
16
|
+
* detected — callers in those scenarios should invoke
|
|
17
|
+
* {@link clearCache} (or per-path {@link invalidateCache}) to force a
|
|
18
|
+
* fresh read.
|
|
19
|
+
*
|
|
20
|
+
* **What's not cached:** `stat`, `list`, `walk`, `exists`, streams,
|
|
21
|
+
* and `watch` always hit the filesystem. Caching directory listings
|
|
22
|
+
* across mutations is a separate design decision and out of scope
|
|
23
|
+
* for the v0 implementation.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { CachedFileRepository } from '@xfcfam/xf-fs'
|
|
28
|
+
*
|
|
29
|
+
* export class TemplatesFileRepository extends CachedFileRepository {
|
|
30
|
+
* constructor() { super({ rootPath: '/etc/app/templates' }) }
|
|
31
|
+
* loadTemplate(name: string) { return this.read(`${name}.tpl`) }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class CachedFileRepository extends FileRepository {
|
|
36
|
+
textCache;
|
|
37
|
+
bytesCache;
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
super(options);
|
|
40
|
+
this.textCache = new Map();
|
|
41
|
+
this.bytesCache = new Map();
|
|
42
|
+
}
|
|
43
|
+
async terminate() {
|
|
44
|
+
this.textCache.clear();
|
|
45
|
+
this.bytesCache.clear();
|
|
46
|
+
await super.terminate();
|
|
47
|
+
}
|
|
48
|
+
async read(path) {
|
|
49
|
+
const abs = this.resolve(path);
|
|
50
|
+
const cached = this.textCache.get(abs);
|
|
51
|
+
if (cached !== undefined)
|
|
52
|
+
return cached;
|
|
53
|
+
const content = await super.read(path);
|
|
54
|
+
this.textCache.set(abs, content);
|
|
55
|
+
return content;
|
|
56
|
+
}
|
|
57
|
+
async readBytes(path) {
|
|
58
|
+
const abs = this.resolve(path);
|
|
59
|
+
const cached = this.bytesCache.get(abs);
|
|
60
|
+
if (cached !== undefined)
|
|
61
|
+
return cached;
|
|
62
|
+
const content = await super.readBytes(path);
|
|
63
|
+
this.bytesCache.set(abs, content);
|
|
64
|
+
return content;
|
|
65
|
+
}
|
|
66
|
+
async write(path, content) {
|
|
67
|
+
await super.write(path, content);
|
|
68
|
+
this.invalidateCache(path);
|
|
69
|
+
}
|
|
70
|
+
async append(path, content) {
|
|
71
|
+
await super.append(path, content);
|
|
72
|
+
this.invalidateCache(path);
|
|
73
|
+
}
|
|
74
|
+
async delete(path) {
|
|
75
|
+
await super.delete(path);
|
|
76
|
+
this.invalidateCache(path);
|
|
77
|
+
}
|
|
78
|
+
async rmdir(path, options = {}) {
|
|
79
|
+
await super.rmdir(path, options);
|
|
80
|
+
if (options.recursive === true) {
|
|
81
|
+
// Recursive removal: drop every cache entry under this directory.
|
|
82
|
+
const abs = this.resolve(path);
|
|
83
|
+
const prefix = abs.endsWith('/') ? abs : `${abs}/`;
|
|
84
|
+
for (const k of this.textCache.keys())
|
|
85
|
+
if (k === abs || k.startsWith(prefix))
|
|
86
|
+
this.textCache.delete(k);
|
|
87
|
+
for (const k of this.bytesCache.keys())
|
|
88
|
+
if (k === abs || k.startsWith(prefix))
|
|
89
|
+
this.bytesCache.delete(k);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.invalidateCache(path);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Drop the cache entry for a single path. */
|
|
96
|
+
invalidateCache(path) {
|
|
97
|
+
const abs = this.resolve(path);
|
|
98
|
+
this.textCache.delete(abs);
|
|
99
|
+
this.bytesCache.delete(abs);
|
|
100
|
+
}
|
|
101
|
+
/** Drop every cache entry. Use after external mutations. */
|
|
102
|
+
clearCache() {
|
|
103
|
+
this.textCache.clear();
|
|
104
|
+
this.bytesCache.clear();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=CachedFileRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CachedFileRepository.js","sourceRoot":"","sources":["../../../../src/repository/general/CachedFileRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Repository } from '@xfcfam/xf';
|
|
2
|
+
import type { FileEntry } from '../transfers/FileEntry.js';
|
|
3
|
+
import type { FileStat } from '../transfers/FileStat.js';
|
|
4
|
+
import type { Watcher } from '../transfers/Watcher.js';
|
|
5
|
+
import type { TempFile } from '../transfers/TempFile.js';
|
|
6
|
+
import type { WatchEvent } from '../transfers/WatchEvent.js';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration accepted by {@link FileRepository}'s constructor.
|
|
9
|
+
*
|
|
10
|
+
* `rootPath` becomes the implicit root for every relative path passed
|
|
11
|
+
* to a Repository method; absolute paths are accepted as-is. This
|
|
12
|
+
* mirrors the way `RestRepository.baseUrl` anchors REST requests.
|
|
13
|
+
*/
|
|
14
|
+
export interface FileOptions {
|
|
15
|
+
/**
|
|
16
|
+
* If set, every relative path passed to a method is resolved against
|
|
17
|
+
* this directory. Absolute paths bypass it. Defaults to the current
|
|
18
|
+
* working directory.
|
|
19
|
+
*/
|
|
20
|
+
readonly rootPath?: string;
|
|
21
|
+
}
|
|
22
|
+
interface FileRepoState {
|
|
23
|
+
readonly rootPath: string;
|
|
24
|
+
readonly openWatchers: Set<WatcherImpl>;
|
|
25
|
+
readonly openTempFiles: Set<TempFileImpl>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generalization for Access Layer components that operate on the
|
|
29
|
+
* local filesystem.
|
|
30
|
+
*
|
|
31
|
+
* The protocol is "local filesystem" — every method on this class is
|
|
32
|
+
* a syscall (or a tight wrapper over one) translated into the XF
|
|
33
|
+
* Transfer projection: file content as `string` / `Uint8Array`,
|
|
34
|
+
* metadata as {@link FileStat}, directory entries as
|
|
35
|
+
* {@link FileEntry}, change events as {@link WatchEvent}, and active
|
|
36
|
+
* handles as {@link Watcher} / {@link TempFile}.
|
|
37
|
+
*
|
|
38
|
+
* Concrete components extend this class to expose business-meaningful
|
|
39
|
+
* methods that compose the base operations (e.g. `loadProfile()`
|
|
40
|
+
* calls `this.read('/users/profile.json')` then `JSON.parse`).
|
|
41
|
+
*
|
|
42
|
+
* Errors raised by `node:fs` are translated into typed Exception
|
|
43
|
+
* components ({@link FileNotFoundException},
|
|
44
|
+
* {@link FileAccessDeniedException},
|
|
45
|
+
* {@link DirectoryNotEmptyException}) so the Business Layer never
|
|
46
|
+
* inspects `errno` strings. Other failures propagate as native
|
|
47
|
+
* `Error` (consistent with the XF doctrine that runtime exceptions
|
|
48
|
+
* are well-formed transfer vehicles).
|
|
49
|
+
*
|
|
50
|
+
* `terminate()` releases every still-active {@link Watcher} and
|
|
51
|
+
* deletes every still-open {@link TempFile}: even careless callers
|
|
52
|
+
* won't leak OS handles.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { FileRepository } from '@xfcfam/xf-fs'
|
|
57
|
+
* import type { User } from '../transfers/User.js'
|
|
58
|
+
*
|
|
59
|
+
* export class UsersFileRepository extends FileRepository {
|
|
60
|
+
* constructor() { super({ rootPath: '/var/data/users' }) }
|
|
61
|
+
*
|
|
62
|
+
* async findById(id: string): Promise<User> {
|
|
63
|
+
* const text = await this.read(`${id}.json`)
|
|
64
|
+
* return JSON.parse(text) as User
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* async save(user: User): Promise<void> {
|
|
68
|
+
* await this.write(`${user.id}.json`, JSON.stringify(user))
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare abstract class FileRepository extends Repository<FileRepoState> {
|
|
74
|
+
constructor(options?: FileOptions);
|
|
75
|
+
init(): Promise<void>;
|
|
76
|
+
terminate(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a path against `rootPath`. Absolute paths are returned
|
|
79
|
+
* unchanged (with separators normalised); relative paths are joined
|
|
80
|
+
* to the root.
|
|
81
|
+
*/
|
|
82
|
+
protected resolve(path: string): string;
|
|
83
|
+
/** Read a file as UTF-8 text. */
|
|
84
|
+
read(path: string): Promise<string>;
|
|
85
|
+
/** Read a file as raw bytes. */
|
|
86
|
+
readBytes(path: string): Promise<Uint8Array>;
|
|
87
|
+
/**
|
|
88
|
+
* Write `content` to `path`, overwriting any existing file.
|
|
89
|
+
* Parent directories must already exist; use `mkdir` first if not.
|
|
90
|
+
*/
|
|
91
|
+
write(path: string, content: string | Uint8Array): Promise<void>;
|
|
92
|
+
/** Append `content` to the end of `path` (creating it if absent). */
|
|
93
|
+
append(path: string, content: string | Uint8Array): Promise<void>;
|
|
94
|
+
/** Delete the file at `path`. Throws {@link FileNotFoundException} if absent. */
|
|
95
|
+
delete(path: string): Promise<void>;
|
|
96
|
+
/** Whether the path exists (file, directory, link, anything). */
|
|
97
|
+
exists(path: string): Promise<boolean>;
|
|
98
|
+
/** Stat `path`. Throws {@link FileNotFoundException} if absent. */
|
|
99
|
+
stat(path: string): Promise<FileStat>;
|
|
100
|
+
/** List immediate entries of a directory (not recursive). */
|
|
101
|
+
list(path: string): Promise<FileEntry[]>;
|
|
102
|
+
/**
|
|
103
|
+
* Walk a directory recursively. Returns every regular file and
|
|
104
|
+
* directory under `path`. `relativePath` is computed relative to
|
|
105
|
+
* the walk root.
|
|
106
|
+
*/
|
|
107
|
+
walk(path: string): Promise<FileEntry[]>;
|
|
108
|
+
private walkInto;
|
|
109
|
+
/** Create a directory. `recursive` creates intermediate parents. */
|
|
110
|
+
mkdir(path: string, options?: {
|
|
111
|
+
recursive?: boolean;
|
|
112
|
+
}): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Remove a directory. By default the directory must be empty;
|
|
115
|
+
* pass `{ recursive: true }` to delete contents too.
|
|
116
|
+
*/
|
|
117
|
+
rmdir(path: string, options?: {
|
|
118
|
+
recursive?: boolean;
|
|
119
|
+
}): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Open a streaming reader for `path` as a Web `ReadableStream`.
|
|
122
|
+
* Useful for large files (> a few MB) where buffering the whole
|
|
123
|
+
* content in memory is wasteful.
|
|
124
|
+
*/
|
|
125
|
+
readStream(path: string): ReadableStream<Uint8Array>;
|
|
126
|
+
/**
|
|
127
|
+
* Open a streaming writer for `path` as a Web `WritableStream`.
|
|
128
|
+
* Overwrites any existing content.
|
|
129
|
+
*/
|
|
130
|
+
writeStream(path: string): WritableStream<Uint8Array>;
|
|
131
|
+
/**
|
|
132
|
+
* Watch a file or directory for changes. `callback` is invoked
|
|
133
|
+
* synchronously by the OS for each event.
|
|
134
|
+
*
|
|
135
|
+
* **Platform restriction**: recursive directory watching is supported
|
|
136
|
+
* only on macOS and Windows (via `node:fs`'s native `recursive`
|
|
137
|
+
* option). On Linux, `node:fs` silently ignores the `recursive` flag
|
|
138
|
+
* and emits no sub-directory events, which would be a silent footgun.
|
|
139
|
+
* Therefore this method throws an `Error` when watching a **directory**
|
|
140
|
+
* on Linux. Watching a single **file** works on every platform and is
|
|
141
|
+
* always allowed.
|
|
142
|
+
*
|
|
143
|
+
* @throws {Error} When watching a directory on Linux (where `node:fs`
|
|
144
|
+
* recursive watching is not supported).
|
|
145
|
+
* @throws {FileNotFoundException} If `path` does not exist.
|
|
146
|
+
*/
|
|
147
|
+
watch(path: string, callback: (event: WatchEvent) => void): Promise<Watcher>;
|
|
148
|
+
/**
|
|
149
|
+
* Create a unique temporary file under the OS temp directory. The
|
|
150
|
+
* file exists empty on disk; callers write/read it via `read()` /
|
|
151
|
+
* `write()` passing `tempFile.path`. Closing the returned handle
|
|
152
|
+
* deletes the file. `terminate()` closes all outstanding handles.
|
|
153
|
+
*/
|
|
154
|
+
tempFile(prefix?: string): Promise<TempFile>;
|
|
155
|
+
/**
|
|
156
|
+
* Translate a `node:fs` error into a typed Exception component when
|
|
157
|
+
* the error code is one of the modelled cases; otherwise return the
|
|
158
|
+
* error unchanged for plain propagation.
|
|
159
|
+
*/
|
|
160
|
+
protected translateError(err: unknown, path: string): unknown;
|
|
161
|
+
}
|
|
162
|
+
declare class WatcherImpl implements Watcher {
|
|
163
|
+
readonly path: string;
|
|
164
|
+
private handle;
|
|
165
|
+
private active;
|
|
166
|
+
private readonly registry;
|
|
167
|
+
constructor(path: string, callback: (event: WatchEvent) => void, registry: Set<WatcherImpl>);
|
|
168
|
+
get isActive(): boolean;
|
|
169
|
+
close(): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
declare class TempFileImpl implements TempFile {
|
|
172
|
+
readonly path: string;
|
|
173
|
+
private open;
|
|
174
|
+
private readonly registry;
|
|
175
|
+
constructor(path: string, registry: Set<TempFileImpl>);
|
|
176
|
+
get isOpen(): boolean;
|
|
177
|
+
close(): Promise<void>;
|
|
178
|
+
}
|
|
179
|
+
export {};
|
|
180
|
+
//# sourceMappingURL=FileRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileRepository.d.ts","sourceRoot":"","sources":["../../../../src/repository/general/FileRepository.ts"],"names":[],"mappings":"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"}
|