Skip to content

Commit 8d500fc

Browse files
improve FileSystemAccessApiFsClient.rename function
1 parent 9b45b16 commit 8d500fc

3 files changed

Lines changed: 38 additions & 14 deletions

File tree

src/clients/fs/FileSystemAccessApiFsClient.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,7 @@ export class FileSystemAccessApiFsClient implements FsClient {
250250

251251
const oldFilepathStat = await this.stat(oldPath)
252252
if (oldFilepathStat.isFile()) {
253-
const data = await this.readFile(oldPath)
254-
await this.writeFile(newPath, data)
255-
await this.rm(oldPath)
253+
await this.renameFile(oldPath, newPath)
256254
} else if (oldFilepathStat.isDirectory()) {
257255
await this.mkdir(newPath)
258256
const sourceFolder = await this.getDirectoryByPath(oldPath)
@@ -264,6 +262,40 @@ export class FileSystemAccessApiFsClient implements FsClient {
264262
}
265263
}
266264

265+
private async renameFile(oldPath: string, newPath: string): Promise<void> {
266+
const { folderPath: oldFolder, leafSegment: oldName } = this.getFolderPathAndLeafSegment(oldPath)
267+
const { folderPath: newFolder, leafSegment: newName } = this.getFolderPathAndLeafSegment(newPath)
268+
269+
const oldDir = await this.getDirectoryByPath(oldFolder)
270+
const fileHandle = await this.getEntry<'file'>(oldDir, oldName, 'file')
271+
if (!fileHandle) {
272+
throw new ENOENT(oldPath)
273+
}
274+
275+
// Strategy 1: Native move() — zero-copy rename, supported in Chrome and Safari OPFS.
276+
// Always pass (directory, newName) form — Safari doesn't support the move(newName) shorthand.
277+
if (typeof fileHandle.move === 'function') {
278+
const newDir = oldFolder === newFolder ? oldDir : await this.getDirectoryByPath(newFolder)
279+
await fileHandle.move(newDir, newName)
280+
return
281+
}
282+
283+
// Strategy 2: Streaming copy — read in chunks, write via stream. Never loads entire file.
284+
const CHUNK_SIZE = 1024 * 1024
285+
const file = await fileHandle.getFile()
286+
const writable = await this.createWritableStream(newPath)
287+
let offset = 0
288+
while (offset < file.size) {
289+
const end = Math.min(offset + CHUNK_SIZE, file.size)
290+
const blob = file.slice(offset, end)
291+
const chunk = new Uint8Array(await blob.arrayBuffer())
292+
await writable.write(chunk)
293+
offset = end
294+
}
295+
await writable.close()
296+
await this.rm(oldPath)
297+
}
298+
267299
/**
268300
* Symlinks are not supported in the current implementation.
269301
* @throws Error: symlinks are not supported.

src/commands/fetch.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -439,17 +439,7 @@ async function processPackfileStreaming({
439439
onProgress,
440440
})
441441
await fs.write(fullpath.replace(/\.pack$/, '.idx'), await idx.toBuffer())
442-
// Stream-copy pack to final path to avoid loading entire file (OPFS rename reads whole file).
443-
const finalWritable = await fs.createWritableStream(fullpath)
444-
if (finalWritable) {
445-
for await (const chunk of fs.readFileChunks(tempPackPath, CHUNK_SIZE)) {
446-
await finalWritable.write(chunk)
447-
}
448-
await finalWritable.close()
449-
await fs.rm(tempPackPath)
450-
} else {
451-
await fs.rename(tempPackPath, fullpath)
452-
}
442+
await fs.rename(tempPackPath, fullpath)
453443
} else {
454444
// readFileSlice not available: read entire file back for index creation (1x memory).
455445
const packfileData = (await fs.read(tempPackPath)) as Buffer

src/types/FileSystemAccessApi.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ declare class FileSystemWritableFileStream extends WritableStream {
3535
declare interface FileSystemFileHandle extends FileSystemHandle {
3636
createWritable(options?: FileSystemCreateWritableOptions): Promise<FileSystemWritableFileStream>
3737
createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>
38+
/** Rename or move a file. Not available in all browsers — feature-detect before calling. */
39+
move?(target: string | FileSystemDirectoryHandle, newName?: string): Promise<void>
3840
}

0 commit comments

Comments
 (0)