11 Commits
Author SHA1 Message Date
sugoidogo 40424dde59 fix handle.kind
Release / release (push) Successful in 23s
2026-05-01 11:03:08 -07:00
sugoidogo f361c6b931 fix iterators, use tsx for test
Release / release (push) Successful in 24s
2026-05-01 10:28:22 -07:00
sugoidogo 00e027ef71 add deno-compatible NonBufferedFile
Release / release (push) Successful in 23s
2026-02-16 17:28:28 -08:00
sugoidogo 8bc2c7d70c refactor
Release / release (push) Successful in 24s
2026-02-16 15:57:22 -08:00
sugoidogo e99fc35e25 explicity truncate files when not keeping existing data
Release / release (push) Successful in 23s
2026-02-15 20:51:17 -08:00
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
sugoidogo 1cc6906cc9 switch from @types/web to web-filesystem-types
Release / release (push) Successful in 22s
2026-01-20 07:46:16 -08:00
8 changed files with 755 additions and 115 deletions
+560 -12
View File
@@ -1,35 +1,553 @@
{
"name": "node-file-system-adapter",
"name": "@sugoidogo/node-file-system-adapter",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "node-file-system-adapter",
"name": "@sugoidogo/node-file-system-adapter",
"version": "0.0.0",
"dependencies": {
"mime": "^4.1.0"
},
"devDependencies": {
"@types/node": "^25.0.3",
"@types/web": "^0.0.310",
"@sugoidogo/importable-types-web": "^1.0.0",
"@types/node": "^25.0.9",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@sugoidogo/importable-types-web": {
"version": "1.0.0",
"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-MqWkQ/WsllO7GiQjqWiFB3ZzsDwJwGPXIWkyd0E24d3CMcI0FKGgsOn6G/RVjCO9k2d/aRlzxqVmyuPAYOZ8tQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"undici-types": "^7.16.0"
}
},
"node_modules/@types/node": {
"version": "25.0.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
"version": "25.0.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz",
"integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/web": {
"version": "0.0.310",
"resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.310.tgz",
"integrity": "sha512-XN++V7UZGEeRg8MXJBkzMqmcAWoCRN1UV/zpQxg9YnJQ9pIPSuzEG7oidC+nXi/J8pmfb3UgFv+TjEDzMiNnRg==",
"node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
"license": "Apache-2.0"
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.7",
"@esbuild/android-arm": "0.27.7",
"@esbuild/android-arm64": "0.27.7",
"@esbuild/android-x64": "0.27.7",
"@esbuild/darwin-arm64": "0.27.7",
"@esbuild/darwin-x64": "0.27.7",
"@esbuild/freebsd-arm64": "0.27.7",
"@esbuild/freebsd-x64": "0.27.7",
"@esbuild/linux-arm": "0.27.7",
"@esbuild/linux-arm64": "0.27.7",
"@esbuild/linux-ia32": "0.27.7",
"@esbuild/linux-loong64": "0.27.7",
"@esbuild/linux-mips64el": "0.27.7",
"@esbuild/linux-ppc64": "0.27.7",
"@esbuild/linux-riscv64": "0.27.7",
"@esbuild/linux-s390x": "0.27.7",
"@esbuild/linux-x64": "0.27.7",
"@esbuild/netbsd-arm64": "0.27.7",
"@esbuild/netbsd-x64": "0.27.7",
"@esbuild/openbsd-arm64": "0.27.7",
"@esbuild/openbsd-x64": "0.27.7",
"@esbuild/openharmony-arm64": "0.27.7",
"@esbuild/sunos-x64": "0.27.7",
"@esbuild/win32-arm64": "0.27.7",
"@esbuild/win32-ia32": "0.27.7",
"@esbuild/win32-x64": "0.27.7"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/mime": {
"version": "4.1.0",
@@ -46,6 +564,36 @@
"node": ">=16"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+5 -4
View File
@@ -1,5 +1,6 @@
{
"name": "@sugoidogo/node-file-system-adapter",
"version": "0.0.0",
"main": "dist/NodeFileSystemDirectoryHandle.js",
"files": [
"dist",
@@ -8,15 +9,15 @@
"type": "module",
"scripts": {
"prepack": "tsc",
"pretest": "tsc",
"test": "node scripts/test.ts"
"test": "tsx scripts/test.ts"
},
"dependencies": {
"mime": "^4.1.0"
},
"devDependencies": {
"@types/node": "^25.0.3",
"@types/web": "^0.0.310",
"@sugoidogo/importable-types-web": "^1.0.0",
"@types/node": "^25.0.9",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
+23 -9
View File
@@ -1,14 +1,28 @@
import NodeFileSystemDirectoryHandle from '../dist/NodeFileSystemDirectoryHandle.js'
import NodeFileSystemDirectoryHandle from '../src/NodeFileSystemDirectoryHandle.ts'
const textText="Hello World!"
const cwd_directory_handle = new NodeFileSystemDirectoryHandle('.')
const test_directory_handle=await cwd_directory_handle.getDirectoryHandle('test',{'create':true})
const file_handle = await test_directory_handle.getFileHandle('test.txt', { 'create': true })
const writable = await file_handle.createWritable()
writable.write('Hello World!')
writable.close()
const file = await file_handle.getFile()
console.log(file.type)
console.log(await file.text())
const test_directory_handle = await cwd_directory_handle.getDirectoryHandle('test', { 'create': true })
for (let i = 0; i < 10000; i++) {
const file_handle = await test_directory_handle.getFileHandle(i+'.txt', { 'create': true })
let writable = await file_handle.createWritable()
await writable.write(textText+textText)
await writable.close()
writable = await file_handle.createWritable()
await writable.write(textText)
await writable.close()
const file = await file_handle.getFile()
console.log(file.webkitRelativePath)
const text = await file.text()
if (text !== textText) throw new Error("failed to read back written data")
}
for await (const [name, handle] of cwd_directory_handle) {
console.log(`${name} is a ${handle.kind}`)
if (handle.kind !== "directory" && handle.kind !== "file") {
throw new Error("invalid handle kind")
}
}
console.log("iterable pass")
const result = await cwd_directory_handle.getFileHandle('test/test.txt').catch(error => 'blocked path traversal')
if (typeof result === 'string') console.log(result)
else console.error('path traversal not blocked')
+47 -37
View File
@@ -1,67 +1,77 @@
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 NodeFileSystemHandle from './NodeFileSystemHandle.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 {
declare kind: "directory";
constructor(fpath: string, options: FileSystemGetDirectoryOptions = {}) {
super()
fpath = path.normalize(fpath)
if (!fs.existsSync(fpath) && options.create) fs.mkdirSync(fpath)
if (!fs.lstatSync(fpath).isDirectory()) throw new Error('not a directory')
this.path = fpath
constructor(path: string, options: FileSystemGetDirectoryOptions & { origin?: NodeFileSystemDirectoryHandle } = {}) {
super(path, options)
this.kind="directory"
try {
const stats = fs.statSync(this.path)
if (!stats.isDirectory()) throw new Error("tried to open file as directory: " + this.path)
} catch (error) {
if (options.create) fs.mkdirSync(this.path)
else throw error
}
}
entries(): FileSystemDirectoryHandleAsyncIterator<[string, NodeFileSystemDirectoryHandle | NodeFileSystemFileHandle]> {
const entries: Array<[string, NodeFileSystemDirectoryHandle | NodeFileSystemFileHandle]> = []
fs.readdirSync(this.path).forEach(name => {
if (fs.lstatSync(name).isDirectory()) {
entries.push([name, new NodeFileSystemDirectoryHandle(path.join(this.path, name))])
async* entries(): FileSystemDirectoryHandleAsyncIterator<[string, NodeFileSystemDirectoryHandle | NodeFileSystemFileHandle]> {
for (const name of await asyncfs.readdir(this.path)) {
const entryPath = Path.join(this.path, name)
const stat = await asyncfs.lstat(entryPath)
if (stat.isDirectory()) {
yield [name, new NodeFileSystemDirectoryHandle(entryPath, { origin: this.origin || this })]
} else {
entries.push([name, new NodeFileSystemFileHandle(path.join(this.path, name))])
yield [name, new NodeFileSystemFileHandle(entryPath, { origin: this.origin || this })]
}
})
return entries[Symbol.asyncIterator]
}
}
keys(): FileSystemDirectoryHandleAsyncIterator<string> {
return fs.readdirSync(this.path)[Symbol.asyncIterator]
async* keys(): FileSystemDirectoryHandleAsyncIterator<string> {
for (const name of await asyncfs.readdir(this.path)) {
yield name
}
}
values(): FileSystemDirectoryHandleAsyncIterator<NodeFileSystemDirectoryHandle | NodeFileSystemFileHandle> {
const values: Array<NodeFileSystemDirectoryHandle | NodeFileSystemFileHandle> = [];
fs.readdirSync(this.path).forEach(name => {
if (fs.lstatSync(name).isDirectory()) {
values.push(new NodeFileSystemDirectoryHandle(path.join(this.path, name)))
async* values(): FileSystemDirectoryHandleAsyncIterator<NodeFileSystemDirectoryHandle | NodeFileSystemFileHandle> {
for (const name of await asyncfs.readdir(this.path)) {
const entryPath = Path.join(this.path, name)
const stat = await asyncfs.lstat(entryPath)
if (stat.isDirectory()) {
yield new NodeFileSystemDirectoryHandle(entryPath, { origin: this.origin || this })
} else {
values.push(new NodeFileSystemFileHandle(path.join(this.path, name)))
yield new NodeFileSystemFileHandle(entryPath, { origin: this.origin || this })
}
})
return values[Symbol.asyncIterator]
}
}
#getValidatedPath(name: string) {
name = path.normalize(name)
if (name.includes(path.sep)) throw new TypeError('Invalid Filename')
return path.join(this.path, name)
name = Path.normalize(name)
if (name.includes(Path.sep)) throw new TypeError('Invalid Filename')
return Path.join(this.path, name)
}
async getDirectoryHandle(name: string, options: FileSystemGetDirectoryOptions = {}): Promise<NodeFileSystemDirectoryHandle> {
Object.assign(options, { origin: this.origin || this })
return new NodeFileSystemDirectoryHandle(this.#getValidatedPath(name), options)
}
async getFileHandle(name: string, options: FileSystemGetFileOptions = {}): Promise<NodeFileSystemFileHandle> {
Object.assign(options, { origin: this.origin || this })
return new NodeFileSystemFileHandle(this.#getValidatedPath(name), options)
}
async removeEntry(name: string, options: FileSystemRemoveOptions = { recursive: false }): Promise<void> {
return asyncfs.rm(this.#getValidatedPath(name), { recursive: options.recursive, force: options.recursive })
}
async resolve(possibleDescendant: FileSystemHandle): Promise<string[] | null> {
if (typeof possibleDescendant !== typeof this) return null
let thisName = this.path
let possibleDescendantName = possibleDescendant.name
if (!path.isAbsolute(this.path)) thisName = path.join(process.cwd(), this.path)
if (!path.isAbsolute(possibleDescendant.name)) possibleDescendantName = path.join(process.cwd(), possibleDescendant.name)
if (!possibleDescendantName.startsWith(thisName)) return null
possibleDescendantName = possibleDescendantName.substring(thisName.length - 1)
if (possibleDescendantName[0] == path.sep) possibleDescendantName = possibleDescendantName.substring(1)
return possibleDescendantName.split(path.sep)
if (!(possibleDescendant instanceof NodeFileSystemHandle)) return null
if (!possibleDescendant.path.startsWith(this.path)) return null
return possibleDescendant.path.substring(this.path.length + 1).split(Path.sep)
}
[Symbol.asyncIterator] = this.entries
}
+23 -17
View File
@@ -1,29 +1,35 @@
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 NodeFileSystemHandle from './NodeFileSystemHandle.ts';
import NodeFileSystemWritableFileStream from './NodeFileSystemWriteableFileStream.ts';
import mime from 'mime'
import NonBufferedFile from './NonBufferedFile.ts'
import type {
FileSystemFileHandle,
FileSystemGetFileOptions,
FileSystemCreateWritableOptions,
FileSystemWritableFileStream,
File as WebFile
} from '@sugoidogo/importable-types-web'
import type NodeFileSystemDirectoryHandle from './NodeFileSystemDirectoryHandle.ts';
export default class NodeFileSystemFileHandle extends NodeFileSystemHandle implements FileSystemFileHandle {
declare kind: "file";
constructor(fpath: string, options: FileSystemGetFileOptions={}) {
super()
fpath = path.normalize(fpath)
if (!fs.existsSync(fpath) && options.create) fs.writeFileSync(fpath, '')
if (fs.lstatSync(fpath).isDirectory()) new Error('not a file')
this.path = fpath
constructor(path: string, options: FileSystemGetFileOptions & { origin?: NodeFileSystemDirectoryHandle } = {}) {
super(path,options)
this.kind="file"
let flags = fs.constants.O_RDWR
if (options.create) flags |= fs.constants.O_CREAT
fs.closeSync(fs.openSync(this.path, flags))
}
async createWritable(options: FileSystemCreateWritableOptions = {}): Promise<FileSystemWritableFileStream> {
return new NodeFileSystemWritableFileStream(this.path,options)
let flags = fs.constants.O_WRONLY
const fileHandle = await asyncfs.open(this.path, flags)
if (!options.keepExistingData) await fileHandle.truncate(0)
return new NodeFileSystemWritableFileStream(fileHandle)
}
async getFile(): Promise<File> {
const stats = await asyncfs.stat(this.path)
const type = mime.getType(this.name.split('.').pop())
return asyncfs.readFile(this.path).then(buffer => Object.assign(new Blob([buffer],{type}), {
lastModified: stats.mtimeMs,
name: this.path,
webkitRelativePath: this.path
}))
async getFile(): Promise<WebFile> {
const webkitRelativePathSegments=await this.origin.resolve(this)
return new NonBufferedFile(this.path,webkitRelativePathSegments.join(Path.sep))
}
}
+15 -8
View File
@@ -1,15 +1,22 @@
import * as path from 'node:path'
import * as Path from 'node:path'
import type {
FileSystemHandle,
FileSystemHandleKind
} from '@sugoidogo/importable-types-web'
import type NodeFileSystemDirectoryHandle from './NodeFileSystemDirectoryHandle';
export default class NodeFileSystemHandle implements FileSystemHandle {
kind: FileSystemHandleKind;
path: string;
get name() { return path.basename(this.path) }
origin: NodeFileSystemDirectoryHandle
get name() { return Path.basename(this.path) }
constructor(path: string, options: { origin?: NodeFileSystemDirectoryHandle } = {}) {
if (!Path.isAbsolute(path)) path = Path.resolve(path)
this.path = path
this.origin = options.origin
}
async isSameEntry(other: FileSystemHandle): Promise<boolean> {
if (typeof other != typeof this) return false
let thisName = this.name
let otherName = other.name
if (!path.isAbsolute(this.name)) thisName = path.join(process.cwd(), this.name)
if (!path.isAbsolute(other.name)) otherName = path.join(process.cwd(), other.name)
return thisName === otherName
if (!(other instanceof NodeFileSystemHandle)) return false
return this.path === other.path
}
}
+34 -28
View File
@@ -1,38 +1,44 @@
import * as fs from 'node:fs'
import * as asyncfs from 'node:fs/promises'
import * as path from 'node:path'
import { Writable } from 'node:stream'
import type {
FileSystemWritableFileStream,
FileSystemWriteChunkType,
WriteParams
} from '@sugoidogo/importable-types-web'
import type { FileHandle } from 'node:fs/promises'
function isWriteParams(data: FileSystemWriteChunkType): data is WriteParams {
return ["write", "seek", "truncate"].includes((data as WriteParams).type)
}
export default class NodeFileSystemWritableFileStream extends WritableStream implements FileSystemWritableFileStream {
#seek_position = 0
#name: string
constructor(name: string, options: FileSystemCreateWritableOptions={}) {
name = path.normalize(name)
super(Writable.toWeb(fs.createWriteStream(name)))
this.#name = name
if(!options.keepExistingData) this.truncate(0)
#position = 0
#fileHandle: FileHandle
constructor(fileHandle: FileHandle) {
const writeStream = fileHandle.createWriteStream({ "encoding": "binary" })
const writeableStream = Writable.toWeb(writeStream)
super(writeableStream)
this.#fileHandle = fileHandle
}
async seek(position: number): Promise<void> {
this.#seek_position = position
if (this.locked) throw new Error("stream is locked")
this.#position = position
}
truncate(size: number): Promise<void> {
return asyncfs.truncate(this.#name, size)
async truncate(size: number): Promise<void> {
if (this.locked) throw new Error("stream is locked")
return this.#fileHandle.truncate(size)
}
async write(wdata: FileSystemWriteChunkType): Promise<void> {
if (wdata instanceof Blob) wdata = await wdata.bytes()
if (typeof wdata === 'string') wdata = new TextEncoder().encode(wdata)
let wsize: number = null
let pseek_position = this.#seek_position
if ((wdata as WriteParams).type) {
const { type, position, size, data } = (wdata as WriteParams)
if (type === 'truncate') return asyncfs.truncate(this.#name, size)
if (position) this.#seek_position = position
if (type === 'seek') return
if (size) wsize = size
wdata = data
async write(data: FileSystemWriteChunkType): Promise<void> {
if (this.locked) throw new Error("stream is locked")
let offset: number, length: number, position=this.#position
if (isWriteParams(data)) {
if (data.type === "seek") return this.seek(data.position)
if (data.type === "truncate") return this.truncate(data.size)
if (typeof data.position === "number") position = data.position
length = data.size
data = data.data
}
const file = await asyncfs.open(this.#name,'r+')
await file.write((wdata as Uint8Array), null, wsize, this.#seek_position)
this.#seek_position = pseek_position
if (typeof data === 'string') data = new TextEncoder().encode(data)
const { bytesWritten } = await this.#fileHandle.write(data as any, offset, length, position)
this.#position=position+bytesWritten
}
}
+48
View File
@@ -0,0 +1,48 @@
import * as fs from "node:fs"
import * as asyncfs from "node:fs/promises"
import * as Path from "node:path"
import mime from "mime"
import type { Blob as WebBlob, File as WebFile, ReadableStream } from "@sugoidogo/importable-types-web";
import { Blob } from "node:buffer";
import { Readable } from "node:stream";
export default class NonBufferedFile implements WebFile {
webkitRelativePath: string;
#path: string
constructor(path: string, webkitRelativePath:string) {
this.#path = Path.resolve(path)
this.webkitRelativePath=webkitRelativePath
}
get lastModified() {
return fs.statSync(this.#path).mtimeMs
}
get size() {
return fs.statSync(this.#path).size
}
get type() {
return mime.getType(this.#path)
}
get name() {
return Path.basename(this.#path)
}
async bytes(): Promise<Uint8Array<ArrayBuffer>> {
return asyncfs.readFile(this.#path)
}
async arrayBuffer(): Promise<ArrayBuffer> {
const bytes = await asyncfs.readFile(this.#path)
return bytes.buffer
}
async text(): Promise<string> {
return asyncfs.readFile(this.#path,"utf8")
}
slice(start?: number, end?: number, contentType?: string): WebBlob {
const fd = fs.openSync(this.#path, fs.constants.O_RDONLY)
const buffer=new Uint8Array()
fs.readSync(fd, buffer, { "position": start, "length": end - start })
fs.closeSync(fd)
return new Blob([buffer],{"type":contentType||this.type}) as any
}
stream(): ReadableStream<Uint8Array<ArrayBuffer>> {
return Readable.toWeb(fs.createReadStream(this.#path)) as any
}
}