5 Commits
Author SHA1 Message Date
sugoidogo bbd975feb6 remove close/abort override
Release / release (push) Successful in 23s
2026-02-15 20:22:23 -08:00
sugoidogo 1bd153de5b use single fd for all operations 2026-02-15 19:57:19 -08:00
sugoidogo a1838bc153 fix missing file close
Release / release (push) Successful in 23s
2026-02-15 18:48:28 -08:00
sugoidogo 65e8e64079 cleaner file implementation
Release / release (push) Successful in 23s
2026-02-09 13:41:04 -08:00
sugoidogo 579c4bf131 switch to importable-types-web
Release / release (push) Successful in 23s
2026-02-09 07:09:30 -08:00
8 changed files with 60 additions and 43 deletions
+5 -12
View File
@@ -11,26 +11,19 @@
"mime": "^4.1.0" "mime": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@sugoidogo/web-filesystem-types": "^2.0.0", "@sugoidogo/importable-types-web": "^1.0.0",
"@types/node": "^25.0.9", "@types/node": "^25.0.9",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
"node_modules/@sugoidogo/node-web-globals": { "node_modules/@sugoidogo/importable-types-web": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/%40sugoidogo%2Fnode-web-globals/-/1.0.0/node-web-globals-1.0.0.tgz", "resolved": "https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/%40sugoidogo%2Fimportable-types-web/-/1.0.0/importable-types-web-1.0.0.tgz",
"integrity": "sha512-mE2yB2h2Vg8I+0zkcFHLUZgH+dbpb03e5Zwn+cDeXFRnj/nb/mLCIxXc9OeKUSH06PYyOGtH76XKKwGzey9TnA==", "integrity": "sha512-MqWkQ/WsllO7GiQjqWiFB3ZzsDwJwGPXIWkyd0E24d3CMcI0FKGgsOn6G/RVjCO9k2d/aRlzxqVmyuPAYOZ8tQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@sugoidogo/web-filesystem-types": {
"version": "2.0.0",
"resolved": "https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/%40sugoidogo%2Fweb-filesystem-types/-/2.0.0/web-filesystem-types-2.0.0.tgz",
"integrity": "sha512-ET5F+WlqNZnskgbSACG1flLAKftJBehLPiQgRtUotuh2+pSqIGrHHQT5KC2v6b5/3LM47K1Zjm8GFcJpmXEjmw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@sugoidogo/node-web-globals": "^1.0.0" "undici-types": "^7.16.0"
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
+1 -1
View File
@@ -16,7 +16,7 @@
"mime": "^4.1.0" "mime": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@sugoidogo/web-filesystem-types": "^2.0.0", "@sugoidogo/importable-types-web": "^1.0.0",
"@types/node": "^25.0.9", "@types/node": "^25.0.9",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
+9 -6
View File
@@ -1,14 +1,17 @@
import NodeFileSystemDirectoryHandle from '../dist/NodeFileSystemDirectoryHandle.js' import NodeFileSystemDirectoryHandle from '../src/NodeFileSystemDirectoryHandle.ts'
const textText="Hello World!"
const cwd_directory_handle = new NodeFileSystemDirectoryHandle('.') const cwd_directory_handle = new NodeFileSystemDirectoryHandle('.')
const test_directory_handle = await cwd_directory_handle.getDirectoryHandle('test', { 'create': true }) const test_directory_handle = await cwd_directory_handle.getDirectoryHandle('test', { 'create': true })
const file_handle = await test_directory_handle.getFileHandle('test.txt', { 'create': true }) for (let i = 0; i < 10000; i++) {
const file_handle = await test_directory_handle.getFileHandle(i+'.txt', { 'create': true })
const writable = await file_handle.createWritable() const writable = await file_handle.createWritable()
writable.write('Hello World!') await writable.write('Hello World!')
writable.close() await writable.close()
const file = await file_handle.getFile() const file = await file_handle.getFile()
console.log(file.type) const text = await file.text()
console.log(await file.text()) if(text!==textText) throw new Error("failed to read back written data")
}
const result = await cwd_directory_handle.getFileHandle('test/test.txt').catch(error => 'blocked path traversal') const result = await cwd_directory_handle.getFileHandle('test/test.txt').catch(error => 'blocked path traversal')
if (typeof result === 'string') console.log(result) if (typeof result === 'string') console.log(result)
else console.error('path traversal not blocked') else console.error('path traversal not blocked')
+8
View File
@@ -3,6 +3,14 @@ import * as asyncfs from 'node:fs/promises';
import * as path from 'node:path'; import * as path from 'node:path';
import NodeFileSystemHandle from './NodeFileSystemHandle.ts'; import NodeFileSystemHandle from './NodeFileSystemHandle.ts';
import NodeFileSystemFileHandle from './NodeFileSystemFileHandle.ts'; import NodeFileSystemFileHandle from './NodeFileSystemFileHandle.ts';
import type {
FileSystemDirectoryHandle,
FileSystemGetDirectoryOptions,
FileSystemDirectoryHandleAsyncIterator,
FileSystemGetFileOptions,
FileSystemRemoveOptions,
FileSystemHandle
} from '@sugoidogo/importable-types-web'
export default class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle implements FileSystemDirectoryHandle { export default class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle implements FileSystemDirectoryHandle {
declare kind: "directory"; declare kind: "directory";
+10 -6
View File
@@ -4,6 +4,13 @@ import * as path from 'node:path';
import NodeFileSystemHandle from './NodeFileSystemHandle.ts'; import NodeFileSystemHandle from './NodeFileSystemHandle.ts';
import NodeFileSystemWritableFileStream from './NodeFileSystemWriteableFileStream.ts'; import NodeFileSystemWritableFileStream from './NodeFileSystemWriteableFileStream.ts';
import mime from 'mime' import mime from 'mime'
import type {
FileSystemFileHandle,
FileSystemGetFileOptions,
FileSystemCreateWritableOptions,
FileSystemWritableFileStream,
File as WebFile
} from '@sugoidogo/importable-types-web'
export default class NodeFileSystemFileHandle extends NodeFileSystemHandle implements FileSystemFileHandle { export default class NodeFileSystemFileHandle extends NodeFileSystemHandle implements FileSystemFileHandle {
declare kind: "file"; declare kind: "file";
@@ -17,13 +24,10 @@ export default class NodeFileSystemFileHandle extends NodeFileSystemHandle imple
async createWritable(options: FileSystemCreateWritableOptions = {}): Promise<FileSystemWritableFileStream> { async createWritable(options: FileSystemCreateWritableOptions = {}): Promise<FileSystemWritableFileStream> {
return new NodeFileSystemWritableFileStream(this.path,options) return new NodeFileSystemWritableFileStream(this.path,options)
} }
async getFile(): Promise<File> { async getFile(): Promise<WebFile> {
const stats = await asyncfs.stat(this.path) const stats = await asyncfs.stat(this.path)
const type = mime.getType(this.name.split('.').pop()) const type = mime.getType(this.name.split('.').pop())
return asyncfs.readFile(this.path).then(buffer => Object.assign(new Blob([buffer],{type}), { const buffer = await asyncfs.readFile(this.path)
lastModified: stats.mtimeMs, return new File([buffer], this.path, { "lastModified": stats.mtimeMs, "type": type }) as any //TODO Node File and Web File have some incompatibilities
name: this.path,
webkitRelativePath: this.path
}))
} }
} }
+4
View File
@@ -1,4 +1,8 @@
import * as path from 'node:path' import * as path from 'node:path'
import type {
FileSystemHandle,
FileSystemHandleKind
} from '@sugoidogo/importable-types-web'
export default class NodeFileSystemHandle implements FileSystemHandle { export default class NodeFileSystemHandle implements FileSystemHandle {
kind: FileSystemHandleKind; kind: FileSystemHandleKind;
+19 -10
View File
@@ -1,38 +1,47 @@
import * as fs from 'node:fs' import * as fs from 'node:fs'
import * as asyncfs from 'node:fs/promises'
import * as path from 'node:path' import * as path from 'node:path'
import { Writable } from 'node:stream' import { Writable } from 'node:stream'
import type {
FileSystemWritableFileStream,
FileSystemCreateWritableOptions,
FileSystemWriteChunkType,
WriteParams
} from '@sugoidogo/importable-types-web'
export default class NodeFileSystemWritableFileStream extends WritableStream implements FileSystemWritableFileStream { export default class NodeFileSystemWritableFileStream extends WritableStream implements FileSystemWritableFileStream {
#seek_position = 0 #seek_position = 0
#name: string #fd: number
constructor(name: string, options: FileSystemCreateWritableOptions = {}) { constructor(name: string, options: FileSystemCreateWritableOptions = {}) {
name = path.normalize(name) name = path.normalize(name)
super(Writable.toWeb(fs.createWriteStream(name))) let flags = 'w'
this.#name = name if (!options.keepExistingData) flags = 'r+'
if(!options.keepExistingData) this.truncate(0) const fd = fs.openSync(name, flags)
super(Writable.toWeb(fs.createWriteStream(name, { "fd": fd })))
this.#fd = fd
} }
async seek(position: number): Promise<void> { async seek(position: number): Promise<void> {
if (this.locked) throw new Error("stream is locked")
this.#seek_position = position this.#seek_position = position
} }
truncate(size: number): Promise<void> { async truncate(size: number): Promise<void> {
return asyncfs.truncate(this.#name, size) if (this.locked) throw new Error("stream is locked")
return fs.ftruncateSync(this.#fd, size)
} }
async write(wdata: FileSystemWriteChunkType): Promise<void> { async write(wdata: FileSystemWriteChunkType): Promise<void> {
if (this.locked) throw new Error("stream is locked")
if (wdata instanceof Blob) wdata = await wdata.bytes() if (wdata instanceof Blob) wdata = await wdata.bytes()
if (typeof wdata === 'string') wdata = new TextEncoder().encode(wdata) if (typeof wdata === 'string') wdata = new TextEncoder().encode(wdata)
let wsize: number = null let wsize: number = null
let pseek_position = this.#seek_position let pseek_position = this.#seek_position
if ((wdata as WriteParams).type) { if ((wdata as WriteParams).type) {
const { type, position, size, data } = (wdata as WriteParams) const { type, position, size, data } = (wdata as WriteParams)
if (type === 'truncate') return asyncfs.truncate(this.#name, size) if (type === 'truncate') return this.truncate(size)
if (position) this.#seek_position = position if (position) this.#seek_position = position
if (type === 'seek') return if (type === 'seek') return
if (size) wsize = size if (size) wsize = size
wdata = data wdata = data
} }
const file = await asyncfs.open(this.#name,'r+') fs.writeSync(this.#fd, (wdata as Uint8Array), null, wsize, this.#seek_position)
await file.write((wdata as Uint8Array), null, wsize, this.#seek_position)
this.#seek_position = pseek_position this.#seek_position = pseek_position
} }
} }
-4
View File
@@ -3,10 +3,6 @@
"target": "es2022", "target": "es2022",
"module": "es2022", "module": "es2022",
"lib": ["ES2022"], "lib": ["ES2022"],
"types": [
"node",
"@sugoidogo/web-filesystem-types"
],
"moduleResolution": "node", "moduleResolution": "node",
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,