@poe-platform/safe-fs 0.1.730 → 0.1.731

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.
@@ -69,6 +69,7 @@ export declare class MemoryFileSystem implements FileSystem {
69
69
  private openWrite;
70
70
  private writeData;
71
71
  private writeAt;
72
+ private writeAtRange;
72
73
  writeMemoryFileFast(path: string, data: Uint8Array, append: boolean, mode: number): void;
73
74
  writeMemoryFileInDirFast(dirPrefix: string, name: string, data: Uint8Array, append: boolean, mode: number): void;
74
75
  readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
@@ -118,13 +119,15 @@ export declare class MemoryFileSystem implements FileSystem {
118
119
  }
119
120
  export declare function createMemoryFileSystem(options?: MemoryFileSystemOptions | Readonly<Record<string, unknown>>): MemoryFileSystem;
120
121
  export declare class MemoryRedirectHandle {
121
- readonly fs: MemoryFileSystem;
122
- readonly path: string;
123
- readonly node: FileNode;
124
- readonly append: boolean;
122
+ fs: MemoryFileSystem;
123
+ path: string;
124
+ node: FileNode;
125
+ append: boolean;
125
126
  position: number;
126
127
  closed: boolean;
127
128
  constructor(fs: MemoryFileSystem, path: string, node: FileNode, append: boolean);
129
+ reset(fs: MemoryFileSystem, path: string, node: FileNode, append: boolean): void;
130
+ writeRangeSync(src: Uint8Array, len: number, signal?: AbortSignal): void;
128
131
  writeSync(chunk: Uint8Array, signal?: AbortSignal): void;
129
132
  close(): void;
130
133
  }
@@ -16,12 +16,15 @@ import { normalizeMemoryFileSystemLimits } from "./limits.js";
16
16
  export { defaultMemoryFileSystemLimits } from "./limits.js";
17
17
  class MemoryFileNode {
18
18
  constructor(mode, ino, now, byteLength, allocation, view) {
19
+ this.revision = 0;
19
20
  this.mode = mode;
20
21
  this.ino = ino;
22
+ this.nlink = 1;
21
23
  this.atimeMs = now;
22
24
  this.mtimeMs = now;
23
25
  this.ctimeMs = now;
24
26
  this.birthtimeMs = now;
27
+ this.references = 0;
25
28
  this.byteLength = byteLength;
26
29
  this.allocation = allocation;
27
30
  this.view = view;
@@ -46,19 +49,216 @@ let smallAllocSlab = new Uint8Array(SMALL_ALLOC_SLAB_SIZE);
46
49
  let smallAllocOffset = 0;
47
50
  const DUMMY_POOL_LEDGER = new MemoryLedger(normalizeMemoryFileSystemLimits({}));
48
51
  const DUMMY_POOL_ALLOCATION = new MemoryAllocation(EMPTY_ALLOC_BYTES, DUMMY_POOL_LEDGER);
49
- const sharedAllocationPool = [];
50
- const sharedFileNodePool = [];
51
- const sharedDirectoryNodePool = [];
52
- const sharedRemovedScratch = [];
52
+ const DUMMY_POOL_FILE_NODE = new MemoryFileNode(0, 0, null, 0, DUMMY_POOL_ALLOCATION, DUMMY_POOL_ALLOCATION.data);
53
+ DUMMY_POOL_FILE_NODE.atimeMs = DUMMY_POOL_FILE_NODE.mtimeMs = DUMMY_POOL_FILE_NODE.ctimeMs = DUMMY_POOL_FILE_NODE.birthtimeMs = 1700000000000;
54
+ const defaultDateNow = Date.now;
55
+ let fastWriteCachedNow = Date.now();
56
+ let fastWriteNowTick = 0;
57
+ class FastDirectoryEntriesMap {
58
+ _table = new Int16Array(128).fill(-1);
59
+ _mask = 127;
60
+ _keys = new Array(64).fill("");
61
+ _vals = new Array(64).fill(DUMMY_POOL_FILE_NODE);
62
+ _next = 0;
63
+ size = 0;
64
+ [Symbol.toStringTag] = "Map";
65
+ _hash(k) {
66
+ let h = 5381;
67
+ for (let i = 0; i < k.length; i++) {
68
+ h = (((h & 0x1fffff) * 33) ^ k.charCodeAt(i)) & 0x3fffffff;
69
+ }
70
+ return h & this._mask;
71
+ }
72
+ get(k) {
73
+ if (this.size === 0)
74
+ return undefined;
75
+ let slot = this._hash(k);
76
+ const mask = this._mask;
77
+ const table = this._table;
78
+ const keys = this._keys;
79
+ while (true) {
80
+ const idx = table[slot];
81
+ if (idx === -1)
82
+ return undefined;
83
+ if (idx >= 0 && keys[idx] === k)
84
+ return this._vals[idx];
85
+ slot = (slot + 1) & mask;
86
+ }
87
+ }
88
+ has(k) {
89
+ return this.get(k) !== undefined;
90
+ }
91
+ _rebuild(newCap) {
92
+ const newTableLen = newCap * 2;
93
+ const newMask = newTableLen - 1;
94
+ const newTable = newCap <= 16384 ? new Int16Array(newTableLen).fill(-1) : new Int32Array(newTableLen).fill(-1);
95
+ const oldKeys = this._keys;
96
+ const oldVals = this._vals;
97
+ const oldNext = this._next;
98
+ const newKeys = newCap === oldKeys.length ? oldKeys : new Array(newCap).fill("");
99
+ const newVals = newCap === oldVals.length ? oldVals : new Array(newCap).fill(DUMMY_POOL_FILE_NODE);
100
+ let writeIdx = 0;
101
+ for (let i = 0; i < oldNext; i++) {
102
+ const v = oldVals[i];
103
+ if (v === DUMMY_POOL_FILE_NODE)
104
+ continue;
105
+ const k = oldKeys[i];
106
+ newKeys[writeIdx] = k;
107
+ newVals[writeIdx] = v;
108
+ let h = 5381;
109
+ for (let c = 0; c < k.length; c++) {
110
+ h = (((h & 0x1fffff) * 33) ^ k.charCodeAt(c)) & 0x3fffffff;
111
+ }
112
+ let slot = h & newMask;
113
+ while (newTable[slot] !== -1) {
114
+ slot = (slot + 1) & newMask;
115
+ }
116
+ newTable[slot] = writeIdx++;
117
+ }
118
+ if (newKeys === oldKeys) {
119
+ for (let i = writeIdx; i < oldNext; i++) {
120
+ newKeys[i] = "";
121
+ newVals[i] = DUMMY_POOL_FILE_NODE;
122
+ }
123
+ }
124
+ this._table = newTable;
125
+ this._mask = newMask;
126
+ this._keys = newKeys;
127
+ this._vals = newVals;
128
+ this._next = writeIdx;
129
+ }
130
+ set(k, v) {
131
+ if (this._next >= this._keys.length) {
132
+ this._rebuild(this.size * 2 <= this._keys.length ? this._keys.length : this._keys.length * 2);
133
+ }
134
+ let slot = this._hash(k);
135
+ const mask = this._mask;
136
+ const table = this._table;
137
+ const keys = this._keys;
138
+ let firstDeleted = -1;
139
+ while (true) {
140
+ const idx = table[slot];
141
+ if (idx === -1) {
142
+ const targetSlot = firstDeleted !== -1 ? firstDeleted : slot;
143
+ const entryIdx = this._next++;
144
+ table[targetSlot] = entryIdx;
145
+ keys[entryIdx] = k;
146
+ this._vals[entryIdx] = v;
147
+ this.size++;
148
+ return this;
149
+ }
150
+ if (idx === -2) {
151
+ if (firstDeleted === -1)
152
+ firstDeleted = slot;
153
+ }
154
+ else if (keys[idx] === k) {
155
+ this._vals[idx] = v;
156
+ return this;
157
+ }
158
+ slot = (slot + 1) & mask;
159
+ }
160
+ }
161
+ delete(k) {
162
+ if (this.size === 0)
163
+ return false;
164
+ let slot = this._hash(k);
165
+ const mask = this._mask;
166
+ const table = this._table;
167
+ const keys = this._keys;
168
+ while (true) {
169
+ const idx = table[slot];
170
+ if (idx === -1)
171
+ return false;
172
+ if (idx >= 0 && keys[idx] === k) {
173
+ if (this.size === 1) {
174
+ this.clear();
175
+ return true;
176
+ }
177
+ table[slot] = -2;
178
+ keys[idx] = "";
179
+ this._vals[idx] = DUMMY_POOL_FILE_NODE;
180
+ if (idx === this._next - 1)
181
+ this._next--;
182
+ this.size--;
183
+ return true;
184
+ }
185
+ slot = (slot + 1) & mask;
186
+ }
187
+ }
188
+ clear() {
189
+ if (this._next > 0) {
190
+ this._table.fill(-1);
191
+ for (let i = 0; i < this._next; i++) {
192
+ this._keys[i] = "";
193
+ this._vals[i] = DUMMY_POOL_FILE_NODE;
194
+ }
195
+ this._next = 0;
196
+ this.size = 0;
197
+ }
198
+ }
199
+ forEach(callbackfn, thisArg) {
200
+ const next = this._next;
201
+ const keys = this._keys;
202
+ const vals = this._vals;
203
+ for (let i = 0; i < next; i++) {
204
+ const v = vals[i];
205
+ if (v !== DUMMY_POOL_FILE_NODE) {
206
+ if (thisArg !== undefined)
207
+ callbackfn.call(thisArg, v, keys[i], this);
208
+ else
209
+ callbackfn(v, keys[i], this);
210
+ }
211
+ }
212
+ }
213
+ *keys() {
214
+ for (let i = 0; i < this._next; i++) {
215
+ if (this._vals[i] !== DUMMY_POOL_FILE_NODE)
216
+ yield this._keys[i];
217
+ }
218
+ }
219
+ *values() {
220
+ for (let i = 0; i < this._next; i++) {
221
+ const v = this._vals[i];
222
+ if (v !== DUMMY_POOL_FILE_NODE)
223
+ yield v;
224
+ }
225
+ }
226
+ *entries() {
227
+ for (let i = 0; i < this._next; i++) {
228
+ const v = this._vals[i];
229
+ if (v !== DUMMY_POOL_FILE_NODE)
230
+ yield [this._keys[i], v];
231
+ }
232
+ }
233
+ [Symbol.iterator]() {
234
+ return this.entries();
235
+ }
236
+ }
237
+ const SHARED_POOL_CAPACITY = 256;
238
+ const SHARED_DIR_POOL_CAPACITY = 64;
239
+ const sharedAllocationPool = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
240
+ let sharedAllocationPoolLen = 0;
241
+ const sharedFileNodePool = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_FILE_NODE);
242
+ let sharedFileNodePoolLen = 0;
243
+ const sharedDirectoryNodePool = new Array(SHARED_DIR_POOL_CAPACITY);
244
+ let sharedDirectoryNodePoolLen = 0;
245
+ const sharedRemovedScratch = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_FILE_NODE);
246
+ let sharedRemovedLen = 0;
53
247
  let sharedRemovedKeyChars = 0;
54
248
  let sharedRemovedKeyEntries = 0;
55
249
  function collectRemovedChild(child, name) {
56
- sharedRemovedScratch.push(child);
250
+ if (sharedRemovedLen < sharedRemovedScratch.length) {
251
+ sharedRemovedScratch[sharedRemovedLen++] = child;
252
+ }
253
+ else {
254
+ sharedRemovedScratch.push(child);
255
+ sharedRemovedLen++;
256
+ }
57
257
  sharedRemovedKeyChars += name.length;
58
258
  sharedRemovedKeyEntries++;
59
259
  }
60
260
  function replenishSharedMemoryPools() {
61
- while (sharedAllocationPool.length < 112) {
261
+ while (sharedAllocationPoolLen < 112) {
62
262
  if (smallAllocOffset + 64 > SMALL_ALLOC_SLAB_SIZE) {
63
263
  smallAllocSlab = new Uint8Array(SMALL_ALLOC_SLAB_SIZE);
64
264
  smallAllocOffset = 0;
@@ -68,24 +268,29 @@ function replenishSharedMemoryPools() {
68
268
  DUMMY_POOL_LEDGER.reserve(64, 0, "init", "/");
69
269
  const alloc = new MemoryAllocation(slice, DUMMY_POOL_LEDGER);
70
270
  alloc.release();
71
- sharedAllocationPool.push(alloc);
271
+ sharedAllocationPool[sharedAllocationPoolLen++] = alloc;
72
272
  }
73
- while (sharedFileNodePool.length < 112) {
74
- sharedFileNodePool.push(new MemoryFileNode(0, 0, 0, 0, DUMMY_POOL_ALLOCATION, undefined));
273
+ while (sharedFileNodePoolLen < 112) {
274
+ sharedFileNodePool[sharedFileNodePoolLen++] = new MemoryFileNode(0, 0, fastWriteCachedNow, 0, DUMMY_POOL_ALLOCATION, undefined);
75
275
  }
76
- while (sharedDirectoryNodePool.length < 16) {
77
- sharedDirectoryNodePool.push(new MemoryDirectoryNode(0, 0, 0));
276
+ while (sharedDirectoryNodePoolLen < 16) {
277
+ sharedDirectoryNodePool[sharedDirectoryNodePoolLen++] = new MemoryDirectoryNode(0, 0, fastWriteCachedNow);
78
278
  }
79
279
  }
80
280
  class MemoryDirectoryNode {
81
281
  constructor(mode, ino, now) {
282
+ this.revision = 0;
82
283
  this.mode = mode;
83
284
  this.ino = ino;
285
+ this.nlink = 1;
286
+ this.references = 0;
84
287
  this.atimeMs = now;
85
288
  this.mtimeMs = now;
86
289
  this.ctimeMs = now;
87
290
  this.birthtimeMs = now;
88
- this.entries = new Map();
291
+ this.entries = new FastDirectoryEntriesMap();
292
+ this.cachedNlink = 2;
293
+ this.cachedNlinkRev = 0;
89
294
  }
90
295
  }
91
296
  Object.assign(MemoryDirectoryNode.prototype, {
@@ -96,6 +301,8 @@ Object.assign(MemoryDirectoryNode.prototype, {
96
301
  cachedNlink: 2,
97
302
  cachedNlinkRev: 0,
98
303
  });
304
+ const DUMMY_POOL_DIR_NODE = new MemoryDirectoryNode(0, 0, null);
305
+ DUMMY_POOL_DIR_NODE.atimeMs = DUMMY_POOL_DIR_NODE.mtimeMs = DUMMY_POOL_DIR_NODE.ctimeMs = DUMMY_POOL_DIR_NODE.birthtimeMs = 1700000000000;
99
306
  class MemoryCache {
100
307
  allocations = [];
101
308
  files = [];
@@ -117,6 +324,9 @@ class MemoryCache {
117
324
  const typeModes = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
118
325
  const emptyResolveOptions = Object.freeze({});
119
326
  const noFollowResolveOptions = Object.freeze({ followFinal: false });
327
+ const sharedMkdirResolveOptions = { createDirectories: 0o777 };
328
+ const sharedAllowMissingNoFollowOptions = Object.freeze({ allowMissing: true, followFinal: false });
329
+ const sharedFastLocation = { node: undefined, parent: DUMMY_POOL_DIR_NODE, name: "", path: "" };
120
330
  const resolvedVoid = Promise.resolve();
121
331
  const preferredIoBlockSize = 64 * 1024;
122
332
  const ext4HtreeEof64 = (1n << 63n) - 1n;
@@ -496,11 +706,17 @@ export class MemoryFileSystem {
496
706
  atimeMs: now, mtimeMs: now, ctimeMs: now, birthtimeMs: now,
497
707
  };
498
708
  }
499
- directory(mode) {
500
- const now = Date.now();
709
+ directory(mode, now = Date.now()) {
501
710
  const fullMode = typeModes.directory | mode;
502
711
  const ino = this.nextInode++;
503
- const pooled = sharedDirectoryNodePool.pop() ?? memoryCaches.get(this.ledger)?.directories.pop();
712
+ let pooled;
713
+ if (sharedDirectoryNodePoolLen > 0) {
714
+ pooled = sharedDirectoryNodePool[--sharedDirectoryNodePoolLen];
715
+ sharedDirectoryNodePool[sharedDirectoryNodePoolLen] = DUMMY_POOL_DIR_NODE;
716
+ }
717
+ else {
718
+ pooled = memoryCaches.get(this.ledger)?.directories.pop();
719
+ }
504
720
  if (pooled) {
505
721
  pooled.mode = fullMode;
506
722
  pooled.ino = ino;
@@ -517,13 +733,13 @@ export class MemoryFileSystem {
517
733
  }
518
734
  return new MemoryDirectoryNode(fullMode, ino, now);
519
735
  }
520
- addDirectoryNode(parent, name, mode, syscall, path) {
736
+ addDirectoryNode(parent, name, mode, syscall, path, now = Date.now()) {
521
737
  const bytes = name.length * 2;
522
738
  this.ledger.reserve(bytes, 2, syscall, path);
523
739
  try {
524
- const node = this.directory(mode);
740
+ const node = this.directory(mode, now);
525
741
  parent.entries.set(name, node);
526
- this.changed(parent);
742
+ this.changed(parent, now);
527
743
  return node;
528
744
  }
529
745
  catch (error) {
@@ -569,15 +785,15 @@ export class MemoryFileSystem {
569
785
  alloc.release();
570
786
  if (alloc.isReleased64()) {
571
787
  alloc.detachLedger(DUMMY_POOL_LEDGER);
572
- if (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity && sharedAllocationPool.length < 128) {
573
- sharedAllocationPool.push(alloc);
788
+ if (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity && sharedAllocationPoolLen < SHARED_POOL_CAPACITY) {
789
+ sharedAllocationPool[sharedAllocationPoolLen++] = alloc;
574
790
  }
575
791
  else if (cache.allocations.length < 128) {
576
792
  cache.allocations.push(alloc);
577
793
  }
578
794
  }
579
- if (sharedFileNodePool.length < 128) {
580
- sharedFileNodePool.push(node);
795
+ if (sharedFileNodePoolLen < SHARED_POOL_CAPACITY) {
796
+ sharedFileNodePool[sharedFileNodePoolLen++] = node;
581
797
  }
582
798
  else if (cache.files.length < 128) {
583
799
  cache.files.push(node);
@@ -592,8 +808,8 @@ export class MemoryFileSystem {
592
808
  cache.lastFastDirNode = undefined;
593
809
  }
594
810
  if (node.entries.size === 0) {
595
- if (sharedDirectoryNodePool.length < 32) {
596
- sharedDirectoryNodePool.push(node);
811
+ if (sharedDirectoryNodePoolLen < SHARED_DIR_POOL_CAPACITY) {
812
+ sharedDirectoryNodePool[sharedDirectoryNodePoolLen++] = node;
597
813
  }
598
814
  else if (cache.directories.length < 32) {
599
815
  cache.directories.push(node);
@@ -967,12 +1183,17 @@ export class MemoryFileSystem {
967
1183
  this.ledger.fileSize(length, syscall, path);
968
1184
  this.ledger.reserve(length, 0, syscall, path);
969
1185
  if (length === 0) {
970
- return new MemoryAllocation(EMPTY_ALLOC_BYTES, this.ledger);
1186
+ return DUMMY_POOL_ALLOCATION;
971
1187
  }
972
1188
  if (length === 64) {
973
- const pooled = (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity
974
- ? sharedAllocationPool.pop()
975
- : undefined) ?? memoryCaches.get(this.ledger).allocations.pop();
1189
+ let pooled;
1190
+ if (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity && sharedAllocationPoolLen > 0) {
1191
+ pooled = sharedAllocationPool[--sharedAllocationPoolLen];
1192
+ sharedAllocationPool[sharedAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
1193
+ }
1194
+ else {
1195
+ pooled = memoryCaches.get(this.ledger).allocations.pop();
1196
+ }
976
1197
  if (pooled) {
977
1198
  pooled.reuse(this.ledger);
978
1199
  return pooled;
@@ -1092,7 +1313,14 @@ export class MemoryFileSystem {
1092
1313
  const now = Date.now();
1093
1314
  const fileMode = typeModes.file | target.mode;
1094
1315
  const view = capacity === length ? allocation.data : undefined;
1095
- const pooled = sharedFileNodePool.pop() ?? memoryCaches.get(this.ledger).files.pop();
1316
+ let pooled;
1317
+ if (sharedFileNodePoolLen > 0) {
1318
+ pooled = sharedFileNodePool[--sharedFileNodePoolLen];
1319
+ sharedFileNodePool[sharedFileNodePoolLen] = DUMMY_POOL_FILE_NODE;
1320
+ }
1321
+ else {
1322
+ pooled = memoryCaches.get(this.ledger).files.pop();
1323
+ }
1096
1324
  if (pooled) {
1097
1325
  pooled.mode = fileMode;
1098
1326
  pooled.ino = this.nextInode++;
@@ -1128,11 +1356,14 @@ export class MemoryFileSystem {
1128
1356
  }
1129
1357
  }
1130
1358
  writeAt(node, data, position, syscall, path) {
1131
- if (data.byteLength === 0) {
1132
- this.changed(node);
1359
+ this.writeAtRange(node, data, data.byteLength, position, syscall, path, Date.now());
1360
+ }
1361
+ writeAtRange(node, data, dataLen, position, syscall, path, now) {
1362
+ if (dataLen === 0) {
1363
+ this.changed(node, now);
1133
1364
  return;
1134
1365
  }
1135
- const end = position + data.byteLength;
1366
+ const end = position + dataLen;
1136
1367
  this.ledger.fileSize(end, syscall, path);
1137
1368
  const curLen = node.byteLength;
1138
1369
  const length = Math.max(curLen, end);
@@ -1151,11 +1382,17 @@ export class MemoryFileSystem {
1151
1382
  }
1152
1383
  try {
1153
1384
  const storage = allocation.data;
1154
- if (allocation !== node.allocation)
1155
- storage.set(node.view ?? (curLen === node.allocation.data.byteLength ? node.allocation.data : node.allocation.data.subarray(0, curLen)));
1385
+ if (allocation !== node.allocation && curLen > 0) {
1386
+ storage.set(node.allocation.data.subarray(0, curLen));
1387
+ }
1156
1388
  if (position > curLen)
1157
1389
  storage.fill(0, curLen, position);
1158
- storage.set(data, position);
1390
+ if (dataLen === data.byteLength) {
1391
+ storage.set(data, position);
1392
+ }
1393
+ else {
1394
+ storage.set(data.subarray(0, dataLen), position);
1395
+ }
1159
1396
  }
1160
1397
  catch (error) {
1161
1398
  if (allocation !== node.allocation)
@@ -1169,7 +1406,7 @@ export class MemoryFileSystem {
1169
1406
  node.byteLength = length;
1170
1407
  node.view = length === allocation.data.byteLength ? allocation.data : undefined;
1171
1408
  }
1172
- this.changed(node);
1409
+ this.changed(node, now);
1173
1410
  }
1174
1411
  writeMemoryFileFast(path, data, append, mode) {
1175
1412
  const cache = memoryCaches.get(this.ledger);
@@ -1264,7 +1501,14 @@ export class MemoryFileSystem {
1264
1501
  const now = Date.now();
1265
1502
  const fileMode = typeModes.file | mode;
1266
1503
  const view = capacity === length ? allocation.data : undefined;
1267
- let node = sharedFileNodePool.pop() ?? cache.files.pop();
1504
+ let node;
1505
+ if (sharedFileNodePoolLen > 0) {
1506
+ node = sharedFileNodePool[--sharedFileNodePoolLen];
1507
+ sharedFileNodePool[sharedFileNodePoolLen] = DUMMY_POOL_FILE_NODE;
1508
+ }
1509
+ else {
1510
+ node = cache.files.pop();
1511
+ }
1268
1512
  if (node) {
1269
1513
  node.mode = fileMode;
1270
1514
  node.ino = this.nextInode++;
@@ -1404,16 +1648,26 @@ export class MemoryFileSystem {
1404
1648
  allocation.data.set(data);
1405
1649
  this.ledger.reserve(nameBytes, 2, syscall, name);
1406
1650
  try {
1407
- const now = Date.now();
1651
+ const now = fastWriteCachedNow;
1408
1652
  const fileMode = typeModes.file | mode;
1409
1653
  const view = capacity === length ? allocation.data : undefined;
1410
- let node = sharedFileNodePool.pop() ?? cache.files.pop();
1654
+ let node;
1655
+ if (sharedFileNodePoolLen > 0) {
1656
+ node = sharedFileNodePool[--sharedFileNodePoolLen];
1657
+ sharedFileNodePool[sharedFileNodePoolLen] = DUMMY_POOL_FILE_NODE;
1658
+ }
1659
+ else {
1660
+ node = cache.files.pop();
1661
+ }
1411
1662
  if (node) {
1412
1663
  node.mode = fileMode;
1413
1664
  node.ino = this.nextInode++;
1414
- node.nlink = 1;
1415
- node.references = 0;
1416
- node.revision = 0;
1665
+ if (node.nlink !== 1)
1666
+ node.nlink = 1;
1667
+ if (node.references !== 0)
1668
+ node.references = 0;
1669
+ if (node.revision !== 0)
1670
+ node.revision = 0;
1417
1671
  node.atimeMs = now;
1418
1672
  node.mtimeMs = now;
1419
1673
  node.ctimeMs = now;
@@ -1991,7 +2245,7 @@ export class MemoryFileSystem {
1991
2245
  return Promise.reject(error);
1992
2246
  }
1993
2247
  }
1994
- removeLocation(location, path, syscall, recursive) {
2248
+ removeLocation(location, path, syscall, recursive, now = Date.now()) {
1995
2249
  const node = location.node;
1996
2250
  if (this.terminalDot(path))
1997
2251
  this.fail("EINVAL", syscall, path);
@@ -2003,12 +2257,12 @@ export class MemoryFileSystem {
2003
2257
  const cache = memoryCaches.get(this.ledger);
2004
2258
  cache.clearWrites();
2005
2259
  const removed = sharedRemovedScratch;
2006
- removed.length = 0;
2007
- removed.push(node);
2260
+ sharedRemovedLen = 1;
2261
+ removed[0] = node;
2008
2262
  sharedRemovedKeyChars = 0;
2009
2263
  sharedRemovedKeyEntries = 0;
2010
2264
  try {
2011
- for (let index = 0; index < removed.length; index++) {
2265
+ for (let index = 0; index < sharedRemovedLen; index++) {
2012
2266
  const entry = removed[index];
2013
2267
  if (entry.type === "directory" && entry.entries.size > 0) {
2014
2268
  this.permission(entry, 7, syscall, path);
@@ -2017,9 +2271,10 @@ export class MemoryFileSystem {
2017
2271
  }
2018
2272
  location.parent.entries.delete(location.name);
2019
2273
  this.ledger.release((location.name.length + sharedRemovedKeyChars) * 2, 1 + sharedRemovedKeyEntries);
2020
- const now = Date.now();
2021
- for (let i = 0; i < removed.length; i++) {
2274
+ const totalRemoved = sharedRemovedLen;
2275
+ for (let i = 0; i < totalRemoved; i++) {
2022
2276
  const entry = removed[i];
2277
+ removed[i] = DUMMY_POOL_FILE_NODE;
2023
2278
  if (entry.type === "directory") {
2024
2279
  entry.entries.clear();
2025
2280
  }
@@ -2032,9 +2287,11 @@ export class MemoryFileSystem {
2032
2287
  }
2033
2288
  }
2034
2289
  finally {
2035
- removed.length = 0;
2290
+ for (let i = 0; i < sharedRemovedLen; i++)
2291
+ removed[i] = DUMMY_POOL_FILE_NODE;
2292
+ sharedRemovedLen = 0;
2036
2293
  }
2037
- this.changed(location.parent);
2294
+ this.changed(location.parent, now);
2038
2295
  }
2039
2296
  unlink(path, options = {}) {
2040
2297
  // Do not allow an untyped recursive option to widen final-entry removal.
@@ -2490,24 +2747,46 @@ export class MemoryRedirectHandle {
2490
2747
  this.position = 0;
2491
2748
  this.closed = false;
2492
2749
  }
2493
- writeSync(chunk, signal) {
2750
+ reset(fs, path, node, append) {
2751
+ this.fs = fs;
2752
+ this.path = path;
2753
+ this.node = node;
2754
+ this.append = append;
2755
+ this.position = 0;
2756
+ this.closed = false;
2757
+ }
2758
+ writeRangeSync(src, len, signal) {
2494
2759
  signal?.throwIfAborted();
2495
2760
  if (this.closed)
2496
2761
  throw new FsError("EBADF", { syscall: "write", path: this.path });
2497
- if (chunk.byteLength === 0)
2762
+ if (len === 0)
2498
2763
  return;
2499
2764
  const pos = this.append ? this.node.byteLength : this.position;
2500
- this.fs.writeAt(this.node, chunk, pos, "write", this.path);
2765
+ const now = Date.now === defaultDateNow ? fastWriteCachedNow : Date.now();
2766
+ this.fs.writeAtRange(this.node, src, len, pos, "write", this.path, now);
2501
2767
  if (!this.append)
2502
- this.position = pos + chunk.byteLength;
2768
+ this.position = pos + len;
2769
+ }
2770
+ writeSync(chunk, signal) {
2771
+ this.writeRangeSync(chunk, chunk.byteLength, signal);
2503
2772
  }
2504
2773
  close() {
2505
2774
  if (this.closed)
2506
2775
  return;
2507
2776
  this.closed = true;
2508
- this.fs.releaseReference(this.node, this.path);
2777
+ const fs = this.fs;
2778
+ const node = this.node;
2779
+ const path = this.path;
2780
+ this.fs = undefined;
2781
+ this.node = DUMMY_POOL_FILE_NODE;
2782
+ this.path = "";
2783
+ fs.releaseReference(node, path);
2784
+ if (redirectHandlePool.handle === undefined) {
2785
+ redirectHandlePool.handle = this;
2786
+ }
2509
2787
  }
2510
2788
  }
2789
+ const redirectHandlePool = { handle: undefined };
2511
2790
  export function tryOpenMemoryRedirectHandleSync(filesystem, path, append, mode, signal) {
2512
2791
  const mem = filesystem;
2513
2792
  const owner = ownedStores.get(mem);
@@ -2517,38 +2796,118 @@ export function tryOpenMemoryRedirectHandleSync(filesystem, path, append, mode,
2517
2796
  activeConditionalMutations.has(mem.identityScope) ||
2518
2797
  !isStockMemoryMethods(mem, openRedirectFastMethodNames, false) ||
2519
2798
  !isCleanAbsolutePath(path) ||
2799
+ path === "/" ||
2520
2800
  path === "/dev" ||
2521
2801
  path.startsWith("/dev/")) {
2522
2802
  return undefined;
2523
2803
  }
2524
2804
  signal?.throwIfAborted();
2525
- const validMode = mem.mode(mode, 0o666, "open", path);
2526
- const location = mem.resolve(path, "open", {
2527
- followFinal: true,
2528
- allowMissing: true,
2529
- });
2530
- let node = location.node;
2531
- if (node) {
2532
- if (node.type !== "file")
2533
- mem.fail("EISDIR", "open", path);
2534
- mem.permission(node, 2, "open", path);
2535
- }
2536
- else {
2537
- mem.permission(location.parent, 3, "open", path);
2805
+ const validMode = (mode & 0o777) === mode
2806
+ ? mode
2807
+ : mem.mode(mode, 0o666, "open", path);
2808
+ const memInternal = mem;
2809
+ let parent = memInternal.root;
2810
+ let start = 1;
2811
+ let name = "";
2812
+ while (true) {
2813
+ memInternal.permission(parent, 1, "open", path);
2814
+ const slash = path.indexOf("/", start);
2815
+ if (slash === -1) {
2816
+ name = path.slice(start);
2817
+ if (exceedsComponentByteLimit(name))
2818
+ memInternal.fail("ENAMETOOLONG", "open", path);
2819
+ break;
2820
+ }
2821
+ const seg = path.slice(start, slash);
2822
+ if (exceedsComponentByteLimit(seg))
2823
+ memInternal.fail("ENAMETOOLONG", "open", path);
2824
+ const next = parent.entries.get(seg);
2825
+ if (!next)
2826
+ memInternal.fail("ENOENT", "open", path);
2827
+ if (next.type !== "directory")
2828
+ memInternal.fail("ENOTDIR", "open", path);
2829
+ parent = next;
2830
+ start = slash + 1;
2538
2831
  }
2539
- const ledger = mem.ledger;
2540
- ledger.reserve(path.length * 2, 1, "open", path);
2541
- try {
2542
- node ??= mem.openWrite(path, {}, "open", { location, mode: validMode, append: false });
2832
+ const existing = parent.entries.get(name);
2833
+ const ledger = memInternal.ledger;
2834
+ let node;
2835
+ if (existing !== undefined) {
2836
+ if (existing.type !== "file")
2837
+ memInternal.fail("EISDIR", "open", path);
2838
+ memInternal.permission(existing, 2, "open", path);
2839
+ ledger.reserve(path.length * 2, 1, "open", path);
2840
+ node = existing;
2543
2841
  if (!append) {
2544
- mem.resizeNode(node, 0, "open", path);
2842
+ try {
2843
+ memInternal.resizeNode(node, 0, "open", path);
2844
+ }
2845
+ catch (error) {
2846
+ ledger.release(path.length * 2, 1);
2847
+ throw error;
2848
+ }
2849
+ }
2850
+ node.references++;
2851
+ }
2852
+ else {
2853
+ memInternal.permission(parent, 3, "open", path);
2854
+ const refBytes = path.length * 2;
2855
+ const nameBytes = name.length * 2;
2856
+ ledger.reserve(refBytes, 1, "open", path);
2857
+ try {
2858
+ ledger.fileSize(0, "open", path);
2859
+ ledger.reserve(nameBytes, 2, "open", path);
2860
+ }
2861
+ catch (error) {
2862
+ ledger.release(refBytes, 1);
2863
+ throw error;
2864
+ }
2865
+ const cache = memoryCaches.get(ledger);
2866
+ cache.clearWrites();
2867
+ const now = Date.now === defaultDateNow ? ((++fastWriteNowTick & 63) === 0 ? (fastWriteCachedNow = Date.now()) : fastWriteCachedNow) : Date.now();
2868
+ const fileMode = typeModes.file | validMode;
2869
+ let newNode;
2870
+ if (sharedFileNodePoolLen > 0) {
2871
+ newNode = sharedFileNodePool[--sharedFileNodePoolLen];
2872
+ sharedFileNodePool[sharedFileNodePoolLen] = DUMMY_POOL_FILE_NODE;
2545
2873
  }
2874
+ else {
2875
+ newNode = cache.files.pop();
2876
+ }
2877
+ if (newNode) {
2878
+ newNode.mode = fileMode;
2879
+ newNode.ino = memInternal.nextInode++;
2880
+ newNode.nlink = 1;
2881
+ newNode.references = 1;
2882
+ newNode.revision = 0;
2883
+ newNode.atimeMs = now;
2884
+ newNode.mtimeMs = now;
2885
+ newNode.ctimeMs = now;
2886
+ newNode.birthtimeMs = now;
2887
+ newNode.byteLength = 0;
2888
+ newNode.view = EMPTY_ALLOC_BYTES;
2889
+ newNode.allocation = DUMMY_POOL_ALLOCATION;
2890
+ }
2891
+ else {
2892
+ newNode = new MemoryFileNode(fileMode, memInternal.nextInode++, now, 0, DUMMY_POOL_ALLOCATION, EMPTY_ALLOC_BYTES);
2893
+ newNode.references = 1;
2894
+ }
2895
+ const prevNlink = parent.cachedNlinkRev === parent.revision ? parent.cachedNlink : (parent.entries.size === 0 ? 2 : undefined);
2896
+ parent.entries.set(name, newNode);
2897
+ parent.revision = parent.revision < 1073741823 ? (parent.revision + 1) | 0 : Math.min(Number.MAX_SAFE_INTEGER + 1, parent.revision + 1);
2898
+ parent.mtimeMs = parent.ctimeMs = now;
2899
+ if (prevNlink !== undefined) {
2900
+ parent.cachedNlink = prevNlink;
2901
+ parent.cachedNlinkRev = parent.revision;
2902
+ }
2903
+ node = newNode;
2546
2904
  }
2547
- catch (error) {
2548
- ledger.release(path.length * 2, 1);
2549
- throw error;
2905
+ const handle = redirectHandlePool.handle;
2906
+ if (handle !== undefined) {
2907
+ redirectHandlePool.handle = undefined;
2908
+ handle.reset(mem, path, node, append);
2909
+ return handle;
2550
2910
  }
2551
- node.references++;
2552
2911
  return new MemoryRedirectHandle(mem, path, node, append);
2553
2912
  }
2554
2913
  export function utf8ByteLength(value) {
@@ -2686,7 +3045,8 @@ export function tryReadMemoryFileViewSync(filesystem, path, maxBytes, signal) {
2686
3045
  }
2687
3046
  export function tryGetMemoryDirectoryEntryNamesSync(filesystem, path) {
2688
3047
  const mem = filesystem;
2689
- if (!ownedStores.has(mem) || mem.symlinkCount !== 0 || !isStockMemoryMethods(mem, readFileFastMethodNames, false))
3048
+ const owner = ownedStores.get(mem);
3049
+ if (!owner || mem.symlinkCount !== 0 || !isStockMemoryMethods(mem, readFileFastMethodNames, false))
2690
3050
  return undefined;
2691
3051
  const root = mem.root;
2692
3052
  if (path === "/") {
@@ -2696,6 +3056,16 @@ export function tryGetMemoryDirectoryEntryNamesSync(filesystem, path) {
2696
3056
  }
2697
3057
  if (!isCleanAbsolutePath(path) || path === "/dev" || path.startsWith("/dev/"))
2698
3058
  return undefined;
3059
+ const cache = memoryCaches.get(owner.ledger);
3060
+ const fastDir = cache?.lastFastDirNode;
3061
+ const fastPrefix = cache?.lastFastDirPrefix;
3062
+ if (fastDir !== undefined &&
3063
+ fastDir.nlink !== 0 &&
3064
+ ((fastDir.mode >> 6) & 4) === 4 &&
3065
+ fastPrefix !== undefined &&
3066
+ (fastPrefix === path || (fastPrefix.length === path.length + 1 && fastPrefix.charCodeAt(path.length) === 47 && fastPrefix.startsWith(path)))) {
3067
+ return fastDir.entries;
3068
+ }
2699
3069
  let current = root;
2700
3070
  let start = 1;
2701
3071
  while (true) {
@@ -2746,16 +3116,34 @@ export function tryMkdirMemorySync(filesystem, path, recursive, mode, signal) {
2746
3116
  signal?.throwIfAborted();
2747
3117
  const validMode = mem.mode(mode, 0o777, "mkdir", path);
2748
3118
  if (recursive) {
2749
- const node = mem.resolve(path, "mkdir", { createDirectories: validMode }).node;
2750
- if (node.type !== "directory")
2751
- mem.fail("EEXIST", "mkdir", path);
3119
+ let current = owner.root;
3120
+ let start = 1;
3121
+ while (true) {
3122
+ mem.permission(current, 1, "mkdir", path);
3123
+ const slash = path.indexOf("/", start);
3124
+ const seg = slash === -1 ? path.slice(start) : path.slice(start, slash);
3125
+ if (exceedsComponentByteLimit(seg))
3126
+ mem.fail("ENAMETOOLONG", "mkdir", path);
3127
+ let next = current.entries.get(seg);
3128
+ if (!next) {
3129
+ mem.permission(current, 3, "mkdir", path);
3130
+ next = mem.addDirectoryNode(current, seg, validMode, "mkdir", path, fastWriteCachedNow);
3131
+ }
3132
+ else if (next.type !== "directory") {
3133
+ mem.fail(slash === -1 ? "EEXIST" : "ENOTDIR", "mkdir", path);
3134
+ }
3135
+ if (slash === -1)
3136
+ break;
3137
+ current = next;
3138
+ start = slash + 1;
3139
+ }
2752
3140
  return true;
2753
3141
  }
2754
- const location = mem.resolve(path, "mkdir", { allowMissing: true, followFinal: false });
3142
+ const location = mem.resolve(path, "mkdir", sharedAllowMissingNoFollowOptions);
2755
3143
  if (location.node)
2756
3144
  mem.fail("EEXIST", "mkdir", path);
2757
3145
  mem.permission(location.parent, 3, "mkdir", path);
2758
- mem.addDirectoryNode(location.parent, location.name, validMode, "mkdir", path);
3146
+ mem.addDirectoryNode(location.parent, location.name, validMode, "mkdir", path, fastWriteCachedNow);
2759
3147
  return true;
2760
3148
  }
2761
3149
  export function tryRmRfMemorySync(filesystem, path, signal) {
@@ -2772,16 +3160,46 @@ export function tryRmRfMemorySync(filesystem, path, signal) {
2772
3160
  return false;
2773
3161
  }
2774
3162
  signal?.throwIfAborted();
2775
- let location;
3163
+ let current = owner.root;
3164
+ let start = 1;
3165
+ let targetNode;
3166
+ let targetName = "";
3167
+ while (true) {
3168
+ mem.permission(current, 1, "rm", path);
3169
+ const slash = path.indexOf("/", start);
3170
+ if (slash === -1) {
3171
+ targetName = path.slice(start);
3172
+ if (exceedsComponentByteLimit(targetName))
3173
+ mem.fail("ENAMETOOLONG", "rm", path);
3174
+ targetNode = current.entries.get(targetName);
3175
+ if (!targetNode)
3176
+ return true;
3177
+ break;
3178
+ }
3179
+ const seg = path.slice(start, slash);
3180
+ if (exceedsComponentByteLimit(seg))
3181
+ mem.fail("ENAMETOOLONG", "rm", path);
3182
+ const next = current.entries.get(seg);
3183
+ if (!next)
3184
+ return true;
3185
+ if (next.type !== "directory")
3186
+ mem.fail("ENOTDIR", "rm", path);
3187
+ current = next;
3188
+ start = slash + 1;
3189
+ }
3190
+ sharedFastLocation.node = targetNode;
3191
+ sharedFastLocation.parent = current;
3192
+ sharedFastLocation.name = targetName;
3193
+ sharedFastLocation.path = path;
2776
3194
  try {
2777
- location = mem.resolve(path, "rm", noFollowResolveOptions);
3195
+ mem.removeLocation(sharedFastLocation, path, "rm", true, fastWriteCachedNow);
2778
3196
  }
2779
- catch (error) {
2780
- if (error instanceof FsError && error.code === "ENOENT")
2781
- return true;
2782
- throw error;
3197
+ finally {
3198
+ sharedFastLocation.node = undefined;
3199
+ sharedFastLocation.parent = DUMMY_POOL_DIR_NODE;
3200
+ sharedFastLocation.name = "";
3201
+ sharedFastLocation.path = "";
2783
3202
  }
2784
- mem.removeLocation(location, path, "rm", true);
2785
3203
  return true;
2786
3204
  }
2787
3205
  export function tryWriteMemoryFileInDirSync(filesystem, dirPrefix, name, data, append, mode, signal) {
@@ -68,13 +68,18 @@ export class MemoryLedger {
68
68
  }
69
69
  export class MemoryAllocation {
70
70
  constructor(data, ledger) {
71
+ this.references = 1;
71
72
  this.data = data;
72
73
  this.ledger = ledger;
73
74
  }
74
75
  retain() {
76
+ if (this.data.byteLength === 0)
77
+ return;
75
78
  this.references++;
76
79
  }
77
80
  release() {
81
+ if (this.data.byteLength === 0)
82
+ return;
78
83
  if (this.references <= 0)
79
84
  throw new Error("Memory allocation already released");
80
85
  if (--this.references === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.730",
3
+ "version": "0.1.731",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",