{"_id":"@emdzej/bimmerz-vfs","_rev":"4-0b25dfebf1f5cd9d9536d6ea41e2fd35","name":"@emdzej/bimmerz-vfs","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@emdzej/bimmerz-vfs","version":"0.1.0","_id":"@emdzej/bimmerz-vfs@0.1.0","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"homepage":"https://github.com/emdzej/bimmerz#readme","bugs":{"url":"https://github.com/emdzej/bimmerz/issues"},"dist":{"shasum":"b147b9ecc242094cee6f7c132e2357bb3101b6af","tarball":"https://registry.npmjs.org/@emdzej/bimmerz-vfs/-/bimmerz-vfs-0.1.0.tgz","fileCount":39,"integrity":"sha512-B7X3HJiWr61Ny2Zu4BGmh7N5BGJT+XyuyZYvyWKgapEPycPw7+nrXcIWEYihxjCEZQ/A48mpv7aYRRHhHLUF6A==","signatures":[{"sig":"MEQCIDteINQpWQLvSu9NVC4Nhjm54G4Q2eHoP9dTlpAR21vNAiB8jGHnFOnl64gn55ft6bxAj9x9BA3McpjGLL8ekhOJJA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":82336},"main":"dist/index.js","type":"module","_from":"file:emdzej-bimmerz-vfs-0.1.0.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint .","build":"tsc","clean":"rm -rf dist .turbo *.tsbuildinfo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"emdzej","email":"michal@jaskolski.pro"},"_resolved":"/private/var/folders/7h/x_w_580x4s9dq3sq11tpvzkwy3nbj8/T/4c3dee7e13172cb9962c2c1fe13bed96/emdzej-bimmerz-vfs-0.1.0.tgz","_integrity":"sha512-B7X3HJiWr61Ny2Zu4BGmh7N5BGJT+XyuyZYvyWKgapEPycPw7+nrXcIWEYihxjCEZQ/A48mpv7aYRRHhHLUF6A==","repository":{"url":"git+https://github.com/emdzej/bimmerz.git","type":"git","directory":"packages/vfs"},"_npmVersion":"11.16.0","description":"Read-only virtual file system abstraction for bimmerz tools — same interface over local File System Access API and remote HTTP index.json trees.","directories":{},"_nodeVersion":"22.22.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.3","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/bimmerz-vfs_0.1.0_1780652303988_0.5996215418015427","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by @emdzej/csfs-* (MIT). See https://github.com/emdzej/csfs"},"0.2.0":{"name":"@emdzej/bimmerz-vfs","version":"0.2.0","_id":"@emdzej/bimmerz-vfs@0.2.0","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"homepage":"https://github.com/emdzej/bimmerz#readme","bugs":{"url":"https://github.com/emdzej/bimmerz/issues"},"dist":{"shasum":"05243a8b295f56f4d8ad1a477fca1d23e0d3b8ea","tarball":"https://registry.npmjs.org/@emdzej/bimmerz-vfs/-/bimmerz-vfs-0.2.0.tgz","fileCount":51,"integrity":"sha512-A4AxvBZG6cMJQ6eO57kmACajsw2c6YtUwF+YimFRfluYVQ2keg6xNevSuAEGXa85fhcwQPGJ6AwPDsI92yjHAQ==","signatures":[{"sig":"MEQCIHthVEe95PvCNCAX+cpB/4r/X0YOgb4Aa+8FYNYgjcrLAiBqELxxujy8/NoRM09Dt+6pGwszJ3ATbsuHoqEZW7rbQQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":176495},"main":"dist/index.js","type":"module","_from":"file:emdzej-bimmerz-vfs-0.2.0.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint .","build":"tsc","clean":"rm -rf dist .turbo *.tsbuildinfo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"emdzej","email":"michal@jaskolski.pro"},"_resolved":"/private/var/folders/7h/x_w_580x4s9dq3sq11tpvzkwy3nbj8/T/b2b690393e95378156f4aa0c174f7b2e/emdzej-bimmerz-vfs-0.2.0.tgz","_integrity":"sha512-A4AxvBZG6cMJQ6eO57kmACajsw2c6YtUwF+YimFRfluYVQ2keg6xNevSuAEGXa85fhcwQPGJ6AwPDsI92yjHAQ==","repository":{"url":"git+https://github.com/emdzej/bimmerz.git","type":"git","directory":"packages/vfs"},"_npmVersion":"11.16.0","description":"Read-only virtual file system abstraction for bimmerz tools — same interface over local File System Access API, remote HTTP index.json trees, and an OPFS / IndexedDB cache layer for the HTTP backend.","directories":{},"_nodeVersion":"22.22.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.3","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/bimmerz-vfs_0.2.0_1781452418628_0.3155691483753289","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Superseded by @emdzej/csfs-* (MIT). See https://github.com/emdzej/csfs"},"0.3.0":{"name":"@emdzej/bimmerz-vfs","version":"0.3.0","license":"PolyForm-Noncommercial-1.0.0","_id":"@emdzej/bimmerz-vfs@0.3.0","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"homepage":"https://github.com/emdzej/bimmerz-core#readme","bugs":{"url":"https://github.com/emdzej/bimmerz-core/issues"},"dist":{"shasum":"1e82f668c540f0dd4a4c585f10044ec41222204b","tarball":"https://registry.npmjs.org/@emdzej/bimmerz-vfs/-/bimmerz-vfs-0.3.0.tgz","fileCount":51,"integrity":"sha512-y/1WUoJgXa/Jqt9y5MTRbwHm6osq9CRhbbNLuLF4+AdwkkuircN5eeGxrxeTQ0sFFOZNurIAz8J1K3y07R1kBg==","signatures":[{"sig":"MEYCIQCoXisVkbYwlEbj94eBNBjjNSTQfduFB0AhSPpixByAcQIhAOFvlmLidwzZEIttCBQQ5TgFnC0h8ual6Mh18h6Vp/bi","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIBvVOwSWwYTZuBOrl0I6a3Cg7dD1e/o+7o/gf0wpaTYuAiEApxhIdvcH5zFk1xsI5yhJXmRhqjpto7Fxhi3G6lRcvsY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emdzej%2fbimmerz-vfs@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":184492},"main":"dist/index.js","type":"module","_from":"file:emdzej-bimmerz-vfs-0.3.0.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint .","build":"tsc","clean":"rm -rf dist .turbo *.tsbuildinfo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:33bd13b2-3a72-4ee8-991f-01db601f1d35"}},"_resolved":"/tmp/7a3f1cef391775920fd87d62b558c7dc/emdzej-bimmerz-vfs-0.3.0.tgz","_integrity":"sha512-y/1WUoJgXa/Jqt9y5MTRbwHm6osq9CRhbbNLuLF4+AdwkkuircN5eeGxrxeTQ0sFFOZNurIAz8J1K3y07R1kBg==","deprecated":"Superseded by @emdzej/csfs-* (MIT). See https://github.com/emdzej/csfs","repository":{"url":"git+https://github.com/emdzej/bimmerz-core.git","type":"git","directory":"packages/vfs"},"_npmVersion":"12.1.0","description":"DEPRECATED — use @emdzej/csfs-* instead (MIT). Read-only virtual file system abstraction for bimmerz tools: one interface over the local File System Access API, remote HTTP index.json trees, and an OPFS / IndexedDB cache layer for the HTTP backend.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.3","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/bimmerz-vfs_0.3.0_1790327365481_0.7329273914414653","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-06-05T09:38:23.786Z","modified":"2026-09-25T09:17:58.643Z","0.1.0":"2026-06-05T09:38:24.142Z","0.2.0":"2026-06-14T15:53:38.808Z","0.3.0":"2026-09-25T09:09:25.577Z"},"bugs":{"url":"https://github.com/emdzej/bimmerz-core/issues"},"license":"PolyForm-Noncommercial-1.0.0","homepage":"https://github.com/emdzej/bimmerz-core#readme","repository":{"url":"git+https://github.com/emdzej/bimmerz-core.git","type":"git","directory":"packages/vfs"},"description":"DEPRECATED — use @emdzej/csfs-* instead (MIT). Read-only virtual file system abstraction for bimmerz tools: one interface over the local File System Access API, remote HTTP index.json trees, and an OPFS / IndexedDB cache layer for the HTTP backend.","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"readme":"# @emdzej/bimmerz-vfs\n\n> **Superseded by [csfs](https://github.com/emdzej/csfs).** `@emdzej/csfs-*` does\n> everything this does and more — byte-range reads, zip archives read in place,\n> a writable OPFS backend, `node:fs` for tooling — and it is MIT where this is\n> PolyForm Noncommercial, which has already cost real work in GPL consumers.\n>\n> csfs 0.2.0 closed the four gaps that were blocking the move:\n> case-insensitive HTTP lookups, lookups answering with the name as *stored*\n> rather than as asked for, a readable tree on a host that ignores `Range`, and\n> symlinks surviving a listing.\n>\n> New code should use csfs. Describe a tree with `bimmerz data manifest`\n> (one flat `csfs-manifest.json`) rather than `bimmerz data index` (one\n> `index.json` per directory). This package stays until inpax, ncsx, nfsx and\n> dashx have moved; see `notes/vfs-to-csfs-migration-eval.md` for the plan and\n> the per-app translation table.\n\nRead-only virtual file system for bimmerz tools — one interface, two backends:\n\n| Backend | Class | Source |\n|---|---|---|\n| **Local** | `FsaDirectory` | File System Access API / OPFS (`FileSystemDirectoryHandle`) |\n| **Remote** | `HttpDirectory` | HTTP server + `index.json` directory listings |\n\nBoth implement `VirtualDirectory`. All lookups are **case-insensitive**.\n\n## Install\n\n```bash\nnpm i @emdzej/bimmerz-vfs\n```\n\n## Quick start\n\n```ts\nimport { FsaDirectory, HttpDirectory, drillPath, listFiles } from '@emdzej/bimmerz-vfs';\n\n// ---- Local (File System Access API / OPFS) ----\nconst handle = await showDirectoryPicker();\nconst local = new FsaDirectory(handle);\n\n// ---- Remote (HTTP + index.json) ----\nconst remote = new HttpDirectory('https://files.example.com/inpa-bundle');\n\n// ---- Same API for both ----\nconst ecu = await drillPath(remote, 'EDIABAS', 'Ecu');   // case-insensitive\nconst ipos = await listFiles(ecu!, '.ipo');               // [{ name: 'MS43.IPO', size: 12345 }]\nconst file = await ecu!.file('ms43.prg');                 // case-insensitive\nconst bytes = await file!.arrayBuffer();\n```\n\n## API\n\n### `VirtualDirectory`\n\n```ts\ninterface VirtualDirectory {\n  readonly name: string;\n\n  /** Case-insensitive. Returns null if not found. */\n  file(name: string): Promise<VirtualFile | null>;\n\n  /** Case-insensitive. Returns null if not found. */\n  dir(name: string): Promise<VirtualDirectory | null>;\n\n  /**\n   * Flat listing of direct children.\n   * Note: FsaDirectory returns size=0 for files — call file(name) for the real size.\n   */\n  entries(): Promise<VirtualEntry[]>;\n}\n\ntype VirtualEntry =\n  | { kind: 'file'; name: string; size: number }\n  | { kind: 'dir';  name: string };\n```\n\n### `VirtualFile`\n\n```ts\ninterface VirtualFile {\n  readonly name: string;   // original-cased full basename: \"MS43.IPO\"\n  readonly size: number;\n  arrayBuffer(): Promise<ArrayBuffer>;\n}\n```\n\n### `FsaDirectory(handle)`\n\nWraps a `FileSystemDirectoryHandle` (File System Access API or OPFS). Both sources return the same handle type, so this class works for both.\n\n```ts\n// File System Access API (user picks a folder)\nconst root = new FsaDirectory(await showDirectoryPicker());\n\n// OPFS (pre-extracted bundle)\nconst opfsRoot = await navigator.storage.getDirectory();\nconst root = new FsaDirectory(opfsRoot);\n```\n\n`entries()` does not call `getFile()` per entry (no extra I/O for listings). Call `file(name)` when you need the actual size.\n\n### `HttpDirectory(baseUrl, options?)`\n\nNavigates a remote tree using `index.json` files generated by `bimmerz data index`.\n\n```ts\nconst root = new HttpDirectory('https://cdn.example.com/inpa', {\n  indexFile: 'index.json',  // default\n  fetch: customFetch,       // inject auth headers, mock in tests, etc.\n});\n```\n\n**How it works:**\n1. On first access `entries()` / `file()` / `dir()` fetches `{baseUrl}/index.json` and caches it.\n2. `file(name)` finds the matching entry (case-insensitive on `fullName`) and returns an `HttpFile` that fetches `{baseUrl}/{originalFullName}` on `arrayBuffer()`.\n3. `dir(name)` returns a new `HttpDirectory` rooted at `{baseUrl}/{originalFullName}/`, which lazily fetches its own `index.json`.\n\nThe `index.json` format is the one written by [`bimmerz data index`](../../../apps/cli) — both `fullName` (lowercase) and `originalFullName` (original casing) are present in every entry. The lookup uses `fullName` for matching and `originalFullName` for the fetch URL, so the original casing of the server's files is preserved in HTTP requests.\n\n**`link` entries** (symlinks recorded by `bimmerz data index`) are treated as files.\n\n### `drillPath(root, ...segments)`\n\nWalk down a path segment by segment, case-insensitively. Returns `null` at the first missing segment.\n\n```ts\nconst cfgdat = await drillPath(root, 'EC-APPS', 'INPA', 'CFGDAT');\n// equivalent to:\n// root.dir('EC-APPS') → .dir('INPA') → .dir('CFGDAT')\n```\n\n### `listFiles(dir, ext?)`\n\nReturn all file entries in `dir`, optionally filtered by extension (case-insensitive, dot required).\n\n```ts\nconst ipos = await listFiles(cfgdat, '.ipo');\n// [{ kind: 'file', name: 'MS43.IPO', size: 512 }, ...]\n```\n\nReturns `Array<{ kind: 'file'; name: string; size: number }>` — lightweight entry objects. Call `dir.file(name)` to open one.\n\n## Serving an install remotely\n\nUse `bimmerz data index` to write `index.json` files into the extracted install tree, then serve the whole directory over HTTP (nginx, GitHub Pages, any static host):\n\n```bash\nbimmerz data index ~/inpa-extracted\n# → writes index.json in every subdirectory\n\n# Serve with any static file server\nnpx serve ~/inpa-extracted\n```\n\nThen point `HttpDirectory` at the server root:\n\n```ts\nconst root = new HttpDirectory('http://localhost:3000');\nconst cfgdat = await drillPath(root, 'EC-APPS', 'INPA', 'CFGDAT');\nconst ipo = await cfgdat!.file('MS43.IPO');\nconst bytes = await ipo!.arrayBuffer();\n```\n\n## Notes\n\n- **Read-only** — no write operations. External write-back (fault-store reports, etc.) is application-layer concern.\n- **No runtime dependencies** — FSA is a browser built-in; HTTP uses `fetch`. Works in any environment with `fetch` (browsers, Node ≥ 18, Deno, Bun).\n- **Index caching** — each `HttpDirectory` instance fetches its `index.json` at most once. Create a new instance to force a refresh.\n","readmeFilename":"README.md"}