{"_id":"@bozonx/social-posting-vimeo","name":"@bozonx/social-posting-vimeo","dist-tags":{"latest":"0.8.0"},"versions":{"0.8.0":{"name":"@bozonx/social-posting-vimeo","version":"0.8.0","description":"Vimeo platform for @bozonx/social-posting","keywords":["social-media","posting","vimeo"],"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","workerd":"./dist/index.js","import":"./dist/index.js","default":"./dist/index.js"}},"dependencies":{},"peerDependencies":{"@bozonx/social-posting":"^0.8.0"},"devDependencies":{"@bozonx/social-posting-conformance":"0.8.0","@bozonx/social-posting":"0.8.0"},"publishConfig":{"access":"public","provenance":true},"sideEffects":false,"author":"Ivan K","license":"MIT","engines":{"node":">=24.0.0"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json --noEmit"},"_nodeVersion":"24.19.0","_id":"@bozonx/social-posting-vimeo@0.8.0","dist":{"integrity":"sha512-9mdObJMDPURXsjz2wTXGZVhT5P9jRYkyRu6n+l3ZNJUBEZ2dZxY46fwon1b3Ei21Yr/31/TK8pxp6s5NS9XUYg==","shasum":"45d587e5d2a506b03b87062b8c9947a6191a34f2","tarball":"https://registry.npmjs.org/@bozonx/social-posting-vimeo/-/social-posting-vimeo-0.8.0.tgz","fileCount":21,"unpackedSize":87985,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDqc6CcXHaqkbNlCAQnR6FxXaRpOcdYjeFF3icStJK0ZwIhANtF+MIxp4qp7oLWR7odKIM8g0Fl7IJ1ymD0YKAT2RrL"}]},"_npmUser":{"name":"bozonx","email":"ipkozyrin@gmail.com"},"directories":{},"maintainers":[{"name":"bozonx","email":"ipkozyrin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/social-posting-vimeo_0.8.0_1788181104492_0.6092281323708872"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T12:58:24.372Z","0.8.0":"2026-08-31T12:58:24.607Z","modified":"2026-08-31T12:58:24.781Z"},"maintainers":[{"name":"bozonx","email":"ipkozyrin@gmail.com"}],"description":"Vimeo platform for @bozonx/social-posting","keywords":["social-media","posting","vimeo"],"author":"Ivan K","license":"MIT","readme":"# @bozonx/social-posting-vimeo\n\nVimeo support for [`@bozonx/social-posting`](https://www.npmjs.com/package/@bozonx/social-posting).\nZero runtime dependencies, Web APIs only — it runs on Node, Bun, Deno and Cloudflare Workers.\n\n```bash\npnpm add @bozonx/social-posting @bozonx/social-posting-vimeo\n```\n\n```ts\nimport { createPostingClient } from '@bozonx/social-posting';\nimport { vimeo } from '@bozonx/social-posting-vimeo';\n\nconst client = createPostingClient({\n  platforms: [vimeo],\n  accounts: {\n    studio: { platform: 'vimeo', auth: { accessToken: process.env.VIMEO_TOKEN } },\n  },\n});\n\nconst result = await client.post({\n  platform: 'vimeo',\n  account: 'studio',\n  type: 'video',\n  title: 'Release notes, August',\n  body: 'Everything that shipped this month.',\n  media: [{ type: 'video', source: { kind: 'stream', open: openFile, sizeBytes: 812_000_000 } }],\n});\n\n// result.data.status === 'processing' — see below.\n```\n\n## An uploaded video is not a playable video\n\n`publish()` **never** returns `published`. Vimeo stores the file, then transcodes it, and only then\ncan anybody watch it. So `post()` answers `processing` with a handle, and the host polls\n`checkStatus()` until it says otherwise.\n\n`checkStatus()` reads `transcode.status`, not the video's `status` field: a video reads `available`\nwhile its highest-quality rendition is still being produced.\n\nBudget the wait from `capabilities.asyncProcessing` (`maxWaitSecs`, `pollIntervalSecs`) rather than\nfrom a constant of your own.\n\n## Two upload approaches\n\n|                         | `tus` (default)              | `pull`                              |\n| ----------------------- | ---------------------------- | ----------------------------------- |\n| Who moves the bytes     | This process, chunk by chunk | Vimeo, from a URL you give it       |\n| Resumable               | Yes                          | No                                  |\n| Needs the size up front | Yes                          | No                                  |\n| Your bandwidth          | Full file                    | None                                |\n| A broken link surfaces  | Immediately                  | Minutes later, as a transcode error |\n\nChoose per request with `extra.uploadApproach`, or per account with `defaultUploadApproach`.\n\n**If you use `pull`, the URL must stay alive.** The descriptor states\n`urlMustRemainAvailableForSecs: 86400` for exactly this reason: Vimeo fetches the file\nasynchronously, after the create call has already returned success, and a signed link that expires\nwith the request produces a failure with nothing in the response to explain it.\n\n## Storage, not operations\n\nVimeo's limit is the account's **stored bytes** plus a **weekly upload allowance**, both set by the\nplan. Neither resets on a daily clock.\n\n`getQuota()` reports it in bytes, and returns whichever of the two is tighter — a plan with room\nleft can still be out of its weekly allowance, and that is what will actually stop the next upload:\n\n```ts\nconst quota = await client.getQuota({ platform: 'vimeo', account: 'studio' });\n// { unit: 'bytes', remaining: 4294967296, limit: 5368709120, resetsAt: '2026-09-06T…' }\n```\n\nExhaustion arrives as `QUOTA_EXCEEDED` with `retryable: false`: storage does not free itself, and\nthe right message to the user is \"delete something or upgrade\", not \"try again tomorrow\". That is\nthe opposite of YouTube, where the same code means exactly \"try again tomorrow\" —\n`capabilities.rateLimits.quotaCost.unit` (`bytes` against `quotaUnits`) is what distinguishes them.\n\n## Resumable upload\n\nThe `tus` approach uploads by offset, and a failure carries a `resumeHandle`:\n\n```ts\nconst result = await client.post(request, { resume: previousError.resumeHandle });\n```\n\nThe handle names **only the video**, never the tus upload link. That link is a bearer URL — anyone\nholding it can write bytes into the video — and a handle is something the host writes to its\ndatabase. On resume the adapter re-reads the link from `GET /videos/{id}` and asks the tus endpoint\nfor its real offset, because the stored offset is only what was true before the process died.\n\nIf the session is gone by then (the upload completed, or the video was removed), the adapter raises\n`UNKNOWN_OUTCOME` rather than starting a second upload.\n\n## What it publishes\n\n| Field                  | Maps to                                                 |\n| ---------------------- | ------------------------------------------------------- |\n| `title`                | `name` (≤ 128)                                          |\n| `body` / `description` | `description` (≤ 5000)                                  |\n| `tags`                 | `tags[]` (≤ 20)                                         |\n| `visibility`           | `privacy.view` — `public`→`anybody`, `private`→`nobody` |\n| `extra.privacyView`    | `privacy.view` directly, for modes `visibility` lacks   |\n| `extra.folderUri`      | `folder_uri`                                            |\n\nSetting both `visibility` and `extra.privacyView` is refused: they write the same field, and\nletting one win silently publishes at a visibility nobody asked for.\n\n## Known limits\n\n- Vimeo publishes videos only. No text posts, no images, no galleries, no stories — and no Shorts\n  equivalent, so a vertical video is an ordinary upload and `shortVideo` is absent rather than\n  aliased.\n- Upload access depends on the account plan; a plan that forbids it answers `403` as `AUTH_ERROR`,\n  which no scope change fixes.\n- `delete()` is not implemented in this iteration, and `supportsDeletion` says so.\n","readmeFilename":"","_rev":"1-7e66678bbd2ab486b981b1d2e3fc03fa"}