{"_id":"@aginix/vulcan-js","_rev":"5-7f1c218b212a7f777717357e72066dee","name":"@aginix/vulcan-js","dist-tags":{"beta":"0.1.0-beta.0","latest":"0.2.2"},"versions":{"0.1.0-beta.0":{"name":"@aginix/vulcan-js","version":"0.1.0-beta.0","keywords":["vulcan","aginix","sdk","client","storage","adonisjs","supabase-style"],"author":{"name":"n3n"},"license":"UNLICENSED","_id":"@aginix/vulcan-js@0.1.0-beta.0","maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"c8":{"exclude":["tests/**"],"reporter":["text","html"]},"dist":{"shasum":"8d72dd3847f0eb1f6b9605924bff6c4e38fa8dc6","tarball":"https://registry.npmjs.org/@aginix/vulcan-js/-/vulcan-js-0.1.0-beta.0.tgz","fileCount":15,"integrity":"sha512-71BpJiMvKAK2p6ZHctEl+ZOHx62cuU4/Tg58rHky5sbaZsK5NHOyp4cJ8VRf/05umh77p+I0xYuXHC58f2GcCQ==","signatures":[{"sig":"MEYCIQCcQ0NxXkLN3OkPelrKl67cbgTDPKelYifabH4DC0oIRQIhAPGLR0URBw0rx8ZUiwfqXKNNR4rW7ADpjJjiXDmRetRz","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":25806},"main":"build/index.js","type":"module","types":"build/index.d.ts","tsdown":{"dts":false,"clean":true,"entry":["./index.ts","./src/types.ts","./src/http.ts","./src/create_client.ts","./src/storage/module.ts","./src/storage/types.ts"],"format":"esm","minify":"dce-only","outDir":"./build","target":"esnext","treeshake":false,"sourcemaps":false,"fixedExtension":false},"engines":{"node":">=24.0.0"},"exports":{".":{"types":"./build/index.d.ts","default":"./build/index.js"},"./types":{"types":"./build/src/types.d.ts","default":"./build/src/types.js"},"./storage":{"types":"./build/src/storage/module.d.ts","default":"./build/src/storage/module.js"}},"scripts":{"lint":"eslint .","test":"c8 npm run quick:test","build":"npm run compile","format":"prettier --write .","compile":"tsdown && tsc --emitDeclarationOnly --declaration","pretest":"npm run lint","release":"release-it","version":"npm run build","typecheck":"tsc --noEmit","precompile":"npm run lint","quick:test":"node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"n3n","email":"nonpawit.tee@gmail.com"},"prettier":"@adonisjs/prettier-config","release-it":{"git":{"push":true,"tagName":"vulcan-js-v${version}","commitMessage":"chore(vulcan-js): release ${version}","tagAnnotation":"vulcan-js-v${version}","requireUpstream":true,"requireCleanWorkingDir":true},"npm":{"publish":true,"skipChecks":true},"github":{"release":true,"releaseName":"@aginix/vulcan-js ${version}"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"_npmVersion":"11.8.0","description":"Universal client SDK for the @aginix/vulcan family — Supabase-style createClient() with a storage module that speaks to @aginix/adonis-vulcan-storage.","directories":{},"_nodeVersion":"24.13.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","eslint":"^10.0.3","tsdown":"^0.21.0","prettier":"^3.8.1","release-it":"^19.2.4","typescript":"^5.9.3","@types/node":"^25.3.5","@japa/assert":"^4.2.0","@japa/runner":"^5.3.0","@poppinss/ts-exec":"^1.4.4","@adonisjs/tsconfig":"^2.0.0","@adonisjs/eslint-config":"^3.0.0","@adonisjs/prettier-config":"^1.4.5","@release-it/conventional-changelog":"^10.0.5"},"_npmOperationalInternal":{"tmp":"tmp/vulcan-js_0.1.0-beta.0_1778744629903_0.6764539168767736","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@aginix/vulcan-js","version":"0.1.0","keywords":["vulcan","aginix","sdk","client","storage","adonisjs","supabase-style"],"author":{"name":"n3n"},"license":"UNLICENSED","_id":"@aginix/vulcan-js@0.1.0","maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"c8":{"exclude":["tests/**"],"reporter":["text","html"]},"dist":{"shasum":"9dfd47dcba9181ef8f46f161eddb99c0c79b31df","tarball":"https://registry.npmjs.org/@aginix/vulcan-js/-/vulcan-js-0.1.0.tgz","fileCount":15,"integrity":"sha512-BdmUh1YtI/qCtI/rmowrOgUMY210f901r9589/9YffhP69HxZ5didIksVUW4Bpz0ruhI5q/mq5PlHEPe2Qsskw==","signatures":[{"sig":"MEQCIACZ1GK/Qh+C+MZI4aMHmwcej+ocdl8ajpkplKCHqNOgAiB4SBsExCzKZopZkNUNLRwiJsdCm9LdDZtJki64PPBDjA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29199},"main":"build/index.js","type":"module","types":"build/index.d.ts","tsdown":{"dts":false,"clean":true,"entry":["./index.ts","./src/types.ts","./src/http.ts","./src/create_client.ts","./src/storage/module.ts","./src/storage/types.ts"],"format":"esm","minify":"dce-only","outDir":"./build","target":"esnext","treeshake":false,"sourcemaps":false,"fixedExtension":false},"engines":{"node":">=24.0.0"},"exports":{".":{"types":"./build/index.d.ts","default":"./build/index.js"},"./types":{"types":"./build/src/types.d.ts","default":"./build/src/types.js"},"./storage":{"types":"./build/src/storage/module.d.ts","default":"./build/src/storage/module.js"}},"scripts":{"lint":"eslint .","test":"c8 npm run quick:test","build":"npm run compile","format":"prettier --write .","compile":"tsdown && tsc --emitDeclarationOnly --declaration","pretest":"npm run lint","release":"release-it","version":"npm run build","typecheck":"tsc --noEmit","precompile":"npm run lint","quick:test":"node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"n3n","email":"nonpawit.tee@gmail.com"},"prettier":"@adonisjs/prettier-config","release-it":{"git":{"push":true,"tagName":"vulcan-js-v${version}","commitMessage":"chore(vulcan-js): release ${version}","tagAnnotation":"vulcan-js-v${version}","requireUpstream":true,"requireCleanWorkingDir":true},"npm":{"publish":true,"skipChecks":true},"github":{"release":true,"releaseName":"@aginix/vulcan-js ${version}"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"_npmVersion":"11.8.0","description":"Universal client SDK for the @aginix/vulcan family — Supabase-style createClient() with a storage module that speaks to @aginix/adonis-vulcan-storage.","directories":{},"_nodeVersion":"24.13.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","eslint":"^10.0.3","tsdown":"^0.21.0","prettier":"^3.8.1","release-it":"^19.2.4","typescript":"^5.9.3","@types/node":"^25.3.5","@japa/assert":"^4.2.0","@japa/runner":"^5.3.0","@poppinss/ts-exec":"^1.4.4","@adonisjs/tsconfig":"^2.0.0","@adonisjs/eslint-config":"^3.0.0","@adonisjs/prettier-config":"^1.4.5","@release-it/conventional-changelog":"^10.0.5"},"_npmOperationalInternal":{"tmp":"tmp/vulcan-js_0.1.0_1778782950672_0.04724704867692475","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aginix/vulcan-js","version":"0.2.0","keywords":["vulcan","aginix","sdk","client","storage","adonisjs","supabase-style"],"author":{"name":"n3n"},"license":"UNLICENSED","_id":"@aginix/vulcan-js@0.2.0","maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"c8":{"exclude":["tests/**"],"reporter":["text","html"]},"dist":{"shasum":"e57bc4b970c59160082253b557dbb6d1272008c7","tarball":"https://registry.npmjs.org/@aginix/vulcan-js/-/vulcan-js-0.2.0.tgz","fileCount":15,"integrity":"sha512-wYvKzr0x6/ddkZZ4UXV6leLU879pgDkLLaX+MCUDPaoY/eMQX2jV0028ap8SjVfW5vCzBvKzu/cetX5hokccZQ==","signatures":[{"sig":"MEYCIQD18ot88muTwTV1bxPliPNl9ZUZBGDuJhA/eqBe0L2p0QIhAJkS3OO7R5er5lPfG7pDASKPP27Y0h2TWir+y+tluUkZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30435},"main":"build/index.js","type":"module","types":"build/index.d.ts","tsdown":{"dts":false,"clean":true,"entry":["./index.ts","./src/types.ts","./src/http.ts","./src/create_client.ts","./src/storage/module.ts","./src/storage/types.ts"],"format":"esm","minify":"dce-only","outDir":"./build","target":"esnext","treeshake":false,"sourcemaps":false,"fixedExtension":false},"engines":{"node":">=24.0.0"},"exports":{".":{"types":"./build/index.d.ts","default":"./build/index.js"},"./types":{"types":"./build/src/types.d.ts","default":"./build/src/types.js"},"./storage":{"types":"./build/src/storage/module.d.ts","default":"./build/src/storage/module.js"}},"gitHead":"402308aa3d6cc4a186363c45319d4a99a3629a1c","scripts":{"lint":"eslint .","test":"c8 npm run quick:test","build":"npm run compile","format":"prettier --write .","compile":"tsdown && tsc --emitDeclarationOnly --declaration","pretest":"npm run lint","release":"release-it","version":"npm run build","typecheck":"tsc --noEmit","precompile":"npm run lint","quick:test":"node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"n3n","email":"nonpawit.tee@gmail.com"},"prettier":"@adonisjs/prettier-config","release-it":{"git":{"push":true,"tagName":"vulcan-js-v${version}","commitMessage":"chore(vulcan-js): release ${version}","tagAnnotation":"vulcan-js-v${version}","requireUpstream":true,"requireCleanWorkingDir":true},"npm":{"publish":true,"skipChecks":true},"github":{"release":true,"releaseName":"@aginix/vulcan-js ${version}"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"_npmVersion":"11.8.0","description":"Universal client SDK for the @aginix/vulcan family — Supabase-style createClient() with a storage module that speaks to @aginix/adonis-vulcan-storage.","directories":{},"_nodeVersion":"24.13.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","eslint":"^10.0.3","tsdown":"^0.21.0","prettier":"^3.8.1","release-it":"^19.2.4","typescript":"^5.9.3","@types/node":"^25.3.5","@japa/assert":"^4.2.0","@japa/runner":"^5.3.0","@poppinss/ts-exec":"^1.4.4","@adonisjs/tsconfig":"^2.0.0","@adonisjs/eslint-config":"^3.0.0","@adonisjs/prettier-config":"^1.4.5","@release-it/conventional-changelog":"^10.0.5"},"_npmOperationalInternal":{"tmp":"tmp/vulcan-js_0.2.0_1778956735504_0.29319327875288614","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aginix/vulcan-js","version":"0.2.1","keywords":["vulcan","aginix","sdk","client","storage","adonisjs","supabase-style"],"author":{"name":"n3n"},"license":"UNLICENSED","_id":"@aginix/vulcan-js@0.2.1","maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"c8":{"exclude":["tests/**"],"reporter":["text","html"]},"dist":{"shasum":"8e732347ff85beef9befd0851c7698e6a62c398f","tarball":"https://registry.npmjs.org/@aginix/vulcan-js/-/vulcan-js-0.2.1.tgz","fileCount":15,"integrity":"sha512-8XZo0NK7rA2CQgxwdaq2RlAAIjR8Bwm/UY/84hWh8HskCs3YSgQwClxm+LwL3LhXk5tTLig0SSW9IZ58NZXVhA==","signatures":[{"sig":"MEUCIQCERh7j4wYe6IpDjwn+LHZ78Y0XqFKTdfPbqvouMmQedAIgZv2nbeCGyknxbEVtUUEuCa7VNqbIRKuaresfjteYENI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31939},"main":"build/index.js","type":"module","types":"build/index.d.ts","tsdown":{"dts":false,"clean":true,"entry":["./index.ts","./src/types.ts","./src/http.ts","./src/create_client.ts","./src/storage/module.ts","./src/storage/types.ts"],"format":"esm","minify":"dce-only","outDir":"./build","target":"esnext","treeshake":false,"sourcemaps":false,"fixedExtension":false},"engines":{"node":">=24.0.0"},"exports":{".":{"types":"./build/index.d.ts","default":"./build/index.js"},"./types":{"types":"./build/src/types.d.ts","default":"./build/src/types.js"},"./storage":{"types":"./build/src/storage/module.d.ts","default":"./build/src/storage/module.js"}},"gitHead":"c7bac4e6372e862d13c189495a6214e5a97ce3b4","scripts":{"lint":"eslint .","test":"c8 npm run quick:test","build":"npm run compile","format":"prettier --write .","compile":"tsdown && tsc --emitDeclarationOnly --declaration","pretest":"npm run lint","release":"release-it","version":"npm run build","typecheck":"tsc --noEmit","precompile":"npm run lint","quick:test":"node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts","prepublishOnly":"npm run build"},"_npmUser":{"name":"n3n","email":"nonpawit.tee@gmail.com"},"prettier":"@adonisjs/prettier-config","release-it":{"git":{"push":true,"tagName":"vulcan-js-v${version}","commitMessage":"chore(vulcan-js): release ${version}","tagAnnotation":"vulcan-js-v${version}","requireUpstream":true,"requireCleanWorkingDir":true},"npm":{"publish":true,"skipChecks":true},"github":{"release":true,"releaseName":"@aginix/vulcan-js ${version}"},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"_npmVersion":"11.8.0","description":"Universal client SDK for the @aginix/vulcan family — Supabase-style createClient() with a storage module that speaks to @aginix/adonis-vulcan-storage.","directories":{},"_nodeVersion":"24.13.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^11.0.0","eslint":"^10.0.3","tsdown":"^0.21.0","prettier":"^3.8.1","release-it":"^19.2.4","typescript":"^5.9.3","@types/node":"^25.3.5","@japa/assert":"^4.2.0","@japa/runner":"^5.3.0","@poppinss/ts-exec":"^1.4.4","@adonisjs/tsconfig":"^2.0.0","@adonisjs/eslint-config":"^3.0.0","@adonisjs/prettier-config":"^1.4.5","@release-it/conventional-changelog":"^10.0.5"},"_npmOperationalInternal":{"tmp":"tmp/vulcan-js_0.2.1_1779010726326_0.9647412746728348","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@aginix/vulcan-js","description":"Universal client SDK for the @aginix/vulcan family — Supabase-style createClient() with a storage module that speaks to @aginix/adonis-vulcan-storage.","version":"0.2.2","engines":{"node":">=24.0.0"},"type":"module","main":"build/index.js","types":"build/index.d.ts","exports":{".":{"types":"./build/index.d.ts","default":"./build/index.js"},"./storage":{"types":"./build/src/storage/module.d.ts","default":"./build/src/storage/module.js"},"./types":{"types":"./build/src/types.d.ts","default":"./build/src/types.js"}},"scripts":{"typecheck":"tsc --noEmit","lint":"eslint .","format":"prettier --write .","quick:test":"node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts","pretest":"npm run lint","test":"c8 npm run quick:test","precompile":"npm run lint","compile":"tsdown && tsc --emitDeclarationOnly --declaration","build":"npm run compile","release":"release-it","version":"npm run build","prepublishOnly":"npm run build"},"keywords":["vulcan","aginix","sdk","client","storage","adonisjs","supabase-style"],"author":{"name":"n3n"},"license":"UNLICENSED","devDependencies":{"@adonisjs/eslint-config":"^3.0.0","@adonisjs/prettier-config":"^1.4.5","@adonisjs/tsconfig":"^2.0.0","@japa/assert":"^4.2.0","@japa/runner":"^5.3.0","@poppinss/ts-exec":"^1.4.4","@release-it/conventional-changelog":"^10.0.5","@types/node":"^25.3.5","c8":"^11.0.0","eslint":"^10.0.3","prettier":"^3.8.1","release-it":"^19.2.4","tsdown":"^0.21.0","typescript":"^5.9.3"},"publishConfig":{"access":"public"},"tsdown":{"entry":["./index.ts","./src/types.ts","./src/http.ts","./src/create_client.ts","./src/storage/module.ts","./src/storage/types.ts"],"outDir":"./build","clean":true,"format":"esm","minify":"dce-only","fixedExtension":false,"dts":false,"treeshake":false,"sourcemaps":false,"target":"esnext"},"release-it":{"git":{"requireCleanWorkingDir":true,"requireUpstream":true,"commitMessage":"chore(vulcan-js): release ${version}","tagAnnotation":"vulcan-js-v${version}","push":true,"tagName":"vulcan-js-v${version}"},"github":{"release":true,"releaseName":"@aginix/vulcan-js ${version}"},"npm":{"publish":true,"skipChecks":true},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"c8":{"reporter":["text","html"],"exclude":["tests/**"]},"prettier":"@adonisjs/prettier-config","gitHead":"090864afe3dcf8b7b971fa67aeacdc9786f4baf2","_id":"@aginix/vulcan-js@0.2.2","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-V5XhX7IWvuUIp+MeF/uDOj327Hi90jVDAMqAWqoQ6Z2fkfQElWC3JWdR1DdPqgfl9jqDfUrNJK/S2CoXWNs7ZQ==","shasum":"0884677050ae78f1e5b1501215aeab92adce1ee9","tarball":"https://registry.npmjs.org/@aginix/vulcan-js/-/vulcan-js-0.2.2.tgz","fileCount":15,"unpackedSize":32796,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCppuGUNXEx3yk39b53/n9FrCSpmCiPTC2SQO8qrLyF5QIgSzGzSOaS+xIJkZgmvzIv340cHvyQra2tbjnLAM7Z+ck="}]},"_npmUser":{"name":"n3n","email":"nonpawit.tee@gmail.com"},"directories":{},"maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vulcan-js_0.2.2_1782811001132_0.4091323450808504"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-14T07:43:49.826Z","modified":"2026-06-30T09:16:41.528Z","0.1.0-beta.0":"2026-05-14T07:43:50.072Z","0.1.0":"2026-05-14T18:22:30.805Z","0.2.0":"2026-05-16T18:38:55.649Z","0.2.1":"2026-05-17T09:38:46.513Z","0.2.2":"2026-06-30T09:16:41.279Z"},"author":{"name":"n3n"},"license":"UNLICENSED","keywords":["vulcan","aginix","sdk","client","storage","adonisjs","supabase-style"],"description":"Universal client SDK for the @aginix/vulcan family — Supabase-style createClient() with a storage module that speaks to @aginix/adonis-vulcan-storage.","maintainers":[{"name":"n3n","email":"nonpawit.tee@gmail.com"}],"readme":"# @aginix/vulcan-js\n\n> Universal client SDK for the `@aginix/vulcan` family. Supabase-style `createClient()` returns a single client with named sub-modules. Today it ships a `storage` module that speaks to [`@aginix/adonis-vulcan-storage`](../adonis-vulcan-storage).\n\n## Install\n\n```sh\nnpm install @aginix/vulcan-js\n```\n\nESM only, Node 24+, all modern browsers. No runtime dependencies — uses native `fetch`.\n\n## Quick start\n\n```ts\nimport { createClient } from '@aginix/vulcan-js'\n\nconst vulcan = createClient('/api')\n// or\nconst vulcan = createClient('http://api.localhost')\n// or with auth + extra headers\nconst vulcan = createClient('http://api.localhost', {\n  getToken: async () => session?.accessToken ?? null,\n  headers: { 'X-App': 'web' },\n})\n```\n\n`createClient(baseUrl, options?)` returns a `VulcanClient`. Sub-modules hang off named properties — today `vulcan.storage`; auth/query/realtime will plug in the same way as they ship.\n\n## Result envelope\n\nEvery method returns a Supabase-style `Promise<{ data, error }>`. **Inspect `error` first.** Methods never throw on HTTP failures.\n\n```ts\nconst { data, error } = await vulcan.storage.get(id)\nif (error) {\n  // error: { message, status?, code?, cause? }\n  return showError(error)\n}\ndata // typed payload\n```\n\n## `vulcan.storage`\n\n### `upload(path, file, options?)` — full flow\n\nReserves a path with the server, PUTs the file raw bytes to whatever `uploadUrl` the server returned, then re-fetches the saved metadata. If the PUT fails the half-created row is best-effort cleaned up server-side so the disk and DB stay in sync.\n\nThe SDK PUTs the same way regardless of disk capability — no proxied/signed branching at the call site. The server returns either:\n\n- an **external signed URL** (S3 / GCS) — fast direct-to-storage,\n- or a **same-origin URL** with a server-minted token in the query string (FS driver in dev).\n\nEither way the PUT semantics are identical from the SDK's perspective: raw body, `Content-Type` from the file, no `Authorization` header. The URL is self-authenticating.\n\n```ts\nconst { data, error } = await vulcan.storage.upload(\n  `users/${userId}/avatar.png`,\n  file,\n  {\n    // all optional\n    disk: 'avatars',\n    name: 'avatar.png',          // defaults to file.name\n    contentType: 'image/png',    // defaults to file.type\n    metadata: { uploadedBy: userId },\n    signal: abortController.signal,\n  }\n)\n// data: StorageObject  { id, name, disk, path, mimetype, size, createdAt, updatedAt, metadata }\n```\n\n`disk` is a logical handle that matches a service key in the server's `config/drive.ts` (e.g. `'s3'`, `'gcs'`, `'avatars'`) — *not* the underlying cloud bucket name. `path` is the key inside that disk, relative, no prefix.\n\n### `get(id)` — fetch metadata\n\n```ts\nconst { data, error } = await vulcan.storage.get(id)\n```\n\n### `getDownloadUrl(id, opts?)` — download URL\n\n```ts\nconst { data, error } = await vulcan.storage.getDownloadUrl(id, { expiresInSeconds: 300 })\n// data: { url, expiresIn }\nimgEl.src = data.url\n```\n\nReturns either an external signed URL (S3 / GCS) or a same-origin proxy URL whose query token is the authorization (FS in dev). Either way `data.url` is an **absolute URL** already resolved against the client's `baseUrl` — drop it straight into `<img src>` / `window.location` with no extra headers and no manual origin concatenation.\n\nThe server clamps `expiresInSeconds` to whatever the matching security rule + global ceiling allow.\n\n**Preview a PDF (or image) in a new tab** — pass `disposition: 'inline'` so the server sends `Content-Disposition: inline` instead of forcing a download, then open the URL:\n\n```ts\nconst { data, error } = await vulcan.storage.getDownloadUrl(id, {\n  disposition: 'inline',\n})\nif (data) window.open(data.url, '_blank')\n```\n\nThe browser still picks behavior from `Content-Type`: PDFs and images render inline; types the browser can't preview fall back to download. Default disposition is `'attachment'` (always downloads), which is what you want for a regular Save / Download button.\n\n### `remove(id)` — delete\n\n```ts\nconst { error } = await vulcan.storage.remove(id)\n// data: true\n```\n\n### Lower-level building blocks\n\nFor custom upload flows (e.g. progress tracking, resumable uploads later) the two underlying steps are exposed:\n\n```ts\nconst { data: reservation, error } = await vulcan.storage.reserveUpload(path, {\n  name,\n  contentType,\n  size,\n  disk,\n  metadata,\n})\n// then PUT to reservation.uploadUrl yourself, e.g. using XHR with onprogress\n```\n\n## Options\n\n| Option | Type | Notes |\n|---|---|---|\n| `getToken` | `() => string \\| null \\| Promise<string \\| null>` | Called before each request to set `Authorization: Bearer <token>`. Return `null` for anonymous. |\n| `headers` | `Record<string, string>` or `() => Record<string, string>` | Extra headers applied to every request (e.g. tenant id). |\n| `fetch` | `typeof fetch` | Override the transport. Useful for retries, telemetry, or tests. |\n\nPer-request `headers` (passed via `options`) take precedence over the static ones.\n\n## Error shape\n\n```ts\ntype VulcanError = {\n  message: string\n  status?: number   // HTTP status, when known\n  code?: string     // server-side error code, e.g. 'E_STORAGE_AUTHORIZATION'\n  cause?: unknown   // original Error for network failures\n}\n```\n\nCommon codes from the storage backend:\n\n- `E_INVALID_STORAGE_PATH` (400) — path failed validation\n- `E_STORAGE_AUTHORIZATION` (403) — security rule denied\n- `E_STORAGE_OBJECT_NOT_FOUND` (404) — object id doesn't exist\n\n## License\n\nProprietary — see [LICENSE.md](./LICENSE.md).\n","readmeFilename":"README.md"}