@postrun/js 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-E75IP76L.js → chunk-C4MYARB5.js} +341 -12
- package/dist/chunk-C4MYARB5.js.map +1 -0
- package/dist/{chunk-FH5WJE2D.js → chunk-HY5QFRKO.js} +3 -3
- package/dist/{chunk-FH5WJE2D.js.map → chunk-HY5QFRKO.js.map} +1 -1
- package/dist/index.cjs +349 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +195 -81
- package/dist/query.d.ts +195 -81
- package/dist/query.js.map +1 -1
- package/dist/schemas/index.cjs +339 -9
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +7732 -4193
- package/dist/schemas/index.d.ts +7732 -4193
- package/dist/schemas/index.js +1 -1
- package/dist/server.cjs +338 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/dist/{types.gen-BwX9xx6y.d.cts → types.gen-Ca6kUFtq.d.cts} +139 -25
- package/dist/{types.gen-BwX9xx6y.d.ts → types.gen-Ca6kUFtq.d.ts} +139 -25
- package/package.json +1 -1
- package/dist/chunk-E75IP76L.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/resources.ts","../src/upload/casing.ts","../src/upload/upload-error.ts","../src/upload/upload-file.ts","../src/media.ts","../src/tiktok.ts","../src/linkedin.ts","../src/x.ts","../src/analytics.ts","../src/tiktok-options.ts","../src/compose.ts"],"names":["unit"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConnectionsConnectData,\n ConnectionsConnectResponse,\n ConnectionsGetResponse,\n ConnectionsListAccountsResponse,\n ConnectionsListByProfileData,\n ConnectionsListByProfileResponse,\n ConnectionsSelectData,\n MediaCreateData,\n MediaCreateResponse,\n MediaGetResponse,\n MediaListData,\n MediaListResponse,\n MediaUpdateData,\n PostsCreateData,\n PostsGetResponse,\n PostsListData,\n PostsListResponse,\n PostsUpdateData,\n PostsValidateData,\n PostsValidateResponse,\n ProfilesCreateData,\n ProfilesGetResponse,\n ProfilesListData,\n ProfilesListResponse,\n ProfilesUpdateData,\n TiktokCreatorInfoResponse,\n} from './client/types.gen';\n\n/**\n * Resource and input shapes, DERIVED from the generated Hey API operation types\n * (`*Data` = request `{ body, path, query }`, `*Response` = success body). Never\n * hand-declared — there is exactly one definition of each, sourced from the\n * contract, so `pnpm generate` keeps them in lockstep with the live API.\n */\n\n/** A profile — a client/brand workspace. */\nexport type Profile = ProfilesGetResponse;\n\n/** A page of profiles (the list envelope). */\nexport type ProfileList = ProfilesListResponse;\n\n/** Query parameters for listing profiles (filters + pagination). */\nexport type ListProfilesQuery = NonNullable<ProfilesListData['query']>;\n\n/** Request body to create a profile. */\nexport type CreateProfileInput = NonNullable<ProfilesCreateData['body']>;\n\n/** Request body to update a profile (send only what changes). */\nexport type UpdateProfileInput = NonNullable<ProfilesUpdateData['body']>;\n\n/** A connected social/ads account on a profile. */\nexport type Connection = ConnectionsGetResponse;\n\n/** A page of a profile's connections. */\nexport type ConnectionList = ConnectionsListByProfileResponse;\n\n/** Query filters for listing a profile's connections (kind / status / pagination). */\nexport type ListConnectionsQuery = NonNullable<\n ConnectionsListByProfileData['query']\n>;\n\n/** A connection's kind — `posting` (social) or `ads`. From the contract. */\nexport type ConnectionKind = NonNullable<ListConnectionsQuery['kind']>;\n\n/** A connection's lifecycle status — `pending` / `active` / `needs_reauth`. */\nexport type ConnectionStatus = NonNullable<ListConnectionsQuery['status']>;\n\n/** The session returned when starting a connect (OAuth) flow. */\nexport type ConnectSession = ConnectionsConnectResponse;\n\n/** A platform a connection can start an OAuth connect flow for. */\nexport type ConnectablePlatform =\n NonNullable<ConnectionsConnectData['body']>['platform'];\n\n/** Accounts discoverable on a pending connection, for the selection step. */\nexport type DiscoverableAccountList = ConnectionsListAccountsResponse;\n\n/**\n * A TikTok creator's live publish options for a connection — the data TikTok's\n * publishing UI renders: `creator` (nickname + avatar), the `privacy_options` to\n * offer for PHOTO posts (with NO default — videos publish publicly), the\n * positive `interaction` (allowed) flags for Comment/Duet/Stitch toggles, and\n * `max_video_duration_sec`. Derived from the contract, never hand-declared.\n */\nexport type TikTokCreatorInfo = TiktokCreatorInfoResponse;\n\n/**\n * One TikTok privacy level — a member of the closed audience union\n * (`PUBLIC_TO_EVERYONE` / `MUTUAL_FOLLOW_FRIENDS` / `FOLLOWER_OF_CREATOR` /\n * `SELF_ONLY`). The per-account ALLOWED subset is `TikTokCreatorInfo.privacy_options`;\n * this is one value from it (what you bind a dropdown option to). Derived from\n * the contract, never hand-declared; render it with {@link tiktokPrivacyLabel}.\n */\nexport type TikTokPrivacyLevel = TikTokCreatorInfo['privacy_options'][number];\n\n/** Request body to select an account on a pending connection. */\nexport type SelectAccountInput = NonNullable<ConnectionsSelectData['body']>;\n\n/** The create/update post body, derived from the contract. */\ntype CreatePostBody = NonNullable<PostsCreateData['body']>;\n\n/**\n * One channel's variant on WRITE — the per-platform discriminated-union member\n * with fully typed native `settings`. Preferred over the read resource's variant\n * for anything that inspects settings (on read, `settings` is an opaque record);\n * it is also the compose-time shape, which is exactly what a live preview renders.\n */\nexport type PostVariantInput = CreatePostBody['variants'][number];\n\n/** The X (Twitter) member of the write variant union — typed native settings\n * (`quote_tweet_id`, `reply`, `poll`, …). Narrowed from the contract, never\n * hand-declared. */\nexport type XPostVariant = Extract<PostVariantInput, { platform: 'x' }>;\n\n/** The LinkedIn member of the write variant union — typed native settings\n * (`visibility`, `article`, `poll`, `document`, `mentions`). The `content_kind`\n * shape is server-DERIVED and read-only (see {@link PostVariant}). Narrowed from\n * the contract, never hand-declared. */\nexport type LinkedInPostVariant = Extract<\n PostVariantInput,\n { platform: 'linkedin' }\n>;\n\n/** The TikTok member of the write variant union — typed native settings\n * (photo-only `privacy_level`, `disable_comment`/`disable_duet`/`disable_stitch`,\n * `is_branded_content`/`is_brand_organic`, `is_ai_generated`, photo/video cover).\n * Narrowed from the contract, never hand-declared — it is the compose-time shape a\n * live TikTok preview renders. */\nexport type TikTokPostVariant = Extract<\n PostVariantInput,\n { platform: 'tiktok' }\n>;\n\n/** The Instagram member of the write variant union — typed native settings\n * (`collaborators`, `user_tags`, `location_id`, and the reel-only\n * `share_to_feed`/`audio_name`/`cover_url`/`thumb_offset`). The `media_type` shape\n * is server-DERIVED and read-only (see {@link PostVariant}). Narrowed from the\n * contract, never hand-declared — the compose-time shape an IG preview renders. */\nexport type InstagramPostVariant = Extract<\n PostVariantInput,\n { platform: 'instagram' }\n>;\n\n/** A post — its variants, schedule, and derived status (the read resource). */\nexport type Post = PostsGetResponse;\n\n/**\n * One per-platform variant on a RETURNED post (the read resource): its own\n * `status`, a typed `error` ({@link PostVariantError}) when it failed, and a\n * `result` (platform id + permalink) when it published. Derived from the post\n * resource, never hand-declared. (For inspecting compose-time native `settings`,\n * prefer {@link PostVariantInput}; on read `settings` is opaque.)\n */\nexport type PostVariant = Post['variants'][number];\n\n/**\n * A variant's typed publish failure — `{ code, message }`. The `code` is the\n * adapter's own minted reason (`x_access_not_permitted`, `linkedin_auth_expired`,\n * …); it stays a `string` because adapter codes are minted outside the API's\n * closed registry. Derived from the variant, never hand-declared.\n */\nexport type PostVariantError = NonNullable<PostVariant['error']>;\n\n/**\n * Did the post fully publish? `true` ONLY when the rollup `status` is\n * `'published'` — `partially_published` and `failed` are NOT success. The single\n * pure predicate every surface (SDK, React hook, composer) shares, so \"success\"\n * has exactly one definition.\n */\nexport const isPublished = (post: Post): boolean =>\n post.status === 'published';\n\n/**\n * The variants that FAILED to publish, each carrying its typed {@link\n * PostVariantError}. Empty when none failed. The pure helper behind\n * `useCreatePost().failedVariants` — usable directly from a non-React caller so\n * the \"which platforms failed, and why\" projection has one definition.\n */\nexport const failedVariants = (post: Post): PostVariant[] =>\n post.variants.filter((variant) => variant.status === 'failed');\n\n/** A page of posts (the calendar/queue list). */\nexport type PostList = PostsListResponse;\n\n/** Query parameters for listing posts (filters + pagination). */\nexport type ListPostsQuery = NonNullable<PostsListData['query']>;\n\n/**\n * A post's derived rollup status — the values accepted by the list `status`\n * filter (draft / scheduled / publishing / partially_published / published /\n * failed). Sourced from the generated query contract, never hand-listed.\n */\nexport type PostStatus = NonNullable<ListPostsQuery['status']>[number];\n\n/** The full create-post request body — what `buildCreatePost` produces. */\nexport type CreatePostInput = CreatePostBody;\n\n/** The update-post request body — what `buildUpdatePost` produces. */\nexport type UpdatePostInput = NonNullable<PostsUpdateData['body']>;\n\n/**\n * The validate-post request body — the composition-only subset of the create\n * body (`profile_id` + `variants`). `buildCreatePost` produces a superset, so\n * its output is structurally assignable here. Derived from the contract.\n */\nexport type ValidatePostInput = NonNullable<PostsValidateData['body']>;\n\n/**\n * The server's publish-readiness verdict for a composed post — `{ object,\n * publishable, issues }`. The SERVER is the sole authority on validity; the SDK\n * only builds a best-effort payload and relays this result. Derived from the\n * contract, never hand-declared.\n */\nexport type PostValidation = PostsValidateResponse;\n\n/**\n * One typed, per-variant problem in a `PostValidation` — a closed-registry\n * `code`, a human `message`, the `variant_index` + `path` it applies to, and the\n * optional `hint`/`allowed`/`got` the rule produced. Narrowed from the contract.\n */\nexport type ValidationIssue = PostValidation['issues'][number];\n\n/** A posting platform — the variant discriminator (x / linkedin / …). */\nexport type PostPlatform = PostVariantInput['platform'];\n\n/**\n * The post shape (`text` / `single_image` / `multi_image` / `video` / `reel` /\n * `carousel`) — DERIVED server-side from the attached media, never sent on write.\n * It surfaces on the READ variant (`PostVariant.post_type`, null while\n * undetermined); this strips that null to the concrete union. Derived from the\n * contract, never hand-listed.\n */\nexport type PostType = NonNullable<PostVariant['post_type']>;\n\n/** The native `settings` shape for a given platform (typed per channel). */\nexport type SettingsFor<P extends PostPlatform> = Extract<\n PostVariantInput,\n { platform: P }\n>['settings'];\n\n/** Publish mode: `now` | `schedule` | `draft`. */\nexport type PublishMode = NonNullable<CreatePostBody['publish']>;\n\n/** Customer metadata on a post (the shared scalar map). */\nexport type PostMetadata = NonNullable<CreatePostBody['metadata']>;\n\n/** A media asset (image/video/gif/document) with its per-platform renditions. */\nexport type MediaResource = MediaGetResponse;\n\n/**\n * A media asset's kind (image / video / gif / document), from the contract. The\n * resource's `kind` is nullable only in the transient pre-detection `uploading`\n * window; this is the concrete family, so it strips that null — what you pass to\n * create/upload and switch over on a settled asset.\n */\nexport type MediaKind = NonNullable<MediaResource['kind']>;\n\n/** A media render target — one of the post platforms. */\nexport type MediaTarget = NonNullable<\n NonNullable<MediaCreateData['body']>['targets']\n>[number];\n\n/** The create-media response: the resource plus a multipart upload session. */\nexport type CreateMediaResult = MediaCreateResponse;\n\n/** Request body to create a media asset. */\nexport type CreateMediaInput = NonNullable<MediaCreateData['body']>;\n\n/** Request body to update a media asset (alt text / metadata / extend targets). */\nexport type UpdateMediaInput = NonNullable<MediaUpdateData['body']>;\n\n/** A page of media assets (the list envelope). */\nexport type MediaList = MediaListResponse;\n\n/** Query parameters for listing media (filters + pagination). */\nexport type ListMediaQuery = NonNullable<MediaListData['query']>;\n\n/**\n * A media asset's lifecycle state — the values the list `status` filter accepts\n * (uploading / processing / ready / failed). From the contract, never hand-listed.\n */\nexport type MediaStatus = NonNullable<ListMediaQuery['status']>;\n\n/**\n * Customer metadata — a flat scalar map (`string | number | boolean`; ≤50 keys,\n * keys ≤40 chars, ≤500-char strings, 16 KiB serialized). The contract uses this\n * SAME shape on every stored resource (profiles / posts / media); this alias is\n * derived from the media write body as a stable representative, so it's the type\n * to annotate metadata you send — never `Record<string, unknown>`. (The three\n * resources share one structural shape in the spec; were the API ever to diverge\n * one, prefer that resource's own `*['metadata']` for it.)\n */\nexport type Metadata = NonNullable<CreateMediaInput['metadata']>;\n\n/**\n * A metadata filter for list endpoints — the same scalar map, matched by exact\n * containment (multiple keys ANDed; omit for no filter). Pass the object directly\n * from the SDK/React hooks; the client URL-encodes it for the REST query.\n */\nexport type MetadataFilter = NonNullable<ListMediaQuery['metadata']>;\n","import type { AwsS3Part } from '@uppy/aws-s3';\n\nimport type {\n MediaCompleteData,\n MediaListPartsResponse,\n} from '../client/types.gen';\n\n/**\n * The casing seam between Uppy's `@uppy/aws-s3` callbacks (which speak S3\n * PascalCase — `{ PartNumber, ETag, Size }`) and our endpoints (which speak the\n * API's snake_case — `{ part_number, etag, size }`). Uppy reads the parts we hand\n * it back from `listParts` and hands us the parts to send to `complete`, so a\n * silent mismatch here would break resume/complete with no error. Both maps are\n * pure and tested; the shapes are DERIVED from the generated client + Uppy types,\n * never hand-declared.\n */\n\n/** One part as our `GET /media/{id}/multipart/parts` endpoint returns it. */\ntype ApiListedPart = MediaListPartsResponse['parts'][number];\n\n/** One part as our `POST /media/{id}/multipart/complete` body carries it. */\ntype ApiCompletePart = NonNullable<MediaCompleteData['body']>['parts'][number];\n\n/**\n * Map our snake_case listed parts → Uppy's S3 `AwsS3Part[]` so a resume after a\n * dropped connection skips the parts R2 already holds. A 1:1 field map — the\n * endpoint's `parts` contract guarantees `part_number`/`etag`/`size` on every\n * entry, so there's nothing to drop.\n */\nexport function listedPartsToS3(\n parts: readonly ApiListedPart[],\n): AwsS3Part[] {\n return parts.map((part) => ({\n PartNumber: part.part_number,\n ETag: part.etag,\n Size: part.size,\n }));\n}\n\n/**\n * Map Uppy's S3 `AwsS3Part[]` (handed to `completeMultipartUpload`) → our\n * snake_case `complete` body parts. `AwsS3Part` types `PartNumber`/`ETag` as\n * optional; a part Uppy completes with always has both, but we assert the\n * invariant explicitly rather than cast — a missing field is a hard error, not a\n * silently-dropped part.\n */\nexport function s3PartsToComplete(\n parts: readonly AwsS3Part[],\n): ApiCompletePart[] {\n return parts.map((part) => {\n if (part.PartNumber === undefined || part.ETag === undefined) {\n throw new Error(\n 'Multipart complete received a part without PartNumber/ETag.',\n );\n }\n return { part_number: part.PartNumber, etag: part.ETag };\n });\n}\n","/**\n * A failed direct-to-storage (multipart) upload. `cause` carries the underlying\n * Uppy / network / API error so callers can inspect the real reason; `message` is\n * a short human summary. Thrown by {@link uploadFile} on a hard upload failure\n * (one or more parts could not be uploaded, or `complete` failed).\n */\nexport class UploadError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'UploadError';\n }\n}\n","import Uppy from '@uppy/core';\nimport AwsS3 from '@uppy/aws-s3';\n\nimport type { PostrunClient } from '../client';\nimport {\n mediaAbort,\n mediaComplete,\n mediaListParts,\n mediaSignPart,\n} from '../client/sdk.gen';\nimport type { MediaCreateResponse } from '../client/types.gen';\nimport { listedPartsToS3, s3PartsToComplete } from './casing';\nimport { UploadError } from './upload-error';\n\n/**\n * The multipart session the create response carries on its `upload` field — the\n * cached `{ upload_id, key, part_size, expires_at }` Uppy's `createMultipartUpload`\n * returns without a second HTTP call. Derived from the contract, never declared.\n */\nexport type MultipartSession = NonNullable<MediaCreateResponse['upload']>;\n\nexport interface UploadFileOptions {\n /** The media id the create call returned — binds the multipart sub-endpoints. */\n mediaId: string;\n /** The cached `upload` session from the create response (`upload_id`/`key`/…). */\n session: MultipartSession;\n /** The typed client the sub-endpoint calls go through (auth + base URL). */\n client: PostrunClient;\n /** Fraction uploaded, 0..1, as bytes flow; called once with 1 on success. */\n onProgress?: (fraction: number) => void;\n /** Abort the in-flight upload (cancels Uppy and aborts the R2 session). */\n signal?: AbortSignal;\n}\n\n/**\n * Upload ONE file's bytes to R2 with a resilient, resumable multipart upload,\n * driving `@uppy/aws-s3` headless (no DOM). Uppy owns the bug-prone mechanics —\n * fixed-size chunking, parallel parts, per-part retry, resume via `listParts`,\n * and progress — wired to OUR signing endpoints (`createMultipartUpload` returns\n * the cached create-response session, so there's no extra round-trip to start).\n *\n * Resolves when the upload is fully assembled on R2 (the object then settles\n * through the same probe/transform pipeline a `source_url` import does). Throws\n * {@link UploadError} on a hard failure, or a `DOMException('AbortError')` when\n * `signal` fires.\n */\nexport async function uploadFile(\n file: Blob,\n options: UploadFileOptions,\n): Promise<void> {\n const { mediaId, session, client, onProgress, signal } = options;\n\n if (signal?.aborted) {\n throw new DOMException('Upload aborted', 'AbortError');\n }\n\n const uppy = new Uppy({ autoProceed: false }).use(AwsS3, {\n // Every file is one resumable multipart upload — no size-threshold split, so\n // the path is uniform and a tiny file is just a 1-part upload (PLAN §2). The\n // literal `true` (not a `() => true`) selects Uppy's always-multipart variant.\n shouldUseMultipart: true,\n // Pin Uppy's chunk size to the server's part size — every part except the\n // last MUST be exactly this (an R2 requirement the session encodes).\n getChunkSize: () => session.part_size,\n\n // Start: hand back the cached session from the create response — NO HTTP.\n createMultipartUpload: () => ({\n uploadId: session.upload_id,\n key: session.key,\n }),\n\n // Sign one part → POST /media/{id}/multipart/sign. The sub-endpoint reads the\n // upload id from the asset row, so the body carries only the part number.\n signPart: async (_file, { partNumber }) => {\n const { data } = await mediaSignPart({\n client,\n path: { id: mediaId },\n body: { part_number: partNumber },\n });\n return { url: data.url };\n },\n\n // Resume: GET /media/{id}/multipart/parts → map snake_case → S3 PascalCase so\n // Uppy skips the parts R2 already holds after a dropped connection.\n listParts: async () => {\n const { data } = await mediaListParts({\n client,\n path: { id: mediaId },\n });\n return listedPartsToS3(data.parts);\n },\n\n // Finish: map Uppy's S3 parts → snake_case → POST /media/{id}/multipart/complete.\n completeMultipartUpload: async (_file, { parts }) => {\n await mediaComplete({\n client,\n path: { id: mediaId },\n body: { parts: s3PartsToComplete(parts) },\n });\n return {};\n },\n\n // Cancel: POST /media/{id}/multipart/abort (idempotent server-side).\n abortMultipartUpload: async () => {\n await mediaAbort({ client, path: { id: mediaId } });\n },\n });\n\n // Uppy's `progress` event is the overall 0–100 across all parts of the queue\n // (one file here) — forward it as a 0..1 fraction.\n uppy.on('progress', (percent) => onProgress?.(percent / 100));\n\n const onAbort = () => {\n uppy.cancelAll();\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n try {\n uppy.addFile({ name: fileName(file), type: file.type, data: file });\n const result = await uppy.upload();\n\n if (signal?.aborted) {\n throw new DOMException('Upload aborted', 'AbortError');\n }\n\n const failed = result?.failed ?? [];\n if (failed.length > 0) {\n throw new UploadError('Multipart upload failed.', {\n cause: failed[0]?.error,\n });\n }\n\n onProgress?.(1);\n } finally {\n signal?.removeEventListener('abort', onAbort);\n uppy.destroy();\n }\n}\n\n/** Uppy needs a file name; a bare `Blob` has none, so fall back to a stable one. */\nfunction fileName(file: Blob): string {\n return file instanceof File ? file.name : 'upload';\n}\n","import pWaitFor from 'p-wait-for';\n\nimport type { PostrunClient } from './client';\nimport { mediaGet } from './client/sdk.gen';\nimport type { MediaResource } from './resources';\n\n/** Default delay between status polls. */\nconst DEFAULT_POLL_INTERVAL_MS = 1_500;\n\n/**\n * Default ceiling before giving up. Server-side video transcode can legitimately\n * take many minutes on a busy worker, so this backstop is generous — a short one\n * marks still-processing-but-eventually-ready videos as a timeout. 30 minutes is\n * a backstop, NOT the expectation; assets normally settle in seconds-to-minutes.\n */\nconst DEFAULT_POLL_TIMEOUT_MS = 30 * 60 * 1_000;\n\nexport interface WaitForMediaOptions {\n /** Typed client to poll through; falls back to the SDK's default client. */\n client?: PostrunClient;\n /** Milliseconds between polls (default 1500). */\n pollInterval?: number;\n /** Milliseconds before giving up and rejecting (default 30 minutes). */\n timeout?: number;\n /** Abort the wait early. */\n signal?: AbortSignal;\n /** Each polled snapshot — surface `progress.percent` while it processes. */\n onPoll?: (media: MediaResource) => void;\n}\n\n/**\n * Resolve once a media asset stops processing. Polls `GET /v1/media/{id}` until\n * `status` is `ready` OR `failed`, returning that settled resource — a `failed`\n * asset RESOLVES (the caller inspects `status`/`error`), it does not throw. Rejects\n * on timeout or when `signal` aborts (a `DOMException('…', 'AbortError')`).\n */\nexport async function waitForMedia(\n mediaId: string,\n options: WaitForMediaOptions = {},\n): Promise<MediaResource> {\n const {\n client,\n pollInterval = DEFAULT_POLL_INTERVAL_MS,\n timeout = DEFAULT_POLL_TIMEOUT_MS,\n signal,\n onPoll,\n } = options;\n\n let latest: MediaResource | undefined;\n await pWaitFor(\n async () => {\n if (signal?.aborted) {\n throw new DOMException('Media wait aborted', 'AbortError');\n }\n latest = (await mediaGet({ client, path: { id: mediaId } })).data;\n onPoll?.(latest);\n return latest.status === 'ready' || latest.status === 'failed';\n },\n { interval: pollInterval, timeout },\n );\n\n if (!latest) {\n throw new Error('Media polling returned no result.');\n }\n return latest;\n}\n","import type { TikTokPrivacyLevel } from './resources';\n\n/**\n * Map a TikTok privacy level to a short, human label for the composer's privacy\n * dropdown — the presentation the API deliberately keeps OFF the wire (the\n * `creator-info` response returns the typed `privacy_options`; the SDK owns how\n * they read).\n *\n * Exhaustive over the closed `TikTokPrivacyLevel` union with no `default` branch,\n * so adding a TikTok privacy level upstream surfaces as a compile error here\n * rather than silently rendering a raw enum string.\n */\nexport function tiktokPrivacyLabel(value: TikTokPrivacyLevel): string {\n switch (value) {\n case 'PUBLIC_TO_EVERYONE':\n return 'Everyone';\n case 'MUTUAL_FOLLOW_FRIENDS':\n return 'Friends';\n case 'FOLLOWER_OF_CREATOR':\n return 'Followers';\n case 'SELF_ONLY':\n return 'Only you';\n }\n}\n","import type { LinkedInPostVariant } from './resources';\n\n/**\n * One LinkedIn poll duration — DERIVED from the contract (the poll `duration`\n * field), never hand-listed, so it can't drift from the API shape.\n */\nexport type LinkedInPollDuration = NonNullable<\n NonNullable<LinkedInPostVariant['settings']>['poll']\n>['duration'];\n\n/**\n * Map a poll duration to the short \"time left\" label LinkedIn shows on the poll\n * card — the presentation the wire keeps enum-only. Exhaustive over the closed\n * union with no `default` branch, so adding a duration upstream surfaces as a\n * compile error here rather than silently rendering a raw enum string.\n */\nexport function linkedinPollDurationLabel(value: LinkedInPollDuration): string {\n switch (value) {\n case 'ONE_DAY':\n return '1 day left';\n case 'THREE_DAYS':\n return '3 days left';\n case 'SEVEN_DAYS':\n return '1 week left';\n case 'FOURTEEN_DAYS':\n return '2 weeks left';\n }\n}\n","import type { XPostVariant } from './resources';\n\n/** An X poll's settings shape — DERIVED from the contract, never re-declared. */\nexport type XPoll = NonNullable<\n NonNullable<XPostVariant['settings']>['poll']\n>;\n\n/** `n unit left`, pluralizing the unit. */\nfunction plural(n: number, unit: string): string {\n return `${n} ${unit}${n === 1 ? '' : 's'} left`;\n}\n\n/**\n * Turn an X poll's `duration_minutes` (5–10080, i.e. 5 min – 7 days) into the\n * \"time left\" label the poll card shows, floored to the coarsest sensible unit\n * (days ≥ 1 day, else hours ≥ 1 hour, else minutes). Pure — pin it in tests.\n */\nexport function xPollDurationLabel(durationMinutes: number): string {\n if (durationMinutes >= 1440) {\n return plural(Math.floor(durationMinutes / 1440), 'day');\n }\n if (durationMinutes >= 60) {\n return plural(Math.floor(durationMinutes / 60), 'hour');\n }\n return plural(durationMinutes, 'minute');\n}\n","import type {\n AccountMetrics,\n FacebookPageMetrics,\n FacebookPostMetrics,\n InstagramAccountMetrics,\n InstagramPostMetrics,\n PostMetrics,\n PostVariantStatus,\n TikTokAccountMetrics,\n TikTokPostMetrics,\n} from './client/types.gen';\n// From `./resources`, which re-declares these names: importing the generated\n// ones too gives the d.ts bundle two symbols under one exported name, and\n// @postrun/react's inferred hook types then fail TS2742.\nimport type { PostPlatform, PostType } from './resources';\n\n/**\n * Presentation helpers for the analytics reads (`postsGetAnalytics`,\n * `connectionsGetAnalytics`). The wire keeps each network's own field names;\n * these carry the domain knowledge a UI needs on top: which metrics apply to a\n * post type, their labels, and their units. Pure — pin them in tests.\n */\n\n/** How to read a metric's number: `ratio` is 0–1, `percent` is 0–100. */\nexport type MetricFormat =\n | 'count'\n | 'seconds'\n | 'milliseconds'\n | 'ratio'\n | 'percent';\n\n/** A count, a breakdown keyed by the network's own type names, or `null` when\n * the network didn't report it. */\nexport type MetricValue = number | Record<string, number> | null;\n\nexport interface MetricRow {\n key: string;\n label: string;\n format: MetricFormat;\n value: MetricValue;\n}\n\nexport interface MetricPoint {\n date: string;\n value: number;\n}\n\nconst ANALYTICS_PLATFORMS = [\n 'tiktok',\n 'instagram',\n 'facebook_page',\n] as const satisfies readonly PostPlatform[];\n\nexport type AnalyticsPlatform = (typeof ANALYTICS_PLATFORMS)[number];\n\n/** Whether Postrun reads analytics for this network. */\nexport function hasAnalytics(platform: string): platform is AnalyticsPlatform {\n return ANALYTICS_PLATFORMS.some((supported) => supported === platform);\n}\n\n/** Only a published variant on a network with analytics has metrics to read.\n * Narrows `platform`, so the variant can go straight to code keyed by\n * `AnalyticsPlatform`. */\nexport function hasPostAnalytics<\n Variant extends { platform: PostPlatform; status: PostVariantStatus },\n>(variant: Variant): variant is Variant & { platform: AnalyticsPlatform } {\n return variant.status === 'published' && hasAnalytics(variant.platform);\n}\n\ninterface MetricField<Key> {\n key: Key;\n label: string;\n format?: MetricFormat;\n}\n\ntype Fields<Metrics> = readonly MetricField<\n Exclude<keyof Metrics, 'platform'>\n>[];\n\n// `total_time_watched` is left out: next to views and average watch time it\n// adds nothing a reader acts on.\nconst TIKTOK_POST: Fields<TikTokPostMetrics> = [\n { key: 'video_views', label: 'Views' },\n { key: 'reach', label: 'Reach' },\n { key: 'likes', label: 'Likes' },\n { key: 'comments', label: 'Comments' },\n { key: 'shares', label: 'Shares' },\n { key: 'favorites', label: 'Saves' },\n { key: 'average_time_watched', label: 'Avg. watch time', format: 'seconds' },\n { key: 'full_video_watched_rate', label: 'Completion', format: 'ratio' },\n { key: 'new_followers', label: 'New followers' },\n { key: 'profile_views', label: 'Profile views' },\n];\n\nconst INSTAGRAM_SHARED: Fields<InstagramPostMetrics> = [\n { key: 'views', label: 'Views' },\n { key: 'reach', label: 'Reach' },\n { key: 'likes', label: 'Likes' },\n { key: 'comments', label: 'Comments' },\n { key: 'shares', label: 'Shares' },\n { key: 'saved', label: 'Saves' },\n { key: 'reposts', label: 'Reposts' },\n { key: 'total_interactions', label: 'Interactions' },\n];\n\n// Meta reports these for single image and carousel posts only.\nconst INSTAGRAM_FEED: Fields<InstagramPostMetrics> = [\n ...INSTAGRAM_SHARED,\n { key: 'follows', label: 'Follows' },\n { key: 'profile_visits', label: 'Profile visits' },\n { key: 'profile_activity', label: 'Profile activity' },\n];\n\nconst INSTAGRAM_REEL: Fields<InstagramPostMetrics> = [\n ...INSTAGRAM_SHARED,\n {\n key: 'ig_reels_avg_watch_time',\n label: 'Avg. watch time',\n format: 'milliseconds',\n },\n {\n key: 'ig_reels_video_view_total_time',\n label: 'Watch time',\n format: 'milliseconds',\n },\n { key: 'reels_skip_rate', label: 'Skip rate', format: 'percent' },\n];\n\nconst FACEBOOK_FEED: Fields<FacebookPostMetrics> = [\n { key: 'post_media_view', label: 'Views' },\n { key: 'post_total_media_view_unique', label: 'Viewers' },\n { key: 'post_reactions_by_type_total', label: 'Reactions' },\n { key: 'post_clicks', label: 'Clicks' },\n { key: 'post_clicks_by_type', label: 'Clicks by type' },\n { key: 'comments', label: 'Comments' },\n { key: 'shares', label: 'Shares' },\n];\n\nconst FACEBOOK_REEL: Fields<FacebookPostMetrics> = [\n { key: 'blue_reels_play_count', label: 'Plays' },\n { key: 'fb_reels_total_plays', label: 'Total plays' },\n { key: 'fb_reels_replay_count', label: 'Replays' },\n {\n key: 'post_video_avg_time_watched',\n label: 'Avg. watch time',\n format: 'milliseconds',\n },\n { key: 'post_video_view_time', label: 'Watch time', format: 'milliseconds' },\n { key: 'post_video_followers', label: 'New followers' },\n { key: 'post_video_likes_by_reaction_type', label: 'Reactions' },\n];\n\nfunction rowsOf<Key extends string>(\n fields: readonly MetricField<Key>[],\n metrics: Record<Key, MetricValue>,\n): MetricRow[] {\n return fields.map(({ key, label, format = 'count' }) => ({\n key,\n label,\n format,\n value: metrics[key],\n }));\n}\n\n/**\n * The metrics that apply to a post, in display order. Meta reports different\n * metrics for Reels than for other posts, so `postType` (the variant's\n * `post_type`) picks the set; a metric that doesn't apply is left out rather\n * than shown as unreported.\n */\nexport function postMetricRows(\n metrics: PostMetrics,\n postType: PostType | null,\n): MetricRow[] {\n switch (metrics.platform) {\n case 'tiktok':\n return rowsOf(TIKTOK_POST, metrics);\n case 'instagram':\n return rowsOf(\n postType === 'reel' ? INSTAGRAM_REEL : INSTAGRAM_FEED,\n metrics,\n );\n case 'facebook_page':\n return rowsOf(\n postType === 'reel' ? FACEBOOK_REEL : FACEBOOK_FEED,\n metrics,\n );\n }\n}\n\nconst TIKTOK_ACCOUNT: Fields<Omit<TikTokAccountMetrics, 'daily'>> = [\n { key: 'followers_count', label: 'Followers' },\n { key: 'following_count', label: 'Following' },\n { key: 'total_likes', label: 'Likes' },\n { key: 'videos_count', label: 'Videos' },\n];\n\nconst INSTAGRAM_ACCOUNT: Fields<\n Omit<InstagramAccountMetrics, 'daily' | 'last_30_days'>\n> = [\n { key: 'followers_count', label: 'Followers' },\n { key: 'follows_count', label: 'Following' },\n { key: 'media_count', label: 'Posts' },\n];\n\nconst INSTAGRAM_LAST_30_DAYS: Fields<InstagramAccountMetrics['last_30_days']> =\n [\n { key: 'views', label: 'Views · 30d' },\n { key: 'reach', label: 'Reach · 30d' },\n { key: 'accounts_engaged', label: 'Engaged · 30d' },\n { key: 'total_interactions', label: 'Interactions · 30d' },\n ];\n\nconst FACEBOOK_ACCOUNT: Fields<Omit<FacebookPageMetrics, 'daily'>> = [\n { key: 'followers_count', label: 'Followers' },\n];\n\n/** An account's headline numbers, in display order. */\nexport function accountMetricRows(metrics: AccountMetrics): MetricRow[] {\n switch (metrics.platform) {\n case 'tiktok':\n return rowsOf(TIKTOK_ACCOUNT, metrics);\n case 'instagram':\n return [\n ...rowsOf(INSTAGRAM_ACCOUNT, metrics),\n ...rowsOf(INSTAGRAM_LAST_30_DAYS, metrics.last_30_days),\n ];\n case 'facebook_page':\n return rowsOf(FACEBOOK_ACCOUNT, metrics);\n }\n}\n\nfunction pointsOf<Key extends string>(\n daily: ReadonlyArray<{ date: string } & Record<Key, number | null>>,\n key: Key,\n): MetricPoint[] {\n return daily.flatMap((day) => {\n const value = day[key];\n\n return value === null ? [] : [{ date: day.date, value }];\n });\n}\n\n/**\n * The account's main daily series for a trend line, oldest first, leaving out\n * days the network didn't report. Instagram's is reach: it is the only account\n * metric Meta reports per day.\n */\nexport function accountTrend(metrics: AccountMetrics): {\n label: string;\n points: MetricPoint[];\n} {\n switch (metrics.platform) {\n case 'tiktok':\n return {\n label: 'Followers',\n points: pointsOf(metrics.daily, 'followers_count'),\n };\n case 'instagram':\n return { label: 'Reach', points: pointsOf(metrics.daily, 'reach') };\n case 'facebook_page':\n return {\n label: 'Followers',\n points: pointsOf(metrics.daily, 'page_follows'),\n };\n }\n}\n\n// Pinned to en-US to match the English labels: a reader's locale would turn\n// \"1.2K\" into \"12L\" (en-IN) next to them.\nconst COUNT = new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n});\nconst PERCENT = new Intl.NumberFormat('en-US', {\n style: 'percent',\n maximumFractionDigits: 1,\n});\nconst unit = (name: 'second' | 'minute' | 'hour') =>\n new Intl.NumberFormat('en-US', {\n style: 'unit',\n unit: name,\n unitDisplay: 'narrow',\n maximumFractionDigits: 1,\n });\nconst SECONDS = unit('second');\nconst MINUTES = unit('minute');\nconst HOURS = unit('hour');\n\n// Compared after rounding to the shown precision, so 59.96 s reads \"1m\", not \"60s\".\nconst roundsBelow = (value: number, limit: number) =>\n Math.round(value * 10) / 10 < limit;\n\nfunction formatDuration(seconds: number): string {\n if (roundsBelow(seconds, 60)) {\n return SECONDS.format(seconds);\n }\n\n const minutes = seconds / 60;\n\n return roundsBelow(minutes, 60)\n ? MINUTES.format(minutes)\n : HOURS.format(minutes / 60);\n}\n\nfunction formatNumber(value: number, format: MetricFormat): string {\n switch (format) {\n case 'count':\n return COUNT.format(value);\n case 'seconds':\n return formatDuration(value);\n case 'milliseconds':\n return formatDuration(value / 1000);\n case 'ratio':\n return PERCENT.format(value);\n case 'percent':\n return PERCENT.format(value / 100);\n }\n}\n\nconst capitalize = (text: string) =>\n text.charAt(0).toUpperCase() + text.slice(1);\n\n/**\n * A metric for display. `null` (not reported) reads \"—\", never a made-up 0. A\n * breakdown lists its types largest first: \"Like 1.2K · Love 3\".\n */\nexport function formatMetric(\n value: MetricValue,\n format: MetricFormat = 'count',\n): string {\n if (value === null) {\n return '—';\n }\n\n if (typeof value === 'number') {\n return formatNumber(value, format);\n }\n\n const entries = Object.entries(value).sort(([, a], [, b]) => b - a);\n\n return entries.length === 0\n ? '0'\n : entries\n .map(([type, count]) => `${capitalize(type)} ${COUNT.format(count)}`)\n .join(' · ');\n}\n","import type {\n MediaResource,\n Post,\n PostType,\n SettingsFor,\n TikTokCreatorInfo,\n} from './resources';\nimport { type TikTokPrivacyLevel } from './resources';\nimport { tiktokPrivacyLabel } from './tiktok';\n\n/**\n * The TikTok composer's publish-options model — the framework-agnostic glue\n * between the live `creator-info` payload and a TikTok post's settings. This is\n * the SINGLE source for the audience / creator-gated interaction / commercial\n * disclosure rules; the React panel and the dashboard composer both consume it\n * (never re-implement it). The no-default audience and explicit-consent flow are\n * Postrun's own UX choices, not API requirements.\n *\n * The creator-info shape, the privacy union, and the privacy label come from the\n * contract (`./resources`, `./tiktok`) — never re-declared. This module owns the\n * controlled FORM value, the selection rules, and the mapping into the SDK's\n * per-platform `tiktok` settings.\n */\n\n/** TikTok caption caps, mirrored from the publish contract (`BODY_MAX.tiktok_*`).\n * A video post caps the title (2200), a photo post the description (4000). Lives\n * here (not in React) so non-React consumers (MCP, API client, scripts) can use\n * it. The server enforces these; the editor surfaces them. */\nexport const TIKTOK_CAPTION_MAX = { video: 2200, photo: 4000 } as const;\n\n/** The caption cap for a given post_type (video → title cap, photo → description).\n * `post_type` is server-derived from the media (read-only) — the composer passes\n * the shape it knows it's building (a video post vs a photo post). */\nexport function captionMaxFor(postType: PostType): number {\n return postType === 'video'\n ? TIKTOK_CAPTION_MAX.video\n : TIKTOK_CAPTION_MAX.photo;\n}\n\n/** The creator-gated interactions we surface — DERIVED from the creator-info\n * contract (`interaction` keys), never hand-listed, so it can never drift from\n * the API shape. */\nexport type TikTokInteractionKey = keyof TikTokCreatorInfo['interaction'];\n\n/** Which commercial-disclosure brand option a control maps to. */\nexport type TikTokBrandKind = 'your_brand' | 'branded_content';\n\n/**\n * The controlled value a TikTok composer/preview owns. `privacy_level` is\n * PHOTO-ONLY (TikTok publishes every video publicly) and optional because we\n * make the user pick — there is NO default (an absent value is the \"must choose\"\n * state the photo publish gate keys off). Interaction flags are POSITIVE\n * (\"allow\"); `tiktokSettings` inverts them to the API's `disable_*` shape.\n */\nexport interface TikTokOptionsValue {\n privacy_level?: TikTokPrivacyLevel;\n allow_comment: boolean;\n allow_duet: boolean;\n allow_stitch: boolean;\n /** Commercial Content Disclosure master toggle (off by default). When on, at\n * least one of `your_brand` / `branded_content` must be chosen. */\n commercial_disclosure: boolean;\n your_brand: boolean;\n branded_content: boolean;\n /** AI-generated content disclosure → the API's `is_ai_generated`. Video-only,\n * off by default. */\n ai_generated: boolean;\n}\n\nexport interface AudiencePrivacyChoice {\n level: TikTokPrivacyLevel;\n label: string;\n disabled: boolean;\n /** Why the option is disabled; present only when disabled. */\n disabledReason?: string;\n}\n\nexport interface TikTokInteractionRow {\n key: TikTokInteractionKey;\n label: string;\n /** Disabled (and forced off) when the creator forbids it — TikTok mandate. */\n disabled: boolean;\n}\n\n/* --------------------------- required wording --------------------------- */\n\n/** TikTok's Music Usage Confirmation — the legal URL the declaration links to. */\nexport const TIKTOK_MUSIC_CONFIRMATION_URL =\n 'https://www.tiktok.com/legal/page/global/music-usage-confirmation/en';\n\n/** TikTok's Branded Content Policy — linked when a paid partnership is disclosed. */\nexport const TIKTOK_BRANDED_CONTENT_POLICY_URL =\n 'https://www.tiktok.com/legal/page/global/bc-policy/en';\n\n/** TikTok's exact required prompt when disclosure is on but nothing is chosen. */\nexport const TIKTOK_DISCLOSURE_REQUIRED_HINT =\n 'You need to indicate if your content promotes yourself, a third party, or both.';\n\n/** Shown when a photo post has no audience picked yet. */\nexport const TIKTOK_AUDIENCE_REQUIRED_HINT =\n 'Choose who can view this post before publishing.';\n\n/** Why branded content can't be combined with a private audience. */\nexport const TIKTOK_BRANDED_CONTENT_PRIVATE_DISABLED_REASON =\n 'Branded content visibility cannot be set to private.';\n\n/** A published post may take a few minutes to process; tells the user so. */\nexport const TIKTOK_PROCESSING_NOTICE =\n 'After you publish, it may take a few minutes for your post to process and appear on your TikTok profile.';\n\n/* ------------------------------ defaults ------------------------------ */\n\n/**\n * The initial value for a TikTok post: nothing pre-selected. We make the user\n * actively choose — no default audience, every interaction OFF, no disclosure.\n * The panel still greys the interactions the creator forbids.\n */\nexport function defaultTikTokOptions(): TikTokOptionsValue {\n return {\n privacy_level: undefined,\n allow_comment: false,\n allow_duet: false,\n allow_stitch: false,\n commercial_disclosure: false,\n your_brand: false,\n branded_content: false,\n ai_generated: false,\n };\n}\n\n/* ------------------------------ audience ------------------------------ */\n\n/** Narrow a raw dropdown value to a `TikTokPrivacyLevel` against the creator's\n * allowed options — returns `undefined` for anything not offered (no cast). */\nexport function parsePrivacyLevel(\n value: string,\n options: readonly TikTokPrivacyLevel[],\n): TikTokPrivacyLevel | undefined {\n return options.find((option) => option === value);\n}\n\n/** Effective brand flags (only meaningful while disclosure is on). */\nfunction discloses(value: TikTokOptionsValue, kind: TikTokBrandKind): boolean {\n return value.commercial_disclosure && value[kind];\n}\n\n/**\n * The audience choices for a PHOTO post — one per creator-allowed level. While\n * branded content is disclosed, `SELF_ONLY` stays visible but disabled with the\n * reason (branded content can't be private); it is never silently removed or\n * swapped, so the user's selection is never changed behind their back.\n */\nexport function audiencePrivacyOptions(\n options: readonly TikTokPrivacyLevel[],\n value: TikTokOptionsValue,\n): AudiencePrivacyChoice[] {\n const brandedContent = discloses(value, 'branded_content');\n\n return options.map((level) => {\n const disabled = brandedContent && level === 'SELF_ONLY';\n return {\n level,\n label: tiktokPrivacyLabel(level),\n disabled,\n ...(disabled\n ? { disabledReason: TIKTOK_BRANDED_CONTENT_PRIVATE_DISABLED_REASON }\n : {}),\n };\n });\n}\n\n/** A photo post with no audience picked yet — must BLOCK publish (the API\n * requires `privacy_level` on photos and we offer no default). A video never\n * needs one: TikTok publishes every video publicly. */\nexport function audienceUnselected(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): boolean {\n return !isVideo && value.privacy_level === undefined;\n}\n\n/* --------------------------- commercial disclosure --------------------------- */\n\n/** Disclosure is on but neither brand option is chosen — TikTok requires ≥1. */\nexport function commercialDisclosureIncomplete(\n value: TikTokOptionsValue,\n): boolean {\n return (\n value.commercial_disclosure && !value.your_brand && !value.branded_content\n );\n}\n\n/** True when the post is disclosed as branded content (drives the BC declaration). */\nexport function brandedContentDeclared(value: TikTokOptionsValue): boolean {\n return discloses(value, 'branded_content');\n}\n\n/**\n * TikTok's required label notice for the disclosed content — branded content\n * (alone OR with your-brand) is \"Paid partnership\"; your-brand alone is\n * \"Promotional content\". `null` when nothing is disclosed. Wording is verbatim.\n */\nexport function commercialLabelNotice(\n value: TikTokOptionsValue,\n): string | null {\n if (discloses(value, 'branded_content')) {\n return \"Your photo/video will be labeled as 'Paid partnership'\";\n }\n if (discloses(value, 'your_brand')) {\n return \"Your photo/video will be labeled as 'Promotional content'\";\n }\n return null;\n}\n\n/** Toggle the master disclosure switch. Turning it OFF clears both brand\n * selections (a hidden control must not keep sending a stale selection). */\nexport function setCommercialDisclosure(\n value: TikTokOptionsValue,\n on: boolean,\n): TikTokOptionsValue {\n return on\n ? { ...value, commercial_disclosure: true }\n : {\n ...value,\n commercial_disclosure: false,\n your_brand: false,\n branded_content: false,\n };\n}\n\n/** Set one brand option. Never touches the audience (see\n * `brandedContentDisabledReason` for how a private audience locks branded content). */\nexport function setBrandKind(\n value: TikTokOptionsValue,\n kind: TikTokBrandKind,\n on: boolean,\n): TikTokOptionsValue {\n return { ...value, [kind]: on };\n}\n\n/* ------------------------------ interactions ------------------------------ */\n\n/**\n * Why \"Branded content\" is locked, or undefined when it can be toggled. With a\n * private audience already picked the option stays visible but disabled WITH the\n * reason — an already-checked box stays untickable.\n */\nexport function brandedContentDisabledReason(\n value: TikTokOptionsValue,\n): string | undefined {\n return value.privacy_level === 'SELF_ONLY' && !value.branded_content\n ? TIKTOK_BRANDED_CONTENT_PRIVATE_DISABLED_REASON\n : undefined;\n}\n\n/** True when the creator does NOT permit this interaction (so it's locked off). */\nexport function isInteractionForbidden(\n info: TikTokCreatorInfo,\n key: TikTokInteractionKey,\n): boolean {\n return !info.interaction[key];\n}\n\nconst INTERACTION_LABELS: Record<TikTokInteractionKey, string> = {\n comment: 'Comment',\n duet: 'Duet',\n stitch: 'Stitch',\n};\n\n/** Duet & Stitch are video-only — the API rejects `disable_duet` /\n * `disable_stitch` on a photo post — so a photo post shows only Comment. */\nconst VIDEO_INTERACTIONS: readonly TikTokInteractionKey[] = [\n 'comment',\n 'duet',\n 'stitch',\n];\nconst PHOTO_INTERACTIONS: readonly TikTokInteractionKey[] = ['comment'];\n\nconst INTERACTION_VALUE_KEY = {\n comment: 'allow_comment',\n duet: 'allow_duet',\n stitch: 'allow_stitch',\n} as const satisfies Record<TikTokInteractionKey, keyof TikTokOptionsValue>;\n\n/** The interaction toggles to render — video shows Comment/Duet/Stitch (each\n * disabled iff the creator forbids it); photo shows only Comment. */\nexport function interactionRows(\n info: TikTokCreatorInfo,\n isVideo: boolean,\n): TikTokInteractionRow[] {\n const order = isVideo ? VIDEO_INTERACTIONS : PHOTO_INTERACTIONS;\n return order.map((key) => ({\n key,\n label: INTERACTION_LABELS[key],\n disabled: isInteractionForbidden(info, key),\n }));\n}\n\n/** The `TikTokOptionsValue` flag an interaction key maps to. */\nexport function interactionValueKey(\n key: TikTokInteractionKey,\n): keyof TikTokOptionsValue {\n return INTERACTION_VALUE_KEY[key];\n}\n\n/** Immutably set an interaction flag — returns a new value, never mutates. */\nexport function toggleInteraction(\n value: TikTokOptionsValue,\n key: TikTokInteractionKey,\n on: boolean,\n): TikTokOptionsValue {\n return { ...value, [INTERACTION_VALUE_KEY[key]]: on };\n}\n\n/* -------------------------------- mapping -------------------------------- */\n\n/**\n * Map the composer's positive value into the SDK's `tiktok` settings — INVERTING\n * `allow_*` into TikTok's `disable_*`. The API rejects video-only fields\n * (`disable_duet`/`disable_stitch`/`is_ai_generated`) on a photo and\n * `privacy_level` on a video (`field_placement_invalid`), so each post kind sends\n * only its own.\n */\nexport function tiktokSettings(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): NonNullable<SettingsFor<'tiktok'>> {\n const base = {\n disable_comment: !value.allow_comment,\n is_brand_organic: discloses(value, 'your_brand'),\n is_branded_content: discloses(value, 'branded_content'),\n };\n\n return isVideo\n ? {\n ...base,\n disable_duet: !value.allow_duet,\n disable_stitch: !value.allow_stitch,\n is_ai_generated: value.ai_generated,\n }\n : { ...base, privacy_level: value.privacy_level };\n}\n\n/** A fetched post's TikTok variant settings (the read shape) — derived from the\n * contract, never re-declared. */\ntype TikTokVariantSettings = Extract<\n Post['variants'][number],\n { platform: 'tiktok' }\n>['settings'];\n\n/**\n * The inverse of {@link tiktokSettings}: rebuild the composer's controlled\n * options from a fetched post's TikTok settings — the open-a-draft / scheduled\n * path. `disable_*` flags invert back to positive \"allow\"; disclosure is on when\n * either brand flag is.\n */\nexport function tiktokOptionsFromSettings(\n settings: TikTokVariantSettings,\n): TikTokOptionsValue {\n const yourBrand = settings.is_brand_organic ?? false;\n const brandedContent = settings.is_branded_content ?? false;\n\n return {\n privacy_level: settings.privacy_level,\n allow_comment: !(settings.disable_comment ?? false),\n allow_duet: !(settings.disable_duet ?? false),\n allow_stitch: !(settings.disable_stitch ?? false),\n commercial_disclosure: yourBrand || brandedContent,\n your_brand: yourBrand,\n branded_content: brandedContent,\n ai_generated: settings.is_ai_generated ?? false,\n };\n}\n\n/* ------------------------------ readiness ------------------------------ */\n\n/**\n * A render-ready block message when the selected video exceeds the creator's\n * `max_video_duration_sec`, else `null`. Video-only. Never false-blocks: missing\n * creator-info (still loading), no video asset, or a duration the probe hasn't\n * reported yet (`source.duration_ms === null`) all return `null` — it only fires\n * on a KNOWN over-limit duration.\n */\nexport function tiktokDurationError(\n media: readonly MediaResource[],\n info: TikTokCreatorInfo | null,\n isVideo: boolean,\n): string | null {\n if (!isVideo || !info) return null;\n\n const durationMs = media.find((asset) => asset.kind === 'video')?.source\n ?.duration_ms;\n if (durationMs == null) return null;\n if (durationMs / 1000 <= info.max_video_duration_sec) return null;\n\n return `This video is longer than your TikTok account allows (max ${info.max_video_duration_sec}s).`;\n}\n\n/**\n * Is the TikTok post ready to publish (client-side gate mirroring the API)?\n * A photo needs an audience; any post with disclosure on needs a brand kind.\n * The caption-cap + media-count checks live with their own components.\n */\nexport function tiktokOptionsReady(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): boolean {\n // Branded content can't be private (the API rejects it) — checked here so a\n // host that builds the options object directly (bypassing setBrandKind's\n // clearing) still can't enable Post on a state the wire will refuse.\n const brandedPrivate =\n !isVideo &&\n discloses(value, 'branded_content') &&\n value.privacy_level === 'SELF_ONLY';\n return (\n !audienceUnselected(value, isVideo) &&\n !commercialDisclosureIncomplete(value) &&\n !brandedPrivate\n );\n}\n","import type {\n Connection,\n CreatePostInput,\n MediaResource,\n PostMetadata,\n PostPlatform,\n PostVariantInput,\n PublishMode,\n SettingsFor,\n UpdatePostInput,\n} from './resources';\n\n/* --------------------------------- types --------------------------------- */\n\n/**\n * A media attachment — just the uploaded asset's id. The post SHAPE (`post_type`\n * and the LinkedIn `content_kind` / Instagram `media_type`) is DERIVED server-side\n * from the media's byte-detected kind, so the customer never declares a media\n * `kind` here — they attach the asset and we figure out the rest.\n */\nexport type MediaInput = Pick<MediaResource, 'id'>;\n\n/** Base content, shared across channels unless a channel overrides it. */\nexport interface PostContent {\n body?: string;\n media?: readonly MediaInput[];\n}\n\n/**\n * One channel's config. The SDK owns the plumbing (connection, media, assembly);\n * the CUSTOMER owns `settings` — the platform's native, fully-typed config. An\n * Instagram setting on an X channel is a compile error. The post shape is never\n * specified here — the server derives it from the attached media.\n */\nexport interface ChannelConfig<P extends PostPlatform> {\n settings: SettingsFor<P>;\n body?: string;\n media?: readonly MediaInput[];\n connectionId?: string;\n}\n\n/** Channels keyed by platform — each value typed to that platform. */\nexport type Channels = { [P in PostPlatform]?: ChannelConfig<P> };\n\nexport interface ComposePostInput {\n profileId: string;\n content?: PostContent;\n channels: Channels;\n publish?: PublishMode;\n scheduleAt?: string;\n externalId?: string;\n metadata?: PostMetadata;\n tags?: readonly string[];\n notes?: string;\n dryRun?: boolean;\n}\n\n/** A post edit. Omit `channels` for a light edit; include it to rebuild variants. */\nexport interface ComposeUpdateInput {\n content?: PostContent;\n channels?: Channels;\n publish?: PublishMode;\n scheduleAt?: string;\n externalId?: string;\n metadata?: PostMetadata;\n tags?: readonly string[];\n notes?: string;\n dryRun?: boolean;\n}\n\n/** The connections a build resolves against (accepts a full `Connection[]`). */\nexport type ConnectionRef = Pick<Connection, 'id' | 'platform'>;\n\n/**\n * Thrown only for a genuine USAGE error the SDK can't paper over — an\n * unresolvable connection, or a build with zero channels. It is NEVER thrown for\n * a media combination: validity (which media a platform accepts, document\n * support, count limits, …) is the SERVER's job — it derives the post shape from\n * the media and surfaces any problem as a typed `ValidationIssue` from\n * `POST /v1/posts/validate`. So the SDK can always build an in-progress composition.\n */\nexport class ComposeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ComposeError';\n }\n}\n\ntype VariantFor<P extends PostPlatform> = Extract<PostVariantInput, { platform: P }>;\n\n/* ----------------------------- platform handlers -------------------------- */\n\n/** What a handler receives to assemble one variant. */\ninterface ResolvedChannel<P extends PostPlatform> {\n settings: SettingsFor<P>;\n connectionId: string;\n body: string | undefined;\n media: readonly { media_id: string }[];\n}\n\n/**\n * A per-platform variant builder: assemble the typed variant from a resolved\n * channel, passing `settings` straight through (the customer owns it). Each\n * platform gets its own builder so the literal `platform` discriminant ties the\n * result to the right union member with no cast — and so the registry is\n * exhaustive over `PostPlatform`. The post shape is NOT set here: the server\n * derives it from the media.\n */\ninterface PlatformHandler<P extends PostPlatform> {\n buildVariant(resolved: ResolvedChannel<P>): VariantFor<P>;\n}\n\nconst xHandler: PlatformHandler<'x'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'x',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst linkedInHandler: PlatformHandler<'linkedin'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'linkedin',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst facebookHandler: PlatformHandler<'facebook_page'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'facebook_page',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst instagramHandler: PlatformHandler<'instagram'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'instagram',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst tiktokHandler: PlatformHandler<'tiktok'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'tiktok',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\n/**\n * The handler registry. A `Record<PostPlatform, …>` (no optional keys), so a\n * platform the contract defines without a handler here is a COMPILE ERROR.\n */\nconst PLATFORM_HANDLERS: { [P in PostPlatform]: PlatformHandler<P> } = {\n x: xHandler,\n linkedin: linkedInHandler,\n facebook_page: facebookHandler,\n instagram: instagramHandler,\n tiktok: tiktokHandler,\n};\n\n/** Narrow any platform string to a posting platform. */\nexport function isPostPlatform(value: string): value is PostPlatform {\n return Object.prototype.hasOwnProperty.call(PLATFORM_HANDLERS, value);\n}\n\n/** Posting platforms — the single source, derived from the handler registry\n * (the `.filter` type-guard narrows `string[]` → `PostPlatform[]`, cast-free). */\nexport const POST_PLATFORMS: PostPlatform[] =\n Object.keys(PLATFORM_HANDLERS).filter(isPostPlatform);\n\n/* -------------------------------- assembly -------------------------------- */\n\nfunction resolveConnectionId(\n platform: PostPlatform,\n connectionId: string | undefined,\n connections: readonly ConnectionRef[],\n): string {\n if (connectionId) return connectionId;\n const match = connections.find((connection) => connection.platform === platform);\n if (!match) {\n throw new ComposeError(\n `No connection for \"${platform}\" on this profile. Connect the account or pass connectionId.`,\n );\n }\n return match.id;\n}\n\n/** Resolve one channel into its typed variant. Generic over the concrete `P`, so\n * the handler / config / result stay correlated with no cast. */\nfunction buildChannel<P extends PostPlatform>(\n handler: PlatformHandler<P>,\n platform: P,\n config: ChannelConfig<P>,\n content: PostContent,\n connections: readonly ConnectionRef[],\n): VariantFor<P> {\n const media = config.media ?? content.media ?? [];\n return handler.buildVariant({\n settings: config.settings,\n connectionId: resolveConnectionId(platform, config.connectionId, connections),\n body: config.body ?? content.body,\n media: media.map((item) => ({ media_id: item.id })),\n });\n}\n\n/**\n * Resolve ONE platform's channel, or `undefined` when the caller didn't include\n * it. Generic over the concrete `P`, so `channels[platform]` and\n * `PLATFORM_HANDLERS[platform]` stay correlated to the same platform with no cast.\n * Wrapping the registry lookup in this generic is what lets `buildVariants`\n * iterate `POST_PLATFORMS` (a union) without the per-member variance error.\n */\nfunction collectChannel<P extends PostPlatform>(\n platform: P,\n channels: Channels,\n content: PostContent,\n connections: readonly ConnectionRef[],\n): VariantFor<P> | undefined {\n const config = channels[platform];\n if (!config) return undefined;\n return buildChannel(\n PLATFORM_HANDLERS[platform],\n platform,\n config,\n content,\n connections,\n );\n}\n\nfunction buildVariants(\n content: PostContent,\n channels: Channels,\n connections: readonly ConnectionRef[],\n): PostVariantInput[] {\n // Driven by POST_PLATFORMS (derived from the exhaustive PLATFORM_HANDLERS), so a\n // new platform added to the registry is dispatched here automatically — no\n // hand-maintained per-platform branch to forget (the gap that once dropped TikTok).\n const variants = POST_PLATFORMS.flatMap((platform) => {\n const variant = collectChannel(platform, channels, content, connections);\n return variant ? [variant] : [];\n });\n\n if (variants.length === 0) {\n // The ONE retained composition-level guard: \"you called build with no\n // channels\" is a programmer USAGE error, not a per-end-user validity judgment.\n // It is NOT a verdict on whether the media is publishable — that authority\n // (and the post-shape derivation) lives entirely on the server.\n throw new ComposeError('At least one channel is required.');\n }\n return variants;\n}\n\n/**\n * Turn an ergonomic `{ content, channels }` input into the exact\n * `CreatePostInput` the API expects — resolving each channel's connection and\n * attaching the shared/overridden media. The server derives the post shape from\n * that media, so the build is TOTAL: it never throws for a media combination and\n * an in-progress post can always be built to validate. The customer never\n * assembles `variants[]` or sees a `connection_id`, and owns each channel's typed\n * `settings`.\n */\nexport function buildCreatePost(\n input: ComposePostInput,\n connections: readonly ConnectionRef[],\n): CreatePostInput {\n return {\n profile_id: input.profileId,\n publish: input.publish,\n schedule_at: input.scheduleAt,\n external_id: input.externalId,\n metadata: input.metadata,\n tags: input.tags ? [...input.tags] : undefined,\n notes: input.notes,\n dry_run: input.dryRun,\n variants: buildVariants(input.content ?? {}, input.channels, connections),\n };\n}\n\n/** The envelope fields the API counts as a real edit (`dry_run` deliberately not). */\nconst MUTABLE_ENVELOPE_KEYS = [\n 'publish',\n 'scheduleAt',\n 'externalId',\n 'metadata',\n 'tags',\n 'notes',\n] as const satisfies readonly (keyof ComposeUpdateInput)[];\n\n/**\n * Build an `UpdatePostInput`. With `channels`, the full variant set is rebuilt\n * (the API's PATCH replaces it); without it, only the envelope changes.\n */\nexport function buildUpdatePost(\n input: ComposeUpdateInput,\n connections: readonly ConnectionRef[] = [],\n): UpdatePostInput {\n const changesEnvelope = MUTABLE_ENVELOPE_KEYS.some((key) => input[key] !== undefined);\n if (!input.channels && !changesEnvelope) {\n throw new ComposeError(\n 'A post update must change at least one field — pass content/channels or an ' +\n 'envelope field (publish, scheduleAt, tags, …).',\n );\n }\n\n return {\n publish: input.publish,\n schedule_at: input.scheduleAt,\n external_id: input.externalId,\n metadata: input.metadata,\n tags: input.tags ? [...input.tags] : undefined,\n notes: input.notes,\n dry_run: input.dryRun,\n variants: input.channels\n ? buildVariants(input.content ?? {}, input.channels, connections)\n : undefined,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/resources.ts","../src/upload/casing.ts","../src/upload/upload-error.ts","../src/upload/upload-file.ts","../src/media.ts","../src/tiktok.ts","../src/linkedin.ts","../src/x.ts","../src/analytics.ts","../src/tiktok-options.ts","../src/compose.ts"],"names":["unit"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConnectionsConnectData,\n ConnectionsConnectResponse,\n ConnectionsGetResponse,\n ConnectionsListAccountsResponse,\n ConnectionsListByProfileData,\n ConnectionsListByProfileResponse,\n ConnectionsSelectData,\n MediaCreateData,\n MediaCreateResponse,\n MediaGetResponse,\n MediaListData,\n MediaListResponse,\n MediaUpdateData,\n PostsCreateData,\n PostsGetResponse,\n PostsListData,\n PostsListResponse,\n PostsUpdateData,\n PostsValidateData,\n PostsValidateResponse,\n ProfilesCreateData,\n ProfilesGetResponse,\n ProfilesListData,\n ProfilesListResponse,\n ProfilesUpdateData,\n TiktokCreatorInfoResponse,\n} from './client/types.gen';\n\n/**\n * Resource and input shapes, DERIVED from the generated Hey API operation types\n * (`*Data` = request `{ body, path, query }`, `*Response` = success body). Never\n * hand-declared — there is exactly one definition of each, sourced from the\n * contract, so `pnpm generate` keeps them in lockstep with the live API.\n */\n\n/** A profile — a client/brand workspace. */\nexport type Profile = ProfilesGetResponse;\n\n/** A page of profiles (the list envelope). */\nexport type ProfileList = ProfilesListResponse;\n\n/** Query parameters for listing profiles (filters + pagination). */\nexport type ListProfilesQuery = NonNullable<ProfilesListData['query']>;\n\n/** Request body to create a profile. */\nexport type CreateProfileInput = NonNullable<ProfilesCreateData['body']>;\n\n/** Request body to update a profile (send only what changes). */\nexport type UpdateProfileInput = NonNullable<ProfilesUpdateData['body']>;\n\n/** A connected social/ads account on a profile. */\nexport type Connection = ConnectionsGetResponse;\n\n/** A page of a profile's connections. */\nexport type ConnectionList = ConnectionsListByProfileResponse;\n\n/** Query filters for listing a profile's connections (kind / status / pagination). */\nexport type ListConnectionsQuery = NonNullable<\n ConnectionsListByProfileData['query']\n>;\n\n/** A connection's kind — `posting` (social) or `ads`. From the contract. */\nexport type ConnectionKind = NonNullable<ListConnectionsQuery['kind']>;\n\n/** A connection's lifecycle status — `pending` / `active` / `needs_reauth`. */\nexport type ConnectionStatus = NonNullable<ListConnectionsQuery['status']>;\n\n/** The session returned when starting a connect (OAuth) flow. */\nexport type ConnectSession = ConnectionsConnectResponse;\n\n/** A platform a connection can start an OAuth connect flow for. */\nexport type ConnectablePlatform =\n NonNullable<ConnectionsConnectData['body']>['platform'];\n\n/** Accounts discoverable on a pending connection, for the selection step. */\nexport type DiscoverableAccountList = ConnectionsListAccountsResponse;\n\n/**\n * A TikTok creator's live publish options for a connection — the data TikTok's\n * publishing UI renders: `creator` (nickname + avatar), the `privacy_options` to\n * offer for PHOTO posts (with NO default — videos publish publicly), the\n * positive `interaction` (allowed) flags for Comment/Duet/Stitch toggles, and\n * `max_video_duration_sec`. Derived from the contract, never hand-declared.\n */\nexport type TikTokCreatorInfo = TiktokCreatorInfoResponse;\n\n/**\n * One TikTok privacy level — a member of the closed audience union\n * (`PUBLIC_TO_EVERYONE` / `MUTUAL_FOLLOW_FRIENDS` / `FOLLOWER_OF_CREATOR` /\n * `SELF_ONLY`). The per-account ALLOWED subset is `TikTokCreatorInfo.privacy_options`;\n * this is one value from it (what you bind a dropdown option to). Derived from\n * the contract, never hand-declared; render it with {@link tiktokPrivacyLabel}.\n */\nexport type TikTokPrivacyLevel = TikTokCreatorInfo['privacy_options'][number];\n\n/** Request body to select an account on a pending connection. */\nexport type SelectAccountInput = NonNullable<ConnectionsSelectData['body']>;\n\n/** The create/update post body, derived from the contract. */\ntype CreatePostBody = NonNullable<PostsCreateData['body']>;\n\n/**\n * One channel's variant on WRITE — the per-platform discriminated-union member\n * with fully typed native `settings`. Preferred over the read resource's variant\n * for anything that inspects settings (on read, `settings` is an opaque record);\n * it is also the compose-time shape, which is exactly what a live preview renders.\n */\nexport type PostVariantInput = CreatePostBody['variants'][number];\n\n/** The X (Twitter) member of the write variant union — typed native settings\n * (`quote_tweet_id`, `reply`, `poll`, …). Narrowed from the contract, never\n * hand-declared. */\nexport type XPostVariant = Extract<PostVariantInput, { platform: 'x' }>;\n\n/** The LinkedIn member of the write variant union — typed native settings\n * (`visibility`, `article`, `poll`, `document`, `mentions`). The `content_kind`\n * shape is server-DERIVED and read-only (see {@link PostVariant}). Narrowed from\n * the contract, never hand-declared. */\nexport type LinkedInPostVariant = Extract<\n PostVariantInput,\n { platform: 'linkedin' }\n>;\n\n/** The TikTok member of the write variant union — typed native settings\n * (photo-only `privacy_level`, `disable_comment`/`disable_duet`/`disable_stitch`,\n * `is_branded_content`/`is_brand_organic`, `is_ai_generated`, photo/video cover).\n * Narrowed from the contract, never hand-declared — it is the compose-time shape a\n * live TikTok preview renders. */\nexport type TikTokPostVariant = Extract<\n PostVariantInput,\n { platform: 'tiktok' }\n>;\n\n/** The Instagram member of the write variant union — typed native settings\n * (`collaborators`, `user_tags`, `location_id`, and the reel-only\n * `share_to_feed`/`audio_name`/`cover_url`/`thumb_offset`). The `media_type` shape\n * is server-DERIVED and read-only (see {@link PostVariant}). Narrowed from the\n * contract, never hand-declared — the compose-time shape an IG preview renders. */\nexport type InstagramPostVariant = Extract<\n PostVariantInput,\n { platform: 'instagram' }\n>;\n\n/** The YouTube member of the write variant union — typed native settings\n * (`title`, `privacy_status`, `tags`, `category_id`, `thumbnail_media_id`, the\n * made-for-kids and synthetic-media declarations); `body` is the description.\n * The `short` vs `video` shape is server-DERIVED from the video's duration and\n * size. Narrowed from the contract, never hand-declared. */\nexport type YouTubePostVariant = Extract<\n PostVariantInput,\n { platform: 'youtube' }\n>;\n\n/** A post — its variants, schedule, and derived status (the read resource). */\nexport type Post = PostsGetResponse;\n\n/**\n * One per-platform variant on a RETURNED post (the read resource): its own\n * `status`, a typed `error` ({@link PostVariantError}) when it failed, and a\n * `result` (platform id + permalink) when it published. Derived from the post\n * resource, never hand-declared. (For inspecting compose-time native `settings`,\n * prefer {@link PostVariantInput}; on read `settings` is opaque.)\n */\nexport type PostVariant = Post['variants'][number];\n\n/**\n * A variant's typed publish failure — `{ code, message }`. The `code` is the\n * adapter's own minted reason (`x_access_not_permitted`, `linkedin_auth_expired`,\n * …); it stays a `string` because adapter codes are minted outside the API's\n * closed registry. Derived from the variant, never hand-declared.\n */\nexport type PostVariantError = NonNullable<PostVariant['error']>;\n\n/**\n * Did the post fully publish? `true` ONLY when the rollup `status` is\n * `'published'` — `partially_published` and `failed` are NOT success. The single\n * pure predicate every surface (SDK, React hook, composer) shares, so \"success\"\n * has exactly one definition.\n */\nexport const isPublished = (post: Post): boolean =>\n post.status === 'published';\n\n/**\n * The variants that FAILED to publish, each carrying its typed {@link\n * PostVariantError}. Empty when none failed. The pure helper behind\n * `useCreatePost().failedVariants` — usable directly from a non-React caller so\n * the \"which platforms failed, and why\" projection has one definition.\n */\nexport const failedVariants = (post: Post): PostVariant[] =>\n post.variants.filter((variant) => variant.status === 'failed');\n\n/** A page of posts (the calendar/queue list). */\nexport type PostList = PostsListResponse;\n\n/** Query parameters for listing posts (filters + pagination). */\nexport type ListPostsQuery = NonNullable<PostsListData['query']>;\n\n/**\n * A post's derived rollup status — the values accepted by the list `status`\n * filter (draft / scheduled / publishing / partially_published / published /\n * failed). Sourced from the generated query contract, never hand-listed.\n */\nexport type PostStatus = NonNullable<ListPostsQuery['status']>[number];\n\n/** The full create-post request body — what `buildCreatePost` produces. */\nexport type CreatePostInput = CreatePostBody;\n\n/** The update-post request body — what `buildUpdatePost` produces. */\nexport type UpdatePostInput = NonNullable<PostsUpdateData['body']>;\n\n/**\n * The validate-post request body — the composition-only subset of the create\n * body (`profile_id` + `variants`). `buildCreatePost` produces a superset, so\n * its output is structurally assignable here. Derived from the contract.\n */\nexport type ValidatePostInput = NonNullable<PostsValidateData['body']>;\n\n/**\n * The server's publish-readiness verdict for a composed post — `{ object,\n * publishable, issues }`. The SERVER is the sole authority on validity; the SDK\n * only builds a best-effort payload and relays this result. Derived from the\n * contract, never hand-declared.\n */\nexport type PostValidation = PostsValidateResponse;\n\n/**\n * One typed, per-variant problem in a `PostValidation` — a closed-registry\n * `code`, a human `message`, the `variant_index` + `path` it applies to, and the\n * optional `hint`/`allowed`/`got` the rule produced. Narrowed from the contract.\n */\nexport type ValidationIssue = PostValidation['issues'][number];\n\n/** A posting platform — the variant discriminator (x / linkedin / …). */\nexport type PostPlatform = PostVariantInput['platform'];\n\n/**\n * The post shape (`text` / `single_image` / `multi_image` / `video` / `reel` /\n * `carousel`) — DERIVED server-side from the attached media, never sent on write.\n * It surfaces on the READ variant (`PostVariant.post_type`, null while\n * undetermined); this strips that null to the concrete union. Derived from the\n * contract, never hand-listed.\n */\nexport type PostType = NonNullable<PostVariant['post_type']>;\n\n/** The native `settings` shape for a given platform (typed per channel). */\nexport type SettingsFor<P extends PostPlatform> = Extract<\n PostVariantInput,\n { platform: P }\n>['settings'];\n\n/** Publish mode: `now` | `schedule` | `draft`. */\nexport type PublishMode = NonNullable<CreatePostBody['publish']>;\n\n/** Customer metadata on a post (the shared scalar map). */\nexport type PostMetadata = NonNullable<CreatePostBody['metadata']>;\n\n/** A media asset (image/video/gif/document) with its per-platform renditions. */\nexport type MediaResource = MediaGetResponse;\n\n/**\n * A media asset's kind (image / video / gif / document), from the contract. The\n * resource's `kind` is nullable only in the transient pre-detection `uploading`\n * window; this is the concrete family, so it strips that null — what you pass to\n * create/upload and switch over on a settled asset.\n */\nexport type MediaKind = NonNullable<MediaResource['kind']>;\n\n/** A media render target — one of the post platforms. */\nexport type MediaTarget = NonNullable<\n NonNullable<MediaCreateData['body']>['targets']\n>[number];\n\n/** The create-media response: the resource plus a multipart upload session. */\nexport type CreateMediaResult = MediaCreateResponse;\n\n/** Request body to create a media asset. */\nexport type CreateMediaInput = NonNullable<MediaCreateData['body']>;\n\n/** Request body to update a media asset (alt text / metadata / extend targets). */\nexport type UpdateMediaInput = NonNullable<MediaUpdateData['body']>;\n\n/** A page of media assets (the list envelope). */\nexport type MediaList = MediaListResponse;\n\n/** Query parameters for listing media (filters + pagination). */\nexport type ListMediaQuery = NonNullable<MediaListData['query']>;\n\n/**\n * A media asset's lifecycle state — the values the list `status` filter accepts\n * (uploading / processing / ready / failed). From the contract, never hand-listed.\n */\nexport type MediaStatus = NonNullable<ListMediaQuery['status']>;\n\n/**\n * Customer metadata — a flat scalar map (`string | number | boolean`; ≤50 keys,\n * keys ≤40 chars, ≤500-char strings, 16 KiB serialized). The contract uses this\n * SAME shape on every stored resource (profiles / posts / media); this alias is\n * derived from the media write body as a stable representative, so it's the type\n * to annotate metadata you send — never `Record<string, unknown>`. (The three\n * resources share one structural shape in the spec; were the API ever to diverge\n * one, prefer that resource's own `*['metadata']` for it.)\n */\nexport type Metadata = NonNullable<CreateMediaInput['metadata']>;\n\n/**\n * A metadata filter for list endpoints — the same scalar map, matched by exact\n * containment (multiple keys ANDed; omit for no filter). Pass the object directly\n * from the SDK/React hooks; the client URL-encodes it for the REST query.\n */\nexport type MetadataFilter = NonNullable<ListMediaQuery['metadata']>;\n","import type { AwsS3Part } from '@uppy/aws-s3';\n\nimport type {\n MediaCompleteData,\n MediaListPartsResponse,\n} from '../client/types.gen';\n\n/**\n * The casing seam between Uppy's `@uppy/aws-s3` callbacks (which speak S3\n * PascalCase — `{ PartNumber, ETag, Size }`) and our endpoints (which speak the\n * API's snake_case — `{ part_number, etag, size }`). Uppy reads the parts we hand\n * it back from `listParts` and hands us the parts to send to `complete`, so a\n * silent mismatch here would break resume/complete with no error. Both maps are\n * pure and tested; the shapes are DERIVED from the generated client + Uppy types,\n * never hand-declared.\n */\n\n/** One part as our `GET /media/{id}/multipart/parts` endpoint returns it. */\ntype ApiListedPart = MediaListPartsResponse['parts'][number];\n\n/** One part as our `POST /media/{id}/multipart/complete` body carries it. */\ntype ApiCompletePart = NonNullable<MediaCompleteData['body']>['parts'][number];\n\n/**\n * Map our snake_case listed parts → Uppy's S3 `AwsS3Part[]` so a resume after a\n * dropped connection skips the parts R2 already holds. A 1:1 field map — the\n * endpoint's `parts` contract guarantees `part_number`/`etag`/`size` on every\n * entry, so there's nothing to drop.\n */\nexport function listedPartsToS3(\n parts: readonly ApiListedPart[],\n): AwsS3Part[] {\n return parts.map((part) => ({\n PartNumber: part.part_number,\n ETag: part.etag,\n Size: part.size,\n }));\n}\n\n/**\n * Map Uppy's S3 `AwsS3Part[]` (handed to `completeMultipartUpload`) → our\n * snake_case `complete` body parts. `AwsS3Part` types `PartNumber`/`ETag` as\n * optional; a part Uppy completes with always has both, but we assert the\n * invariant explicitly rather than cast — a missing field is a hard error, not a\n * silently-dropped part.\n */\nexport function s3PartsToComplete(\n parts: readonly AwsS3Part[],\n): ApiCompletePart[] {\n return parts.map((part) => {\n if (part.PartNumber === undefined || part.ETag === undefined) {\n throw new Error(\n 'Multipart complete received a part without PartNumber/ETag.',\n );\n }\n return { part_number: part.PartNumber, etag: part.ETag };\n });\n}\n","/**\n * A failed direct-to-storage (multipart) upload. `cause` carries the underlying\n * Uppy / network / API error so callers can inspect the real reason; `message` is\n * a short human summary. Thrown by {@link uploadFile} on a hard upload failure\n * (one or more parts could not be uploaded, or `complete` failed).\n */\nexport class UploadError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'UploadError';\n }\n}\n","import Uppy from '@uppy/core';\nimport AwsS3 from '@uppy/aws-s3';\n\nimport type { PostrunClient } from '../client';\nimport {\n mediaAbort,\n mediaComplete,\n mediaListParts,\n mediaSignPart,\n} from '../client/sdk.gen';\nimport type { MediaCreateResponse } from '../client/types.gen';\nimport { listedPartsToS3, s3PartsToComplete } from './casing';\nimport { UploadError } from './upload-error';\n\n/**\n * The multipart session the create response carries on its `upload` field — the\n * cached `{ upload_id, key, part_size, expires_at }` Uppy's `createMultipartUpload`\n * returns without a second HTTP call. Derived from the contract, never declared.\n */\nexport type MultipartSession = NonNullable<MediaCreateResponse['upload']>;\n\nexport interface UploadFileOptions {\n /** The media id the create call returned — binds the multipart sub-endpoints. */\n mediaId: string;\n /** The cached `upload` session from the create response (`upload_id`/`key`/…). */\n session: MultipartSession;\n /** The typed client the sub-endpoint calls go through (auth + base URL). */\n client: PostrunClient;\n /** Fraction uploaded, 0..1, as bytes flow; called once with 1 on success. */\n onProgress?: (fraction: number) => void;\n /** Abort the in-flight upload (cancels Uppy and aborts the R2 session). */\n signal?: AbortSignal;\n}\n\n/**\n * Upload ONE file's bytes to R2 with a resilient, resumable multipart upload,\n * driving `@uppy/aws-s3` headless (no DOM). Uppy owns the bug-prone mechanics —\n * fixed-size chunking, parallel parts, per-part retry, resume via `listParts`,\n * and progress — wired to OUR signing endpoints (`createMultipartUpload` returns\n * the cached create-response session, so there's no extra round-trip to start).\n *\n * Resolves when the upload is fully assembled on R2 (the object then settles\n * through the same probe/transform pipeline a `source_url` import does). Throws\n * {@link UploadError} on a hard failure, or a `DOMException('AbortError')` when\n * `signal` fires.\n */\nexport async function uploadFile(\n file: Blob,\n options: UploadFileOptions,\n): Promise<void> {\n const { mediaId, session, client, onProgress, signal } = options;\n\n if (signal?.aborted) {\n throw new DOMException('Upload aborted', 'AbortError');\n }\n\n const uppy = new Uppy({ autoProceed: false }).use(AwsS3, {\n // Every file is one resumable multipart upload — no size-threshold split, so\n // the path is uniform and a tiny file is just a 1-part upload (PLAN §2). The\n // literal `true` (not a `() => true`) selects Uppy's always-multipart variant.\n shouldUseMultipart: true,\n // Pin Uppy's chunk size to the server's part size — every part except the\n // last MUST be exactly this (an R2 requirement the session encodes).\n getChunkSize: () => session.part_size,\n\n // Start: hand back the cached session from the create response — NO HTTP.\n createMultipartUpload: () => ({\n uploadId: session.upload_id,\n key: session.key,\n }),\n\n // Sign one part → POST /media/{id}/multipart/sign. The sub-endpoint reads the\n // upload id from the asset row, so the body carries only the part number.\n signPart: async (_file, { partNumber }) => {\n const { data } = await mediaSignPart({\n client,\n path: { id: mediaId },\n body: { part_number: partNumber },\n });\n return { url: data.url };\n },\n\n // Resume: GET /media/{id}/multipart/parts → map snake_case → S3 PascalCase so\n // Uppy skips the parts R2 already holds after a dropped connection.\n listParts: async () => {\n const { data } = await mediaListParts({\n client,\n path: { id: mediaId },\n });\n return listedPartsToS3(data.parts);\n },\n\n // Finish: map Uppy's S3 parts → snake_case → POST /media/{id}/multipart/complete.\n completeMultipartUpload: async (_file, { parts }) => {\n await mediaComplete({\n client,\n path: { id: mediaId },\n body: { parts: s3PartsToComplete(parts) },\n });\n return {};\n },\n\n // Cancel: POST /media/{id}/multipart/abort (idempotent server-side).\n abortMultipartUpload: async () => {\n await mediaAbort({ client, path: { id: mediaId } });\n },\n });\n\n // Uppy's `progress` event is the overall 0–100 across all parts of the queue\n // (one file here) — forward it as a 0..1 fraction.\n uppy.on('progress', (percent) => onProgress?.(percent / 100));\n\n const onAbort = () => {\n uppy.cancelAll();\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n try {\n uppy.addFile({ name: fileName(file), type: file.type, data: file });\n const result = await uppy.upload();\n\n if (signal?.aborted) {\n throw new DOMException('Upload aborted', 'AbortError');\n }\n\n const failed = result?.failed ?? [];\n if (failed.length > 0) {\n throw new UploadError('Multipart upload failed.', {\n cause: failed[0]?.error,\n });\n }\n\n onProgress?.(1);\n } finally {\n signal?.removeEventListener('abort', onAbort);\n uppy.destroy();\n }\n}\n\n/** Uppy needs a file name; a bare `Blob` has none, so fall back to a stable one. */\nfunction fileName(file: Blob): string {\n return file instanceof File ? file.name : 'upload';\n}\n","import pWaitFor from 'p-wait-for';\n\nimport type { PostrunClient } from './client';\nimport { mediaGet } from './client/sdk.gen';\nimport type { MediaResource } from './resources';\n\n/** Default delay between status polls. */\nconst DEFAULT_POLL_INTERVAL_MS = 1_500;\n\n/**\n * Default ceiling before giving up. Server-side video transcode can legitimately\n * take many minutes on a busy worker, so this backstop is generous — a short one\n * marks still-processing-but-eventually-ready videos as a timeout. 30 minutes is\n * a backstop, NOT the expectation; assets normally settle in seconds-to-minutes.\n */\nconst DEFAULT_POLL_TIMEOUT_MS = 30 * 60 * 1_000;\n\nexport interface WaitForMediaOptions {\n /** Typed client to poll through; falls back to the SDK's default client. */\n client?: PostrunClient;\n /** Milliseconds between polls (default 1500). */\n pollInterval?: number;\n /** Milliseconds before giving up and rejecting (default 30 minutes). */\n timeout?: number;\n /** Abort the wait early. */\n signal?: AbortSignal;\n /** Each polled snapshot — surface `progress.percent` while it processes. */\n onPoll?: (media: MediaResource) => void;\n}\n\n/**\n * Resolve once a media asset stops processing. Polls `GET /v1/media/{id}` until\n * `status` is `ready` OR `failed`, returning that settled resource — a `failed`\n * asset RESOLVES (the caller inspects `status`/`error`), it does not throw. Rejects\n * on timeout or when `signal` aborts (a `DOMException('…', 'AbortError')`).\n */\nexport async function waitForMedia(\n mediaId: string,\n options: WaitForMediaOptions = {},\n): Promise<MediaResource> {\n const {\n client,\n pollInterval = DEFAULT_POLL_INTERVAL_MS,\n timeout = DEFAULT_POLL_TIMEOUT_MS,\n signal,\n onPoll,\n } = options;\n\n let latest: MediaResource | undefined;\n await pWaitFor(\n async () => {\n if (signal?.aborted) {\n throw new DOMException('Media wait aborted', 'AbortError');\n }\n latest = (await mediaGet({ client, path: { id: mediaId } })).data;\n onPoll?.(latest);\n return latest.status === 'ready' || latest.status === 'failed';\n },\n { interval: pollInterval, timeout },\n );\n\n if (!latest) {\n throw new Error('Media polling returned no result.');\n }\n return latest;\n}\n","import type { TikTokPrivacyLevel } from './resources';\n\n/**\n * Map a TikTok privacy level to a short, human label for the composer's privacy\n * dropdown — the presentation the API deliberately keeps OFF the wire (the\n * `creator-info` response returns the typed `privacy_options`; the SDK owns how\n * they read).\n *\n * Exhaustive over the closed `TikTokPrivacyLevel` union with no `default` branch,\n * so adding a TikTok privacy level upstream surfaces as a compile error here\n * rather than silently rendering a raw enum string.\n */\nexport function tiktokPrivacyLabel(value: TikTokPrivacyLevel): string {\n switch (value) {\n case 'PUBLIC_TO_EVERYONE':\n return 'Everyone';\n case 'MUTUAL_FOLLOW_FRIENDS':\n return 'Friends';\n case 'FOLLOWER_OF_CREATOR':\n return 'Followers';\n case 'SELF_ONLY':\n return 'Only you';\n }\n}\n","import type { LinkedInPostVariant } from './resources';\n\n/**\n * One LinkedIn poll duration — DERIVED from the contract (the poll `duration`\n * field), never hand-listed, so it can't drift from the API shape.\n */\nexport type LinkedInPollDuration = NonNullable<\n NonNullable<LinkedInPostVariant['settings']>['poll']\n>['duration'];\n\n/**\n * Map a poll duration to the short \"time left\" label LinkedIn shows on the poll\n * card — the presentation the wire keeps enum-only. Exhaustive over the closed\n * union with no `default` branch, so adding a duration upstream surfaces as a\n * compile error here rather than silently rendering a raw enum string.\n */\nexport function linkedinPollDurationLabel(value: LinkedInPollDuration): string {\n switch (value) {\n case 'ONE_DAY':\n return '1 day left';\n case 'THREE_DAYS':\n return '3 days left';\n case 'SEVEN_DAYS':\n return '1 week left';\n case 'FOURTEEN_DAYS':\n return '2 weeks left';\n }\n}\n","import type { XPostVariant } from './resources';\n\n/** An X poll's settings shape — DERIVED from the contract, never re-declared. */\nexport type XPoll = NonNullable<\n NonNullable<XPostVariant['settings']>['poll']\n>;\n\n/** `n unit left`, pluralizing the unit. */\nfunction plural(n: number, unit: string): string {\n return `${n} ${unit}${n === 1 ? '' : 's'} left`;\n}\n\n/**\n * Turn an X poll's `duration_minutes` (5–10080, i.e. 5 min – 7 days) into the\n * \"time left\" label the poll card shows, floored to the coarsest sensible unit\n * (days ≥ 1 day, else hours ≥ 1 hour, else minutes). Pure — pin it in tests.\n */\nexport function xPollDurationLabel(durationMinutes: number): string {\n if (durationMinutes >= 1440) {\n return plural(Math.floor(durationMinutes / 1440), 'day');\n }\n if (durationMinutes >= 60) {\n return plural(Math.floor(durationMinutes / 60), 'hour');\n }\n return plural(durationMinutes, 'minute');\n}\n","import type {\n AccountMetrics,\n FacebookPageMetrics,\n FacebookPostMetrics,\n InstagramAccountMetrics,\n InstagramPostMetrics,\n PostMetrics,\n PostVariantStatus,\n TikTokAccountMetrics,\n TikTokPostMetrics,\n} from './client/types.gen';\n// From `./resources`, which re-declares these names: importing the generated\n// ones too gives the d.ts bundle two symbols under one exported name, and\n// @postrun/react's inferred hook types then fail TS2742.\nimport type { PostPlatform, PostType } from './resources';\n\n/**\n * Presentation helpers for the analytics reads (`postsGetAnalytics`,\n * `connectionsGetAnalytics`). The wire keeps each network's own field names;\n * these carry the domain knowledge a UI needs on top: which metrics apply to a\n * post type, their labels, and their units. Pure — pin them in tests.\n */\n\n/** How to read a metric's number: `ratio` is 0–1, `percent` is 0–100. */\nexport type MetricFormat =\n | 'count'\n | 'seconds'\n | 'milliseconds'\n | 'ratio'\n | 'percent';\n\n/** A count, a breakdown keyed by the network's own type names, or `null` when\n * the network didn't report it. */\nexport type MetricValue = number | Record<string, number> | null;\n\nexport interface MetricRow {\n key: string;\n label: string;\n format: MetricFormat;\n value: MetricValue;\n}\n\nexport interface MetricPoint {\n date: string;\n value: number;\n}\n\nconst ANALYTICS_PLATFORMS = [\n 'tiktok',\n 'instagram',\n 'facebook_page',\n] as const satisfies readonly PostPlatform[];\n\nexport type AnalyticsPlatform = (typeof ANALYTICS_PLATFORMS)[number];\n\n/** Whether Postrun reads analytics for this network. */\nexport function hasAnalytics(platform: string): platform is AnalyticsPlatform {\n return ANALYTICS_PLATFORMS.some((supported) => supported === platform);\n}\n\n/** Only a published variant on a network with analytics has metrics to read.\n * Narrows `platform`, so the variant can go straight to code keyed by\n * `AnalyticsPlatform`. */\nexport function hasPostAnalytics<\n Variant extends { platform: PostPlatform; status: PostVariantStatus },\n>(variant: Variant): variant is Variant & { platform: AnalyticsPlatform } {\n return variant.status === 'published' && hasAnalytics(variant.platform);\n}\n\ninterface MetricField<Key> {\n key: Key;\n label: string;\n format?: MetricFormat;\n}\n\ntype Fields<Metrics> = readonly MetricField<\n Exclude<keyof Metrics, 'platform'>\n>[];\n\n// `total_time_watched` is left out: next to views and average watch time it\n// adds nothing a reader acts on.\nconst TIKTOK_POST: Fields<TikTokPostMetrics> = [\n { key: 'video_views', label: 'Views' },\n { key: 'reach', label: 'Reach' },\n { key: 'likes', label: 'Likes' },\n { key: 'comments', label: 'Comments' },\n { key: 'shares', label: 'Shares' },\n { key: 'favorites', label: 'Saves' },\n { key: 'average_time_watched', label: 'Avg. watch time', format: 'seconds' },\n { key: 'full_video_watched_rate', label: 'Completion', format: 'ratio' },\n { key: 'new_followers', label: 'New followers' },\n { key: 'profile_views', label: 'Profile views' },\n];\n\nconst INSTAGRAM_SHARED: Fields<InstagramPostMetrics> = [\n { key: 'views', label: 'Views' },\n { key: 'reach', label: 'Reach' },\n { key: 'likes', label: 'Likes' },\n { key: 'comments', label: 'Comments' },\n { key: 'shares', label: 'Shares' },\n { key: 'saved', label: 'Saves' },\n { key: 'reposts', label: 'Reposts' },\n { key: 'total_interactions', label: 'Interactions' },\n];\n\n// Meta reports these for single image and carousel posts only.\nconst INSTAGRAM_FEED: Fields<InstagramPostMetrics> = [\n ...INSTAGRAM_SHARED,\n { key: 'follows', label: 'Follows' },\n { key: 'profile_visits', label: 'Profile visits' },\n { key: 'profile_activity', label: 'Profile activity' },\n];\n\nconst INSTAGRAM_REEL: Fields<InstagramPostMetrics> = [\n ...INSTAGRAM_SHARED,\n {\n key: 'ig_reels_avg_watch_time',\n label: 'Avg. watch time',\n format: 'milliseconds',\n },\n {\n key: 'ig_reels_video_view_total_time',\n label: 'Watch time',\n format: 'milliseconds',\n },\n { key: 'reels_skip_rate', label: 'Skip rate', format: 'percent' },\n];\n\nconst FACEBOOK_FEED: Fields<FacebookPostMetrics> = [\n { key: 'post_media_view', label: 'Views' },\n { key: 'post_total_media_view_unique', label: 'Viewers' },\n { key: 'post_reactions_by_type_total', label: 'Reactions' },\n { key: 'post_clicks', label: 'Clicks' },\n { key: 'post_clicks_by_type', label: 'Clicks by type' },\n { key: 'comments', label: 'Comments' },\n { key: 'shares', label: 'Shares' },\n];\n\nconst FACEBOOK_REEL: Fields<FacebookPostMetrics> = [\n { key: 'blue_reels_play_count', label: 'Plays' },\n { key: 'fb_reels_total_plays', label: 'Total plays' },\n { key: 'fb_reels_replay_count', label: 'Replays' },\n {\n key: 'post_video_avg_time_watched',\n label: 'Avg. watch time',\n format: 'milliseconds',\n },\n { key: 'post_video_view_time', label: 'Watch time', format: 'milliseconds' },\n { key: 'post_video_followers', label: 'New followers' },\n { key: 'post_video_likes_by_reaction_type', label: 'Reactions' },\n];\n\nfunction rowsOf<Key extends string>(\n fields: readonly MetricField<Key>[],\n metrics: Record<Key, MetricValue>,\n): MetricRow[] {\n return fields.map(({ key, label, format = 'count' }) => ({\n key,\n label,\n format,\n value: metrics[key],\n }));\n}\n\n/**\n * The metrics that apply to a post, in display order. Meta reports different\n * metrics for Reels than for other posts, so `postType` (the variant's\n * `post_type`) picks the set; a metric that doesn't apply is left out rather\n * than shown as unreported.\n */\nexport function postMetricRows(\n metrics: PostMetrics,\n postType: PostType | null,\n): MetricRow[] {\n switch (metrics.platform) {\n case 'tiktok':\n return rowsOf(TIKTOK_POST, metrics);\n case 'instagram':\n return rowsOf(\n postType === 'reel' ? INSTAGRAM_REEL : INSTAGRAM_FEED,\n metrics,\n );\n case 'facebook_page':\n return rowsOf(\n postType === 'reel' ? FACEBOOK_REEL : FACEBOOK_FEED,\n metrics,\n );\n }\n}\n\nconst TIKTOK_ACCOUNT: Fields<Omit<TikTokAccountMetrics, 'daily'>> = [\n { key: 'followers_count', label: 'Followers' },\n { key: 'following_count', label: 'Following' },\n { key: 'total_likes', label: 'Likes' },\n { key: 'videos_count', label: 'Videos' },\n];\n\nconst INSTAGRAM_ACCOUNT: Fields<\n Omit<InstagramAccountMetrics, 'daily' | 'last_30_days'>\n> = [\n { key: 'followers_count', label: 'Followers' },\n { key: 'follows_count', label: 'Following' },\n { key: 'media_count', label: 'Posts' },\n];\n\nconst INSTAGRAM_LAST_30_DAYS: Fields<InstagramAccountMetrics['last_30_days']> =\n [\n { key: 'views', label: 'Views · 30d' },\n { key: 'reach', label: 'Reach · 30d' },\n { key: 'accounts_engaged', label: 'Engaged · 30d' },\n { key: 'total_interactions', label: 'Interactions · 30d' },\n ];\n\nconst FACEBOOK_ACCOUNT: Fields<Omit<FacebookPageMetrics, 'daily'>> = [\n { key: 'followers_count', label: 'Followers' },\n];\n\n/** An account's headline numbers, in display order. */\nexport function accountMetricRows(metrics: AccountMetrics): MetricRow[] {\n switch (metrics.platform) {\n case 'tiktok':\n return rowsOf(TIKTOK_ACCOUNT, metrics);\n case 'instagram':\n return [\n ...rowsOf(INSTAGRAM_ACCOUNT, metrics),\n ...rowsOf(INSTAGRAM_LAST_30_DAYS, metrics.last_30_days),\n ];\n case 'facebook_page':\n return rowsOf(FACEBOOK_ACCOUNT, metrics);\n }\n}\n\nfunction pointsOf<Key extends string>(\n daily: ReadonlyArray<{ date: string } & Record<Key, number | null>>,\n key: Key,\n): MetricPoint[] {\n return daily.flatMap((day) => {\n const value = day[key];\n\n return value === null ? [] : [{ date: day.date, value }];\n });\n}\n\n/**\n * The account's main daily series for a trend line, oldest first, leaving out\n * days the network didn't report. Instagram's is reach: it is the only account\n * metric Meta reports per day.\n */\nexport function accountTrend(metrics: AccountMetrics): {\n label: string;\n points: MetricPoint[];\n} {\n switch (metrics.platform) {\n case 'tiktok':\n return {\n label: 'Followers',\n points: pointsOf(metrics.daily, 'followers_count'),\n };\n case 'instagram':\n return { label: 'Reach', points: pointsOf(metrics.daily, 'reach') };\n case 'facebook_page':\n return {\n label: 'Followers',\n points: pointsOf(metrics.daily, 'page_follows'),\n };\n }\n}\n\n// Pinned to en-US to match the English labels: a reader's locale would turn\n// \"1.2K\" into \"12L\" (en-IN) next to them.\nconst COUNT = new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n});\nconst PERCENT = new Intl.NumberFormat('en-US', {\n style: 'percent',\n maximumFractionDigits: 1,\n});\nconst unit = (name: 'second' | 'minute' | 'hour') =>\n new Intl.NumberFormat('en-US', {\n style: 'unit',\n unit: name,\n unitDisplay: 'narrow',\n maximumFractionDigits: 1,\n });\nconst SECONDS = unit('second');\nconst MINUTES = unit('minute');\nconst HOURS = unit('hour');\n\n// Compared after rounding to the shown precision, so 59.96 s reads \"1m\", not \"60s\".\nconst roundsBelow = (value: number, limit: number) =>\n Math.round(value * 10) / 10 < limit;\n\nfunction formatDuration(seconds: number): string {\n if (roundsBelow(seconds, 60)) {\n return SECONDS.format(seconds);\n }\n\n const minutes = seconds / 60;\n\n return roundsBelow(minutes, 60)\n ? MINUTES.format(minutes)\n : HOURS.format(minutes / 60);\n}\n\nfunction formatNumber(value: number, format: MetricFormat): string {\n switch (format) {\n case 'count':\n return COUNT.format(value);\n case 'seconds':\n return formatDuration(value);\n case 'milliseconds':\n return formatDuration(value / 1000);\n case 'ratio':\n return PERCENT.format(value);\n case 'percent':\n return PERCENT.format(value / 100);\n }\n}\n\nconst capitalize = (text: string) =>\n text.charAt(0).toUpperCase() + text.slice(1);\n\n/**\n * A metric for display. `null` (not reported) reads \"—\", never a made-up 0. A\n * breakdown lists its types largest first: \"Like 1.2K · Love 3\".\n */\nexport function formatMetric(\n value: MetricValue,\n format: MetricFormat = 'count',\n): string {\n if (value === null) {\n return '—';\n }\n\n if (typeof value === 'number') {\n return formatNumber(value, format);\n }\n\n const entries = Object.entries(value).sort(([, a], [, b]) => b - a);\n\n return entries.length === 0\n ? '0'\n : entries\n .map(([type, count]) => `${capitalize(type)} ${COUNT.format(count)}`)\n .join(' · ');\n}\n","import type {\n MediaResource,\n Post,\n PostType,\n SettingsFor,\n TikTokCreatorInfo,\n} from './resources';\nimport { type TikTokPrivacyLevel } from './resources';\nimport { tiktokPrivacyLabel } from './tiktok';\n\n/**\n * The TikTok composer's publish-options model — the framework-agnostic glue\n * between the live `creator-info` payload and a TikTok post's settings. This is\n * the SINGLE source for the audience / creator-gated interaction / commercial\n * disclosure rules; the React panel and the dashboard composer both consume it\n * (never re-implement it). The no-default audience and explicit-consent flow are\n * Postrun's own UX choices, not API requirements.\n *\n * The creator-info shape, the privacy union, and the privacy label come from the\n * contract (`./resources`, `./tiktok`) — never re-declared. This module owns the\n * controlled FORM value, the selection rules, and the mapping into the SDK's\n * per-platform `tiktok` settings.\n */\n\n/** TikTok caption caps, mirrored from the publish contract (`BODY_MAX.tiktok_*`).\n * A video post caps the title (2200), a photo post the description (4000). Lives\n * here (not in React) so non-React consumers (MCP, API client, scripts) can use\n * it. The server enforces these; the editor surfaces them. */\nexport const TIKTOK_CAPTION_MAX = { video: 2200, photo: 4000 } as const;\n\n/** The caption cap for a given post_type (video → title cap, photo → description).\n * `post_type` is server-derived from the media (read-only) — the composer passes\n * the shape it knows it's building (a video post vs a photo post). */\nexport function captionMaxFor(postType: PostType): number {\n return postType === 'video'\n ? TIKTOK_CAPTION_MAX.video\n : TIKTOK_CAPTION_MAX.photo;\n}\n\n/** The creator-gated interactions we surface — DERIVED from the creator-info\n * contract (`interaction` keys), never hand-listed, so it can never drift from\n * the API shape. */\nexport type TikTokInteractionKey = keyof TikTokCreatorInfo['interaction'];\n\n/** Which commercial-disclosure brand option a control maps to. */\nexport type TikTokBrandKind = 'your_brand' | 'branded_content';\n\n/**\n * The controlled value a TikTok composer/preview owns. `privacy_level` is\n * PHOTO-ONLY (TikTok publishes every video publicly) and optional because we\n * make the user pick — there is NO default (an absent value is the \"must choose\"\n * state the photo publish gate keys off). Interaction flags are POSITIVE\n * (\"allow\"); `tiktokSettings` inverts them to the API's `disable_*` shape.\n */\nexport interface TikTokOptionsValue {\n privacy_level?: TikTokPrivacyLevel;\n allow_comment: boolean;\n allow_duet: boolean;\n allow_stitch: boolean;\n /** Commercial Content Disclosure master toggle (off by default). When on, at\n * least one of `your_brand` / `branded_content` must be chosen. */\n commercial_disclosure: boolean;\n your_brand: boolean;\n branded_content: boolean;\n /** AI-generated content disclosure → the API's `is_ai_generated`. Video-only,\n * off by default. */\n ai_generated: boolean;\n}\n\nexport interface AudiencePrivacyChoice {\n level: TikTokPrivacyLevel;\n label: string;\n disabled: boolean;\n /** Why the option is disabled; present only when disabled. */\n disabledReason?: string;\n}\n\nexport interface TikTokInteractionRow {\n key: TikTokInteractionKey;\n label: string;\n /** Disabled (and forced off) when the creator forbids it — TikTok mandate. */\n disabled: boolean;\n}\n\n/* --------------------------- required wording --------------------------- */\n\n/** TikTok's Music Usage Confirmation — the legal URL the declaration links to. */\nexport const TIKTOK_MUSIC_CONFIRMATION_URL =\n 'https://www.tiktok.com/legal/page/global/music-usage-confirmation/en';\n\n/** TikTok's Branded Content Policy — linked when a paid partnership is disclosed. */\nexport const TIKTOK_BRANDED_CONTENT_POLICY_URL =\n 'https://www.tiktok.com/legal/page/global/bc-policy/en';\n\n/** TikTok's exact required prompt when disclosure is on but nothing is chosen. */\nexport const TIKTOK_DISCLOSURE_REQUIRED_HINT =\n 'You need to indicate if your content promotes yourself, a third party, or both.';\n\n/** Shown when a photo post has no audience picked yet. */\nexport const TIKTOK_AUDIENCE_REQUIRED_HINT =\n 'Choose who can view this post before publishing.';\n\n/** Why branded content can't be combined with a private audience. */\nexport const TIKTOK_BRANDED_CONTENT_PRIVATE_DISABLED_REASON =\n 'Branded content visibility cannot be set to private.';\n\n/** A published post may take a few minutes to process; tells the user so. */\nexport const TIKTOK_PROCESSING_NOTICE =\n 'After you publish, it may take a few minutes for your post to process and appear on your TikTok profile.';\n\n/* ------------------------------ defaults ------------------------------ */\n\n/**\n * The initial value for a TikTok post: nothing pre-selected. We make the user\n * actively choose — no default audience, every interaction OFF, no disclosure.\n * The panel still greys the interactions the creator forbids.\n */\nexport function defaultTikTokOptions(): TikTokOptionsValue {\n return {\n privacy_level: undefined,\n allow_comment: false,\n allow_duet: false,\n allow_stitch: false,\n commercial_disclosure: false,\n your_brand: false,\n branded_content: false,\n ai_generated: false,\n };\n}\n\n/* ------------------------------ audience ------------------------------ */\n\n/** Narrow a raw dropdown value to a `TikTokPrivacyLevel` against the creator's\n * allowed options — returns `undefined` for anything not offered (no cast). */\nexport function parsePrivacyLevel(\n value: string,\n options: readonly TikTokPrivacyLevel[],\n): TikTokPrivacyLevel | undefined {\n return options.find((option) => option === value);\n}\n\n/** Effective brand flags (only meaningful while disclosure is on). */\nfunction discloses(value: TikTokOptionsValue, kind: TikTokBrandKind): boolean {\n return value.commercial_disclosure && value[kind];\n}\n\n/**\n * The audience choices for a PHOTO post — one per creator-allowed level. While\n * branded content is disclosed, `SELF_ONLY` stays visible but disabled with the\n * reason (branded content can't be private); it is never silently removed or\n * swapped, so the user's selection is never changed behind their back.\n */\nexport function audiencePrivacyOptions(\n options: readonly TikTokPrivacyLevel[],\n value: TikTokOptionsValue,\n): AudiencePrivacyChoice[] {\n const brandedContent = discloses(value, 'branded_content');\n\n return options.map((level) => {\n const disabled = brandedContent && level === 'SELF_ONLY';\n return {\n level,\n label: tiktokPrivacyLabel(level),\n disabled,\n ...(disabled\n ? { disabledReason: TIKTOK_BRANDED_CONTENT_PRIVATE_DISABLED_REASON }\n : {}),\n };\n });\n}\n\n/** A photo post with no audience picked yet — must BLOCK publish (the API\n * requires `privacy_level` on photos and we offer no default). A video never\n * needs one: TikTok publishes every video publicly. */\nexport function audienceUnselected(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): boolean {\n return !isVideo && value.privacy_level === undefined;\n}\n\n/* --------------------------- commercial disclosure --------------------------- */\n\n/** Disclosure is on but neither brand option is chosen — TikTok requires ≥1. */\nexport function commercialDisclosureIncomplete(\n value: TikTokOptionsValue,\n): boolean {\n return (\n value.commercial_disclosure && !value.your_brand && !value.branded_content\n );\n}\n\n/** True when the post is disclosed as branded content (drives the BC declaration). */\nexport function brandedContentDeclared(value: TikTokOptionsValue): boolean {\n return discloses(value, 'branded_content');\n}\n\n/**\n * TikTok's required label notice for the disclosed content — branded content\n * (alone OR with your-brand) is \"Paid partnership\"; your-brand alone is\n * \"Promotional content\". `null` when nothing is disclosed. Wording is verbatim.\n */\nexport function commercialLabelNotice(\n value: TikTokOptionsValue,\n): string | null {\n if (discloses(value, 'branded_content')) {\n return \"Your photo/video will be labeled as 'Paid partnership'\";\n }\n if (discloses(value, 'your_brand')) {\n return \"Your photo/video will be labeled as 'Promotional content'\";\n }\n return null;\n}\n\n/** Toggle the master disclosure switch. Turning it OFF clears both brand\n * selections (a hidden control must not keep sending a stale selection). */\nexport function setCommercialDisclosure(\n value: TikTokOptionsValue,\n on: boolean,\n): TikTokOptionsValue {\n return on\n ? { ...value, commercial_disclosure: true }\n : {\n ...value,\n commercial_disclosure: false,\n your_brand: false,\n branded_content: false,\n };\n}\n\n/** Set one brand option. Never touches the audience (see\n * `brandedContentDisabledReason` for how a private audience locks branded content). */\nexport function setBrandKind(\n value: TikTokOptionsValue,\n kind: TikTokBrandKind,\n on: boolean,\n): TikTokOptionsValue {\n return { ...value, [kind]: on };\n}\n\n/* ------------------------------ interactions ------------------------------ */\n\n/**\n * Why \"Branded content\" is locked, or undefined when it can be toggled. With a\n * private audience already picked the option stays visible but disabled WITH the\n * reason — an already-checked box stays untickable.\n */\nexport function brandedContentDisabledReason(\n value: TikTokOptionsValue,\n): string | undefined {\n return value.privacy_level === 'SELF_ONLY' && !value.branded_content\n ? TIKTOK_BRANDED_CONTENT_PRIVATE_DISABLED_REASON\n : undefined;\n}\n\n/** True when the creator does NOT permit this interaction (so it's locked off). */\nexport function isInteractionForbidden(\n info: TikTokCreatorInfo,\n key: TikTokInteractionKey,\n): boolean {\n return !info.interaction[key];\n}\n\nconst INTERACTION_LABELS: Record<TikTokInteractionKey, string> = {\n comment: 'Comment',\n duet: 'Duet',\n stitch: 'Stitch',\n};\n\n/** Duet & Stitch are video-only — the API rejects `disable_duet` /\n * `disable_stitch` on a photo post — so a photo post shows only Comment. */\nconst VIDEO_INTERACTIONS: readonly TikTokInteractionKey[] = [\n 'comment',\n 'duet',\n 'stitch',\n];\nconst PHOTO_INTERACTIONS: readonly TikTokInteractionKey[] = ['comment'];\n\nconst INTERACTION_VALUE_KEY = {\n comment: 'allow_comment',\n duet: 'allow_duet',\n stitch: 'allow_stitch',\n} as const satisfies Record<TikTokInteractionKey, keyof TikTokOptionsValue>;\n\n/** The interaction toggles to render — video shows Comment/Duet/Stitch (each\n * disabled iff the creator forbids it); photo shows only Comment. */\nexport function interactionRows(\n info: TikTokCreatorInfo,\n isVideo: boolean,\n): TikTokInteractionRow[] {\n const order = isVideo ? VIDEO_INTERACTIONS : PHOTO_INTERACTIONS;\n return order.map((key) => ({\n key,\n label: INTERACTION_LABELS[key],\n disabled: isInteractionForbidden(info, key),\n }));\n}\n\n/** The `TikTokOptionsValue` flag an interaction key maps to. */\nexport function interactionValueKey(\n key: TikTokInteractionKey,\n): keyof TikTokOptionsValue {\n return INTERACTION_VALUE_KEY[key];\n}\n\n/** Immutably set an interaction flag — returns a new value, never mutates. */\nexport function toggleInteraction(\n value: TikTokOptionsValue,\n key: TikTokInteractionKey,\n on: boolean,\n): TikTokOptionsValue {\n return { ...value, [INTERACTION_VALUE_KEY[key]]: on };\n}\n\n/* -------------------------------- mapping -------------------------------- */\n\n/**\n * Map the composer's positive value into the SDK's `tiktok` settings — INVERTING\n * `allow_*` into TikTok's `disable_*`. The API rejects video-only fields\n * (`disable_duet`/`disable_stitch`/`is_ai_generated`) on a photo and\n * `privacy_level` on a video (`field_placement_invalid`), so each post kind sends\n * only its own.\n */\nexport function tiktokSettings(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): NonNullable<SettingsFor<'tiktok'>> {\n const base = {\n disable_comment: !value.allow_comment,\n is_brand_organic: discloses(value, 'your_brand'),\n is_branded_content: discloses(value, 'branded_content'),\n };\n\n return isVideo\n ? {\n ...base,\n disable_duet: !value.allow_duet,\n disable_stitch: !value.allow_stitch,\n is_ai_generated: value.ai_generated,\n }\n : { ...base, privacy_level: value.privacy_level };\n}\n\n/** A fetched post's TikTok variant settings (the read shape) — derived from the\n * contract, never re-declared. */\ntype TikTokVariantSettings = Extract<\n Post['variants'][number],\n { platform: 'tiktok' }\n>['settings'];\n\n/**\n * The inverse of {@link tiktokSettings}: rebuild the composer's controlled\n * options from a fetched post's TikTok settings — the open-a-draft / scheduled\n * path. `disable_*` flags invert back to positive \"allow\"; disclosure is on when\n * either brand flag is.\n */\nexport function tiktokOptionsFromSettings(\n settings: TikTokVariantSettings,\n): TikTokOptionsValue {\n const yourBrand = settings.is_brand_organic ?? false;\n const brandedContent = settings.is_branded_content ?? false;\n\n return {\n privacy_level: settings.privacy_level,\n allow_comment: !(settings.disable_comment ?? false),\n allow_duet: !(settings.disable_duet ?? false),\n allow_stitch: !(settings.disable_stitch ?? false),\n commercial_disclosure: yourBrand || brandedContent,\n your_brand: yourBrand,\n branded_content: brandedContent,\n ai_generated: settings.is_ai_generated ?? false,\n };\n}\n\n/* ------------------------------ readiness ------------------------------ */\n\n/**\n * A render-ready block message when the selected video exceeds the creator's\n * `max_video_duration_sec`, else `null`. Video-only. Never false-blocks: missing\n * creator-info (still loading), no video asset, or a duration the probe hasn't\n * reported yet (`source.duration_ms === null`) all return `null` — it only fires\n * on a KNOWN over-limit duration.\n */\nexport function tiktokDurationError(\n media: readonly MediaResource[],\n info: TikTokCreatorInfo | null,\n isVideo: boolean,\n): string | null {\n if (!isVideo || !info) return null;\n\n const durationMs = media.find((asset) => asset.kind === 'video')?.source\n ?.duration_ms;\n if (durationMs == null) return null;\n if (durationMs / 1000 <= info.max_video_duration_sec) return null;\n\n return `This video is longer than your TikTok account allows (max ${info.max_video_duration_sec}s).`;\n}\n\n/**\n * Is the TikTok post ready to publish (client-side gate mirroring the API)?\n * A photo needs an audience; any post with disclosure on needs a brand kind.\n * The caption-cap + media-count checks live with their own components.\n */\nexport function tiktokOptionsReady(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): boolean {\n // Branded content can't be private (the API rejects it) — checked here so a\n // host that builds the options object directly (bypassing setBrandKind's\n // clearing) still can't enable Post on a state the wire will refuse.\n const brandedPrivate =\n !isVideo &&\n discloses(value, 'branded_content') &&\n value.privacy_level === 'SELF_ONLY';\n return (\n !audienceUnselected(value, isVideo) &&\n !commercialDisclosureIncomplete(value) &&\n !brandedPrivate\n );\n}\n","import type {\n Connection,\n CreatePostInput,\n MediaResource,\n PostMetadata,\n PostPlatform,\n PostVariantInput,\n PublishMode,\n SettingsFor,\n UpdatePostInput,\n} from './resources';\n\n/* --------------------------------- types --------------------------------- */\n\n/**\n * A media attachment — just the uploaded asset's id. The post SHAPE (`post_type`\n * and the LinkedIn `content_kind` / Instagram `media_type`) is DERIVED server-side\n * from the media's byte-detected kind, so the customer never declares a media\n * `kind` here — they attach the asset and we figure out the rest.\n */\nexport type MediaInput = Pick<MediaResource, 'id'>;\n\n/** Base content, shared across channels unless a channel overrides it. */\nexport interface PostContent {\n body?: string;\n media?: readonly MediaInput[];\n}\n\n/**\n * One channel's config. The SDK owns the plumbing (connection, media, assembly);\n * the CUSTOMER owns `settings` — the platform's native, fully-typed config. An\n * Instagram setting on an X channel is a compile error. The post shape is never\n * specified here — the server derives it from the attached media.\n */\nexport interface ChannelConfig<P extends PostPlatform> {\n settings: SettingsFor<P>;\n body?: string;\n media?: readonly MediaInput[];\n connectionId?: string;\n}\n\n/** Channels keyed by platform — each value typed to that platform. */\nexport type Channels = { [P in PostPlatform]?: ChannelConfig<P> };\n\nexport interface ComposePostInput {\n profileId: string;\n content?: PostContent;\n channels: Channels;\n publish?: PublishMode;\n scheduleAt?: string;\n externalId?: string;\n metadata?: PostMetadata;\n tags?: readonly string[];\n notes?: string;\n dryRun?: boolean;\n}\n\n/** A post edit. Omit `channels` for a light edit; include it to rebuild variants. */\nexport interface ComposeUpdateInput {\n content?: PostContent;\n channels?: Channels;\n publish?: PublishMode;\n scheduleAt?: string;\n externalId?: string;\n metadata?: PostMetadata;\n tags?: readonly string[];\n notes?: string;\n dryRun?: boolean;\n}\n\n/** The connections a build resolves against (accepts a full `Connection[]`). */\nexport type ConnectionRef = Pick<Connection, 'id' | 'platform'>;\n\n/**\n * Thrown only for a genuine USAGE error the SDK can't paper over — an\n * unresolvable connection, or a build with zero channels. It is NEVER thrown for\n * a media combination: validity (which media a platform accepts, document\n * support, count limits, …) is the SERVER's job — it derives the post shape from\n * the media and surfaces any problem as a typed `ValidationIssue` from\n * `POST /v1/posts/validate`. So the SDK can always build an in-progress composition.\n */\nexport class ComposeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ComposeError';\n }\n}\n\ntype VariantFor<P extends PostPlatform> = Extract<PostVariantInput, { platform: P }>;\n\n/* ----------------------------- platform handlers -------------------------- */\n\n/** What a handler receives to assemble one variant. */\ninterface ResolvedChannel<P extends PostPlatform> {\n settings: SettingsFor<P>;\n connectionId: string;\n body: string | undefined;\n media: readonly { media_id: string }[];\n}\n\n/**\n * A per-platform variant builder: assemble the typed variant from a resolved\n * channel, passing `settings` straight through (the customer owns it). Each\n * platform gets its own builder so the literal `platform` discriminant ties the\n * result to the right union member with no cast — and so the registry is\n * exhaustive over `PostPlatform`. The post shape is NOT set here: the server\n * derives it from the media.\n */\ninterface PlatformHandler<P extends PostPlatform> {\n buildVariant(resolved: ResolvedChannel<P>): VariantFor<P>;\n}\n\nconst xHandler: PlatformHandler<'x'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'x',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst linkedInHandler: PlatformHandler<'linkedin'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'linkedin',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst facebookHandler: PlatformHandler<'facebook_page'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'facebook_page',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst instagramHandler: PlatformHandler<'instagram'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'instagram',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst tiktokHandler: PlatformHandler<'tiktok'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'tiktok',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst youtubeHandler: PlatformHandler<'youtube'> = {\n buildVariant: ({ settings, connectionId, body, media }) => ({\n platform: 'youtube',\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\n/**\n * The handler registry. A `Record<PostPlatform, …>` (no optional keys), so a\n * platform the contract defines without a handler here is a COMPILE ERROR.\n */\nconst PLATFORM_HANDLERS: { [P in PostPlatform]: PlatformHandler<P> } = {\n x: xHandler,\n linkedin: linkedInHandler,\n facebook_page: facebookHandler,\n instagram: instagramHandler,\n tiktok: tiktokHandler,\n youtube: youtubeHandler,\n};\n\n/** Narrow any platform string to a posting platform. */\nexport function isPostPlatform(value: string): value is PostPlatform {\n return Object.prototype.hasOwnProperty.call(PLATFORM_HANDLERS, value);\n}\n\n/** Posting platforms — the single source, derived from the handler registry\n * (the `.filter` type-guard narrows `string[]` → `PostPlatform[]`, cast-free). */\nexport const POST_PLATFORMS: PostPlatform[] =\n Object.keys(PLATFORM_HANDLERS).filter(isPostPlatform);\n\n/* -------------------------------- assembly -------------------------------- */\n\nfunction resolveConnectionId(\n platform: PostPlatform,\n connectionId: string | undefined,\n connections: readonly ConnectionRef[],\n): string {\n if (connectionId) return connectionId;\n const match = connections.find((connection) => connection.platform === platform);\n if (!match) {\n throw new ComposeError(\n `No connection for \"${platform}\" on this profile. Connect the account or pass connectionId.`,\n );\n }\n return match.id;\n}\n\n/** Resolve one channel into its typed variant. Generic over the concrete `P`, so\n * the handler / config / result stay correlated with no cast. */\nfunction buildChannel<P extends PostPlatform>(\n handler: PlatformHandler<P>,\n platform: P,\n config: ChannelConfig<P>,\n content: PostContent,\n connections: readonly ConnectionRef[],\n): VariantFor<P> {\n const media = config.media ?? content.media ?? [];\n return handler.buildVariant({\n settings: config.settings,\n connectionId: resolveConnectionId(platform, config.connectionId, connections),\n body: config.body ?? content.body,\n media: media.map((item) => ({ media_id: item.id })),\n });\n}\n\n/**\n * Resolve ONE platform's channel, or `undefined` when the caller didn't include\n * it. Generic over the concrete `P`, so `channels[platform]` and\n * `PLATFORM_HANDLERS[platform]` stay correlated to the same platform with no cast.\n * Wrapping the registry lookup in this generic is what lets `buildVariants`\n * iterate `POST_PLATFORMS` (a union) without the per-member variance error.\n */\nfunction collectChannel<P extends PostPlatform>(\n platform: P,\n channels: Channels,\n content: PostContent,\n connections: readonly ConnectionRef[],\n): VariantFor<P> | undefined {\n const config = channels[platform];\n if (!config) return undefined;\n return buildChannel(\n PLATFORM_HANDLERS[platform],\n platform,\n config,\n content,\n connections,\n );\n}\n\nfunction buildVariants(\n content: PostContent,\n channels: Channels,\n connections: readonly ConnectionRef[],\n): PostVariantInput[] {\n // Driven by POST_PLATFORMS (derived from the exhaustive PLATFORM_HANDLERS), so a\n // new platform added to the registry is dispatched here automatically — no\n // hand-maintained per-platform branch to forget (the gap that once dropped TikTok).\n const variants = POST_PLATFORMS.flatMap((platform) => {\n const variant = collectChannel(platform, channels, content, connections);\n return variant ? [variant] : [];\n });\n\n if (variants.length === 0) {\n // The ONE retained composition-level guard: \"you called build with no\n // channels\" is a programmer USAGE error, not a per-end-user validity judgment.\n // It is NOT a verdict on whether the media is publishable — that authority\n // (and the post-shape derivation) lives entirely on the server.\n throw new ComposeError('At least one channel is required.');\n }\n return variants;\n}\n\n/**\n * Turn an ergonomic `{ content, channels }` input into the exact\n * `CreatePostInput` the API expects — resolving each channel's connection and\n * attaching the shared/overridden media. The server derives the post shape from\n * that media, so the build is TOTAL: it never throws for a media combination and\n * an in-progress post can always be built to validate. The customer never\n * assembles `variants[]` or sees a `connection_id`, and owns each channel's typed\n * `settings`.\n */\nexport function buildCreatePost(\n input: ComposePostInput,\n connections: readonly ConnectionRef[],\n): CreatePostInput {\n return {\n profile_id: input.profileId,\n publish: input.publish,\n schedule_at: input.scheduleAt,\n external_id: input.externalId,\n metadata: input.metadata,\n tags: input.tags ? [...input.tags] : undefined,\n notes: input.notes,\n dry_run: input.dryRun,\n variants: buildVariants(input.content ?? {}, input.channels, connections),\n };\n}\n\n/** The envelope fields the API counts as a real edit (`dry_run` deliberately not). */\nconst MUTABLE_ENVELOPE_KEYS = [\n 'publish',\n 'scheduleAt',\n 'externalId',\n 'metadata',\n 'tags',\n 'notes',\n] as const satisfies readonly (keyof ComposeUpdateInput)[];\n\n/**\n * Build an `UpdatePostInput`. With `channels`, the full variant set is rebuilt\n * (the API's PATCH replaces it); without it, only the envelope changes.\n */\nexport function buildUpdatePost(\n input: ComposeUpdateInput,\n connections: readonly ConnectionRef[] = [],\n): UpdatePostInput {\n const changesEnvelope = MUTABLE_ENVELOPE_KEYS.some((key) => input[key] !== undefined);\n if (!input.channels && !changesEnvelope) {\n throw new ComposeError(\n 'A post update must change at least one field — pass content/channels or an ' +\n 'envelope field (publish, scheduleAt, tags, …).',\n );\n }\n\n return {\n publish: input.publish,\n schedule_at: input.scheduleAt,\n external_id: input.externalId,\n metadata: input.metadata,\n tags: input.tags ? [...input.tags] : undefined,\n notes: input.notes,\n dry_run: input.dryRun,\n variants: input.channels\n ? buildVariants(input.content ?? {}, input.channels, connections)\n : undefined,\n };\n}\n"]}
|