@dxos/random-access-storage 0.1.57-main.e87098f → 0.1.57
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/dist/lib/browser/index.mjs +194 -222
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +7 -3
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/browser/web-fs.d.ts +30 -16
- package/dist/types/src/browser/web-fs.d.ts.map +1 -1
- package/dist/types/src/common/directory.d.ts +3 -1
- package/dist/types/src/common/directory.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/browser/web-fs.ts +172 -93
- package/src/common/directory.ts +5 -0
- package/dist/types/src/monitor.d.ts +0 -41
- package/dist/types/src/monitor.d.ts.map +0 -1
- package/src/monitor.ts +0 -140
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { RandomAccessStorage } from 'random-access-storage';
|
|
5
|
-
import { Directory, File, Storage, StorageType
|
|
5
|
+
import { Directory, DiskInfo, File, Storage, StorageType } from '../common';
|
|
6
6
|
/**
|
|
7
7
|
* Web file systems.
|
|
8
8
|
*/
|
|
9
9
|
export declare class WebFS implements Storage {
|
|
10
10
|
readonly path: string;
|
|
11
11
|
readonly type = StorageType.WEBFS;
|
|
12
|
-
protected readonly _files: Map<string,
|
|
12
|
+
protected readonly _files: Map<string, WebFile>;
|
|
13
13
|
protected _root?: FileSystemDirectoryHandle;
|
|
14
14
|
constructor(path: string);
|
|
15
15
|
get size(): number;
|
|
@@ -25,42 +25,56 @@ export declare class WebFS implements Storage {
|
|
|
25
25
|
getDiskInfo(): Promise<DiskInfo>;
|
|
26
26
|
}
|
|
27
27
|
export declare class WebFile extends EventEmitter implements File {
|
|
28
|
-
readonly opened: boolean;
|
|
29
|
-
readonly suspended: boolean;
|
|
30
|
-
readonly closed: boolean;
|
|
31
|
-
readonly unlinked: boolean;
|
|
32
|
-
readonly writing: boolean;
|
|
33
|
-
readonly readable: boolean;
|
|
34
|
-
readonly writable: boolean;
|
|
35
|
-
readonly deletable: boolean;
|
|
36
|
-
readonly truncatable: boolean;
|
|
37
|
-
readonly statable: boolean;
|
|
38
28
|
private readonly _fileName;
|
|
39
29
|
private readonly _fileHandle;
|
|
40
30
|
private readonly _destroy;
|
|
41
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Current view of the file contents.
|
|
33
|
+
*/
|
|
34
|
+
private _buffer;
|
|
35
|
+
private _loadBufferPromise;
|
|
36
|
+
private _flushScheduled;
|
|
37
|
+
private _flushPromise;
|
|
38
|
+
private _flushes;
|
|
42
39
|
private _operations;
|
|
43
40
|
private _reads;
|
|
44
41
|
private _readBytes;
|
|
45
42
|
private _writes;
|
|
46
43
|
private _writeBytes;
|
|
44
|
+
get _bufferSize(): number | undefined;
|
|
47
45
|
constructor({ fileName, file, destroy, }: {
|
|
48
46
|
file: Promise<FileSystemFileHandle>;
|
|
49
47
|
fileName: string;
|
|
50
48
|
destroy: () => Promise<void>;
|
|
51
49
|
});
|
|
50
|
+
type: StorageType;
|
|
51
|
+
readonly opened: boolean;
|
|
52
|
+
readonly suspended: boolean;
|
|
53
|
+
readonly closed: boolean;
|
|
54
|
+
readonly unlinked: boolean;
|
|
55
|
+
readonly writing: boolean;
|
|
56
|
+
readonly readable: boolean;
|
|
57
|
+
readonly writable: boolean;
|
|
58
|
+
readonly deletable: boolean;
|
|
59
|
+
readonly truncatable: boolean;
|
|
60
|
+
readonly statable: boolean;
|
|
52
61
|
destroyed: boolean;
|
|
53
62
|
directory: string;
|
|
54
63
|
filename: string;
|
|
55
|
-
type: StorageType;
|
|
56
64
|
native: RandomAccessStorage;
|
|
57
|
-
|
|
65
|
+
private _loadBuffer;
|
|
66
|
+
private _loadBufferGuarded;
|
|
67
|
+
private _flushCache;
|
|
68
|
+
private _flushLater;
|
|
69
|
+
private _flushNow;
|
|
58
70
|
read(offset: number, size: number): Promise<Buffer>;
|
|
71
|
+
write(offset: number, data: Buffer): Promise<void>;
|
|
59
72
|
del(offset: number, size: number): Promise<void>;
|
|
60
73
|
stat(): Promise<{
|
|
61
|
-
size:
|
|
74
|
+
size: number;
|
|
62
75
|
}>;
|
|
63
76
|
truncate(offset: number): Promise<void>;
|
|
77
|
+
flush(): Promise<void>;
|
|
64
78
|
close(): Promise<void>;
|
|
65
79
|
destroy(): Promise<void>;
|
|
66
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-fs.d.ts","sourceRoot":"","sources":["../../../../src/browser/web-fs.ts"],"names":[],"mappings":";;AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAe,
|
|
1
|
+
{"version":3,"file":"web-fs.d.ts","sourceRoot":"","sources":["../../../../src/browser/web-fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,8 @@ export declare class Directory {
|
|
|
9
9
|
private readonly _list;
|
|
10
10
|
private readonly _getOrCreateFile;
|
|
11
11
|
private readonly _delete;
|
|
12
|
-
|
|
12
|
+
private readonly _onFlush?;
|
|
13
|
+
constructor(type: StorageType, path: string, _list: (path: string) => Promise<string[]>, _getOrCreateFile: (path: string, filename: string, opts?: any) => File, _delete: () => Promise<void>, _onFlush?: (() => Promise<void>) | undefined);
|
|
13
14
|
toString(): string;
|
|
14
15
|
/**
|
|
15
16
|
* Create a new sub-directory.
|
|
@@ -23,6 +24,7 @@ export declare class Directory {
|
|
|
23
24
|
* Get or create a new file.
|
|
24
25
|
*/
|
|
25
26
|
getOrCreateFile(filename: string, opts?: any): File;
|
|
27
|
+
flush(): Promise<void>;
|
|
26
28
|
/**
|
|
27
29
|
* Close and delete all files in the directory and all its sub-directories.
|
|
28
30
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../../../src/common/directory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC;;GAEG;AACH,qBAAa,SAAS;aAEF,IAAI,EAAE,WAAW;aACjB,IAAI,EAAE,MAAM;IAE5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO;
|
|
1
|
+
{"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../../../src/common/directory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC;;GAEG;AACH,qBAAa,SAAS;aAEF,IAAI,EAAE,WAAW;aACjB,IAAI,EAAE,MAAM;IAE5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBANV,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,EAEX,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,EAC1C,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,EACtE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC5B,QAAQ,CAAC,SAAQ,QAAQ,IAAI,CAAC,aAAA;IAGjD,QAAQ;IAIR;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;IAIxC;;OAEG;IACH,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIzB;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAI7C,KAAK;IAIX;;OAEG;IACG,MAAM;CAGb"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/random-access-storage",
|
|
3
|
-
"version": "0.1.57
|
|
3
|
+
"version": "0.1.57",
|
|
4
4
|
"description": "Multiple random storage types.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
"random-access-memory": "^4.1.0",
|
|
25
25
|
"random-access-storage": "^3.0.0",
|
|
26
26
|
"random-access-web": "^2.0.3",
|
|
27
|
-
"@dxos/async": "0.1.57
|
|
28
|
-
"@dxos/
|
|
29
|
-
"@dxos/
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
27
|
+
"@dxos/async": "0.1.57",
|
|
28
|
+
"@dxos/context": "0.1.57",
|
|
29
|
+
"@dxos/debug": "0.1.57",
|
|
30
|
+
"@dxos/invariant": "0.1.57",
|
|
31
|
+
"@dxos/log": "0.1.57",
|
|
32
|
+
"@dxos/node-std": "0.1.57",
|
|
33
|
+
"@dxos/tracing": "0.1.57",
|
|
34
|
+
"@dxos/util": "0.1.57"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/pify": "^3.0.2",
|
package/src/browser/web-fs.ts
CHANGED
|
@@ -9,10 +9,9 @@ import { RandomAccessStorage } from 'random-access-storage';
|
|
|
9
9
|
import { synchronized } from '@dxos/async';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
|
-
import { TimeSeriesCounter,
|
|
12
|
+
import { TimeSeriesCounter, trace } from '@dxos/tracing';
|
|
13
13
|
|
|
14
|
-
import { Directory, File, Storage, StorageType, getFullPath
|
|
15
|
-
import { STORAGE_MONITOR } from '../monitor';
|
|
14
|
+
import { Directory, DiskInfo, File, Storage, StorageType, getFullPath } from '../common';
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Web file systems.
|
|
@@ -20,7 +19,7 @@ import { STORAGE_MONITOR } from '../monitor';
|
|
|
20
19
|
export class WebFS implements Storage {
|
|
21
20
|
readonly type = StorageType.WEBFS;
|
|
22
21
|
|
|
23
|
-
protected readonly _files = new Map<string,
|
|
22
|
+
protected readonly _files = new Map<string, WebFile>();
|
|
24
23
|
protected _root?: FileSystemDirectoryHandle;
|
|
25
24
|
|
|
26
25
|
constructor(public readonly path: string) {}
|
|
@@ -29,7 +28,7 @@ export class WebFS implements Storage {
|
|
|
29
28
|
return this._files.size;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
private _getFiles(path: string): Map<string,
|
|
31
|
+
private _getFiles(path: string): Map<string, WebFile> {
|
|
33
32
|
const fullName = this._getFullFilename(this.path, path);
|
|
34
33
|
return new Map(
|
|
35
34
|
[...this._files.entries()].filter(([path, file]) => path.includes(fullName) && file.destroyed !== true),
|
|
@@ -76,6 +75,9 @@ export class WebFS implements Storage {
|
|
|
76
75
|
(path) => this._list(path),
|
|
77
76
|
(...args) => this.getOrCreateFile(...args),
|
|
78
77
|
() => this._delete(sub),
|
|
78
|
+
async () => {
|
|
79
|
+
await Promise.all(Array.from(this._getFiles(sub)).map(([path, file]) => file.flush()));
|
|
80
|
+
},
|
|
79
81
|
);
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -163,24 +165,29 @@ export class WebFS implements Storage {
|
|
|
163
165
|
// TODO(mykola): Remove EventEmitter.
|
|
164
166
|
@trace.resource()
|
|
165
167
|
export class WebFile extends EventEmitter implements File {
|
|
166
|
-
readonly opened: boolean = true;
|
|
167
|
-
readonly suspended: boolean = false;
|
|
168
|
-
readonly closed: boolean = false;
|
|
169
|
-
readonly unlinked: boolean = false;
|
|
170
|
-
readonly writing: boolean = false;
|
|
171
|
-
readonly readable: boolean = true;
|
|
172
|
-
readonly writable: boolean = true;
|
|
173
|
-
readonly deletable: boolean = true;
|
|
174
|
-
readonly truncatable: boolean = true;
|
|
175
|
-
readonly statable: boolean = true;
|
|
176
168
|
@trace.info()
|
|
177
169
|
private readonly _fileName: string;
|
|
178
170
|
|
|
179
171
|
private readonly _fileHandle: Promise<FileSystemFileHandle>;
|
|
180
172
|
private readonly _destroy: () => Promise<void>;
|
|
181
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Current view of the file contents.
|
|
176
|
+
*/
|
|
177
|
+
private _buffer: Uint8Array | null = null;
|
|
178
|
+
|
|
179
|
+
private _loadBufferPromise: Promise<void> | null = null;
|
|
180
|
+
|
|
181
|
+
private _flushScheduled = false;
|
|
182
|
+
|
|
183
|
+
private _flushPromise: Promise<void> | null = null;
|
|
184
|
+
|
|
185
|
+
//
|
|
186
|
+
// Metrics
|
|
187
|
+
//
|
|
188
|
+
|
|
182
189
|
@trace.metricsCounter()
|
|
183
|
-
private
|
|
190
|
+
private _flushes = new TimeSeriesCounter();
|
|
184
191
|
|
|
185
192
|
@trace.metricsCounter()
|
|
186
193
|
private _operations = new TimeSeriesCounter();
|
|
@@ -197,6 +204,11 @@ export class WebFile extends EventEmitter implements File {
|
|
|
197
204
|
@trace.metricsCounter()
|
|
198
205
|
private _writeBytes = new TimeSeriesCounter();
|
|
199
206
|
|
|
207
|
+
@trace.info()
|
|
208
|
+
get _bufferSize() {
|
|
209
|
+
return this._buffer?.length;
|
|
210
|
+
}
|
|
211
|
+
|
|
200
212
|
constructor({
|
|
201
213
|
fileName,
|
|
202
214
|
file,
|
|
@@ -210,13 +222,32 @@ export class WebFile extends EventEmitter implements File {
|
|
|
210
222
|
this._fileName = fileName;
|
|
211
223
|
this._fileHandle = file;
|
|
212
224
|
this._destroy = destroy;
|
|
225
|
+
|
|
226
|
+
void this._loadBufferGuarded();
|
|
213
227
|
}
|
|
214
228
|
|
|
229
|
+
type: StorageType = StorageType.WEBFS;
|
|
230
|
+
|
|
231
|
+
//
|
|
232
|
+
// random-access-storage library compatibility
|
|
233
|
+
//
|
|
234
|
+
|
|
235
|
+
// TODO(dmaretskyi): Are those all needed?
|
|
236
|
+
readonly opened: boolean = true;
|
|
237
|
+
readonly suspended: boolean = false;
|
|
238
|
+
readonly closed: boolean = false;
|
|
239
|
+
readonly unlinked: boolean = false;
|
|
240
|
+
readonly writing: boolean = false;
|
|
241
|
+
readonly readable: boolean = true;
|
|
242
|
+
readonly writable: boolean = true;
|
|
243
|
+
readonly deletable: boolean = true;
|
|
244
|
+
readonly truncatable: boolean = true;
|
|
245
|
+
readonly statable: boolean = true;
|
|
246
|
+
|
|
215
247
|
destroyed = false;
|
|
216
248
|
directory = '';
|
|
217
249
|
// TODO(dmaretskyi): is this used?
|
|
218
250
|
filename = '';
|
|
219
|
-
type: StorageType = StorageType.WEBFS;
|
|
220
251
|
native: RandomAccessStorage = {
|
|
221
252
|
write: callbackify(this.write.bind(this)),
|
|
222
253
|
read: callbackify(this.read.bind(this)),
|
|
@@ -226,113 +257,161 @@ export class WebFile extends EventEmitter implements File {
|
|
|
226
257
|
truncate: callbackify(this.truncate?.bind(this)),
|
|
227
258
|
} as RandomAccessStorage;
|
|
228
259
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
this.
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
private async _loadBuffer() {
|
|
261
|
+
const fileHandle = await this._fileHandle;
|
|
262
|
+
const file = await fileHandle.getFile();
|
|
263
|
+
this._buffer = new Uint8Array(await file.arrayBuffer());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async _loadBufferGuarded() {
|
|
267
|
+
await (this._loadBufferPromise ??= this._loadBuffer());
|
|
268
|
+
}
|
|
235
269
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
241
|
-
await writable.write({ type: 'write', data, position: offset });
|
|
242
|
-
await writable.close();
|
|
243
|
-
} finally {
|
|
244
|
-
span.end();
|
|
245
|
-
metric.end();
|
|
270
|
+
// Do not call directly, use _flushLater or _flushNow.
|
|
271
|
+
private async _flushCache() {
|
|
272
|
+
if (this.destroyed) {
|
|
273
|
+
return;
|
|
246
274
|
}
|
|
275
|
+
|
|
276
|
+
this._flushes.inc();
|
|
277
|
+
|
|
278
|
+
await this._loadBufferGuarded();
|
|
279
|
+
invariant(this._buffer);
|
|
280
|
+
|
|
281
|
+
const fileHandle = await this._fileHandle;
|
|
282
|
+
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
283
|
+
await writable.write({ type: 'write', data: this._buffer, position: 0 });
|
|
284
|
+
await writable.close();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private _flushLater() {
|
|
288
|
+
if (this._flushScheduled) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
setTimeout(async () => {
|
|
293
|
+
// Making sure only one flush can run at a time.
|
|
294
|
+
const promiseBefore = this._flushPromise;
|
|
295
|
+
await this._flushPromise;
|
|
296
|
+
this._flushScheduled = false;
|
|
297
|
+
|
|
298
|
+
// _flushNow might have been called. In that case we don't want to run the flush again.
|
|
299
|
+
if (promiseBefore !== this._flushPromise) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this._flushPromise = this._flushCache().catch((err) => log.warn(err));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
this._flushScheduled = true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private async _flushNow() {
|
|
310
|
+
this._flushPromise = (this._flushPromise ?? Promise.resolve())
|
|
311
|
+
.then(() => this._flushCache())
|
|
312
|
+
.catch((err) => log.warn(err));
|
|
313
|
+
|
|
314
|
+
await this._flushPromise;
|
|
247
315
|
}
|
|
248
316
|
|
|
249
|
-
@synchronized
|
|
250
317
|
async read(offset: number, size: number) {
|
|
251
|
-
const span = this._usage.beginRecording();
|
|
252
318
|
this._operations.inc();
|
|
253
319
|
this._reads.inc();
|
|
254
320
|
this._readBytes.inc(size);
|
|
255
321
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// does not copy the buffer
|
|
264
|
-
return Buffer.from(await file.slice(offset, offset + size).arrayBuffer());
|
|
265
|
-
} finally {
|
|
266
|
-
span.end();
|
|
267
|
-
metric.end();
|
|
322
|
+
if (!this._buffer) {
|
|
323
|
+
await this._loadBufferGuarded();
|
|
324
|
+
invariant(this._buffer);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (offset + size > this._buffer.length) {
|
|
328
|
+
throw new Error('Read out of bounds');
|
|
268
329
|
}
|
|
330
|
+
|
|
331
|
+
// Copy data into a new buffer.
|
|
332
|
+
return Buffer.from(this._buffer.slice(offset, offset + size));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async write(offset: number, data: Buffer) {
|
|
336
|
+
this._operations.inc();
|
|
337
|
+
this._writes.inc();
|
|
338
|
+
this._writeBytes.inc(data.length);
|
|
339
|
+
|
|
340
|
+
if (!this._buffer) {
|
|
341
|
+
await this._loadBufferGuarded();
|
|
342
|
+
invariant(this._buffer);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (offset + data.length <= this._buffer.length) {
|
|
346
|
+
this._buffer.set(data, offset);
|
|
347
|
+
} else {
|
|
348
|
+
// TODO(dmaretskyi): Optimize re-allocations.
|
|
349
|
+
const newCache = new Uint8Array(offset + data.length);
|
|
350
|
+
newCache.set(this._buffer);
|
|
351
|
+
newCache.set(data, offset);
|
|
352
|
+
this._buffer = newCache;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this._flushLater();
|
|
269
356
|
}
|
|
270
357
|
|
|
271
|
-
@synchronized
|
|
272
358
|
async del(offset: number, size: number) {
|
|
273
|
-
const span = this._usage.beginRecording();
|
|
274
359
|
this._operations.inc();
|
|
275
360
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const fileHandle: any = await this._fileHandle;
|
|
282
|
-
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
283
|
-
const file = await fileHandle.getFile();
|
|
284
|
-
let leftoverSize = 0;
|
|
285
|
-
if (offset + size < file.size) {
|
|
286
|
-
// does not copy the buffer
|
|
287
|
-
const leftover = Buffer.from(await file.slice(offset + size, file.size).arrayBuffer());
|
|
288
|
-
leftoverSize = leftover.length;
|
|
289
|
-
await writable.write({ type: 'write', data: leftover, position: offset });
|
|
290
|
-
}
|
|
361
|
+
if (offset < 0 || size < 0) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
291
364
|
|
|
292
|
-
|
|
293
|
-
await
|
|
294
|
-
|
|
295
|
-
span.end();
|
|
296
|
-
metric.end();
|
|
365
|
+
if (!this._buffer) {
|
|
366
|
+
await this._loadBufferGuarded();
|
|
367
|
+
invariant(this._buffer);
|
|
297
368
|
}
|
|
369
|
+
|
|
370
|
+
let leftoverSize = 0;
|
|
371
|
+
if (offset + size < this._buffer.length) {
|
|
372
|
+
leftoverSize = this._buffer.length - (offset + size);
|
|
373
|
+
this._buffer.set(this._buffer.slice(offset + size, offset + size + leftoverSize), offset);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this._buffer = this._buffer.slice(0, offset + leftoverSize);
|
|
377
|
+
|
|
378
|
+
this._flushLater();
|
|
298
379
|
}
|
|
299
380
|
|
|
300
|
-
@synchronized
|
|
301
381
|
async stat() {
|
|
302
|
-
const span = this._usage.beginRecording();
|
|
303
382
|
this._operations.inc();
|
|
304
383
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
size: file.size,
|
|
311
|
-
};
|
|
312
|
-
} finally {
|
|
313
|
-
span.end();
|
|
314
|
-
metric.end();
|
|
384
|
+
// NOTE: This will load all data from the file just to get it's size. While this is a lot of overhead, this works ok for out use cases.
|
|
385
|
+
if (!this._buffer) {
|
|
386
|
+
await this._loadBufferGuarded();
|
|
387
|
+
invariant(this._buffer);
|
|
315
388
|
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
size: this._buffer.length,
|
|
392
|
+
};
|
|
316
393
|
}
|
|
317
394
|
|
|
318
|
-
@synchronized
|
|
319
395
|
async truncate(offset: number) {
|
|
320
|
-
const span = this._usage.beginRecording();
|
|
321
396
|
this._operations.inc();
|
|
322
397
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
327
|
-
await writable.write({ type: 'truncate', size: offset });
|
|
328
|
-
await writable.close();
|
|
329
|
-
} finally {
|
|
330
|
-
span.end();
|
|
331
|
-
metric.end();
|
|
398
|
+
if (!this._buffer) {
|
|
399
|
+
await this._loadBufferGuarded();
|
|
400
|
+
invariant(this._buffer);
|
|
332
401
|
}
|
|
402
|
+
|
|
403
|
+
this._buffer = this._buffer.slice(0, offset);
|
|
404
|
+
|
|
405
|
+
this._flushLater();
|
|
333
406
|
}
|
|
334
407
|
|
|
335
|
-
async
|
|
408
|
+
async flush() {
|
|
409
|
+
await this._flushNow();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async close(): Promise<void> {
|
|
413
|
+
await this._flushNow();
|
|
414
|
+
}
|
|
336
415
|
|
|
337
416
|
@synchronized
|
|
338
417
|
async destroy() {
|
package/src/common/directory.ts
CHANGED
|
@@ -17,6 +17,7 @@ export class Directory {
|
|
|
17
17
|
private readonly _list: (path: string) => Promise<string[]>,
|
|
18
18
|
private readonly _getOrCreateFile: (path: string, filename: string, opts?: any) => File,
|
|
19
19
|
private readonly _delete: () => Promise<void>,
|
|
20
|
+
private readonly _onFlush?: () => Promise<void>,
|
|
20
21
|
) {}
|
|
21
22
|
|
|
22
23
|
toString() {
|
|
@@ -44,6 +45,10 @@ export class Directory {
|
|
|
44
45
|
return this._getOrCreateFile(this.path, filename, opts);
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
async flush() {
|
|
49
|
+
await this._onFlush?.();
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
/**
|
|
48
53
|
* Close and delete all files in the directory and all its sub-directories.
|
|
49
54
|
*/
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export type StorageOperation = {
|
|
2
|
-
resource: string;
|
|
3
|
-
type: 'read' | 'write' | 'stat' | 'delete' | 'truncate';
|
|
4
|
-
size?: number;
|
|
5
|
-
};
|
|
6
|
-
export type OpHandle = {
|
|
7
|
-
end: () => void;
|
|
8
|
-
};
|
|
9
|
-
export type StatsBucket = {
|
|
10
|
-
resource: string;
|
|
11
|
-
beginTime: number;
|
|
12
|
-
endTime: number;
|
|
13
|
-
/**
|
|
14
|
-
* Fraction of time spent in storage operations.
|
|
15
|
-
*/
|
|
16
|
-
usage: number;
|
|
17
|
-
readOps: number;
|
|
18
|
-
readBytes: number;
|
|
19
|
-
readTime: number;
|
|
20
|
-
writeOps: number;
|
|
21
|
-
writeBytes: number;
|
|
22
|
-
writeTime: number;
|
|
23
|
-
otherOps: number;
|
|
24
|
-
otherTime: number;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Storage performance metrics.
|
|
28
|
-
*/
|
|
29
|
-
export declare class StorageMonitor {
|
|
30
|
-
private _enabled;
|
|
31
|
-
/**
|
|
32
|
-
* By resource.
|
|
33
|
-
*/
|
|
34
|
-
private readonly _buckets;
|
|
35
|
-
enable(): void;
|
|
36
|
-
beginOp(op: StorageOperation): OpHandle;
|
|
37
|
-
private _getOrInitBucket;
|
|
38
|
-
private _finalizeBuckets;
|
|
39
|
-
}
|
|
40
|
-
export declare const STORAGE_MONITOR: StorageMonitor;
|
|
41
|
-
//# sourceMappingURL=monitor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../../../src/monitor.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IAEjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAElB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAIF;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,MAAM;IASN,OAAO,CAAC,EAAE,EAAE,gBAAgB,GAAG,QAAQ;IAiCvC,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,gBAAgB;CAiBzB;AAED,eAAO,MAAM,eAAe,gBAAuB,CAAC"}
|