@postrun/js 2.17.0 → 2.18.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-LSW34R3K.js → chunk-RSCSTB6J.js} +508 -7
- package/dist/chunk-RSCSTB6J.js.map +1 -0
- package/dist/{chunk-LMZC6OUA.js → chunk-V34SBEHW.js} +158 -4
- package/dist/chunk-V34SBEHW.js.map +1 -0
- package/dist/index.cjs +669 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -3
- package/dist/index.d.ts +83 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/query.cjs +3812 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.cts +27419 -0
- package/dist/query.d.ts +27419 -0
- package/dist/query.js +3661 -0
- package/dist/query.js.map +1 -0
- package/dist/schemas/index.cjs +533 -4
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +522 -9
- package/dist/schemas/index.d.ts +522 -9
- package/dist/schemas/index.js +1 -1
- package/dist/server.cjs +505 -4
- 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-B0Bb9XYb.d.cts → types.gen-ASOPREfm.d.cts} +10028 -9688
- package/dist/{types.gen-B0Bb9XYb.d.ts → types.gen-ASOPREfm.d.ts} +10028 -9688
- package/package.json +15 -1
- package/dist/chunk-LMZC6OUA.js.map +0 -1
- package/dist/chunk-LSW34R3K.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/tiktok.ts","../src/linkedin.ts","../src/x.ts","../src/tiktok-options.ts","../src/compose.ts"],"names":[],"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 * Content Posting policy REQUIRES the publishing UI to render: `creator`\n * (nickname + avatar), the `privacy_options` to offer (with NO default), 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 * (`content_kind`, `visibility`, `article`, `poll`, `document`, `mentions`).\n * Narrowed from 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 * (`privacy_level`, `disable_comment`/`disable_duet`/`disable_stitch`,\n * `brand_content_toggle`/`brand_organic_toggle`, `is_aigc`, 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 * (`media_type`, `collaborators`, `user_tags`, `location_id`, and the reel-only\n * `share_to_feed`/`audio_name`/`cover_url`/`thumb_offset`). 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/** The allowed `post_type` union for a given platform. */\nexport type PostTypeFor<P extends PostPlatform> = Extract<\n PostVariantInput,\n { platform: P }\n>['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 — a post platform or `google_ads`. */\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. Drops any part missing a\n * `part_number` or `etag` (an incomplete record can't be resumed against).\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 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 SettingsFor,\n TikTokCreatorInfo,\n TikTokPostVariant,\n} from './resources';\nimport { type TikTokPrivacyLevel } from './resources';\nimport { tiktokPrivacyLabel } from './tiktok';\n\n/**\n * TikTok's mandatory publish-options model — the framework-agnostic glue between\n * the live `creator-info` payload and a TikTok post's settings. This is the\n * SINGLE source for the no-default / creator-gated / commercial-disclosure rules\n * TikTok's Required UX mandates; the React panel and the dashboard composer both\n * consume it (never re-implement it).\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). */\nexport function captionMaxFor(postType: TikTokPostVariant['post_type']): number {\n return postType === 'video'\n ? TIKTOK_CAPTION_MAX.video\n : TIKTOK_CAPTION_MAX.photo;\n}\n\n/** The creator-gated interactions TikTok requires us to surface — DERIVED from\n * the creator-info contract (`interaction` keys), never hand-listed, so it can\n * never drift from 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 * optional because TikTok requires the user to pick — there is NO default (an\n * absent value is the \"must choose\" state the publish gate keys off). Interaction\n * flags are POSITIVE (\"allow\"); `tiktokSettings` inverts them to the API's\n * `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_aigc`. Video-only, off by\n * default. */\n aigc: boolean;\n}\n\nexport interface TikTokPrivacyChoice {\n level: TikTokPrivacyLevel;\n label: 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/** TikTok's required prompt when no audience has been picked yet. */\nexport const TIKTOK_AUDIENCE_REQUIRED_HINT =\n 'Choose who can view this video before publishing.';\n\n/** TikTok Required UX: clients must notify users a published post may take a few\n * minutes to process. No exact string is mandated; this conveys it. */\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. TikTok's Required UX\n * mandates the user actively choose — no default privacy, every interaction OFF,\n * no disclosure. 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 aigc: false,\n };\n}\n\n/* ------------------------------ audience ------------------------------ */\n\n/** The audience choices for the dropdown — one per level the creator allows. */\nexport function privacyChoices(\n options: readonly TikTokPrivacyLevel[],\n): TikTokPrivacyChoice[] {\n return options.map((level) => ({ level, label: tiktokPrivacyLabel(level) }));\n}\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 levels to offer. Branded content can ONLY be public/friends —\n * TikTok forbids posting it SELF_ONLY (private) — so that option is dropped when\n * branded content is disclosed. Otherwise every creator-allowed level is offered.\n */\nexport function audiencePrivacyOptions(\n options: readonly TikTokPrivacyLevel[],\n value: TikTokOptionsValue,\n): TikTokPrivacyLevel[] {\n return discloses(value, 'branded_content')\n ? options.filter((option) => option !== 'SELF_ONLY')\n : [...options];\n}\n\n/** No audience picked yet — must BLOCK publish (TikTok mandates an explicit\n * choice with no default; the API rejects a missing `privacy_level` too). */\nexport function audienceUnselected(value: TikTokOptionsValue): boolean {\n return 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/**\n * Set one brand option. Enabling branded content while a private audience is\n * picked clears the audience (branded content can't be SELF_ONLY; no default — the\n * user re-picks from the now-narrowed options).\n */\nexport function setBrandKind(\n value: TikTokOptionsValue,\n kind: TikTokBrandKind,\n on: boolean,\n): TikTokOptionsValue {\n const clearPrivacy =\n kind === 'branded_content' && on && value.privacy_level === 'SELF_ONLY';\n return {\n ...value,\n [kind]: on,\n ...(clearPrivacy ? { privacy_level: undefined } : {}),\n };\n}\n\n/* ------------------------------ interactions ------------------------------ */\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-remix features — TikTok states they are NOT applicable\n * to photo posts (\"only 'Allow Comment' can be displayed\"), and the API rejects\n * `disable_duet`/`disable_stitch` on a non-video post. So a photo post shows only\n * 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_*`. `disable_duet`/`disable_stitch`/`is_aigc`\n * are video-only (the API rejects them on a photo post), so a photo post sends\n * just `privacy_level` + `disable_comment` + the brand toggles.\n */\nexport function tiktokSettings(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): NonNullable<SettingsFor<'tiktok'>> {\n const base = {\n privacy_level: value.privacy_level,\n disable_comment: !value.allow_comment,\n brand_organic_toggle: discloses(value, 'your_brand'),\n brand_content_toggle: 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_aigc: value.aigc,\n }\n : base;\n}\n\n/* ------------------------------ readiness ------------------------------ */\n\n/**\n * Is the TikTok post ready to publish (client-side gate mirroring the API)?\n * Audience must be chosen and, if disclosure is on, a brand kind must be picked.\n * The caption-cap + media-count checks live with their own components.\n */\nexport function tiktokOptionsReady(value: TikTokOptionsValue): boolean {\n // Branded content cannot be SELF_ONLY (the API rejects it) — mirror that here so\n // the Post button never enables on a state the wire will refuse, even if a host\n // constructs the options object directly (bypassing setBrandKind's clearing).\n const brandedPrivate =\n discloses(value, 'branded_content') && value.privacy_level === 'SELF_ONLY';\n return (\n !audienceUnselected(value) &&\n !commercialDisclosureIncomplete(value) &&\n !brandedPrivate\n );\n}\n","import type {\n Connection,\n CreatePostInput,\n MediaResource,\n PostMetadata,\n PostPlatform,\n PostTypeFor,\n PostVariantInput,\n PublishMode,\n SettingsFor,\n UpdatePostInput,\n} from './resources';\n\n/* --------------------------------- types --------------------------------- */\n\n/** A media attachment — the uploaded asset (or just its id + kind). */\nexport type MediaInput = Pick<MediaResource, 'id' | 'kind'>;\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 * and derives `post_type` from the media; the CUSTOMER owns `settings` — the\n * platform's native, fully-typed config. An Instagram setting on an X channel is\n * a compile error. `postType` is an optional override of the derived value.\n */\nexport interface ChannelConfig<P extends PostPlatform> {\n settings: SettingsFor<P>;\n postType?: PostTypeFor<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/`post_type` combination: validity (which media pair with which\n * post_type, document support, count limits, …) is the SERVER's job, surfaced as\n * typed `ValidationIssue`s by `POST /v1/posts/validate`. `derivePostType` is\n * best-effort SUGAR, 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 once `post_type` is resolved (derived or explicit). */\ninterface ResolvedChannel<P extends PostPlatform> {\n settings: SettingsFor<P>;\n postType: PostTypeFor<P>;\n connectionId: string;\n body: string | undefined;\n media: readonly { media_id: string }[];\n}\n\n/**\n * Each platform implements two layers:\n * - `derivePostType` — the SUGAR: a BEST-EFFORT default `post_type` guessed from\n * the media. Pure and TOTAL — it NEVER throws and never decides validity; it\n * only runs when the caller doesn't pass `postType`. The SERVER\n * (`POST /v1/posts/validate`) is the sole authority on whether the\n * composition is publishable, returning typed `ValidationIssue`s.\n * - `buildVariant` — the explicit CORE: assemble the typed variant from a\n * resolved channel. Passes `settings` straight through (the customer owns it).\n * Splitting them keeps the core stable and the derivation purely additive.\n */\ninterface PlatformHandler<P extends PostPlatform> {\n derivePostType(media: readonly MediaInput[]): PostTypeFor<P>;\n buildVariant(resolved: ResolvedChannel<P>): VariantFor<P>;\n}\n\nfunction countKinds(media: readonly MediaInput[]) {\n let images = 0;\n let videos = 0;\n let documents = 0;\n for (const item of media) {\n if (item.kind === 'video') videos += 1;\n else if (item.kind === 'document') documents += 1;\n else images += 1; // image | gif\n }\n return { images, videos, documents };\n}\n\n/**\n * X / LinkedIn / Facebook have no mixed-media placement: a post is text, images,\n * OR a single video. This is a BEST-EFFORT guess — it never throws. When a video\n * is present it wins the placement (`video`/`reel`), else 2+ images is\n * `multi_image` and a lone item is `single_image`. The SERVER rules on whether\n * the actual combination (a blend, >1 video, a document) is publishable.\n */\nfunction deriveSinglePlacement<V extends 'video' | 'reel'>(\n media: readonly MediaInput[],\n videoType: V,\n): 'single_image' | 'multi_image' | V {\n const { images, videos } = countKinds(media);\n if (videos >= 1) return videoType;\n return images >= 2 ? 'multi_image' : 'single_image';\n}\n\nconst xHandler: PlatformHandler<'x'> = {\n derivePostType: (media) => {\n if (media.length === 0) return 'text';\n return deriveSinglePlacement(media, 'video');\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'x',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst linkedInHandler: PlatformHandler<'linkedin'> = {\n derivePostType: (media) => {\n if (media.length === 0) return 'text';\n if (countKinds(media).documents > 0) {\n // A document rides as a `single_image` post_type; the customer sets\n // `content_kind: 'document'` in settings — we don't infer it. Best-effort:\n // multi-document is a server concern, not a local throw.\n return 'single_image';\n }\n return deriveSinglePlacement(media, 'video');\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'linkedin',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst facebookHandler: PlatformHandler<'facebook_page'> = {\n derivePostType: (media) => {\n if (media.length === 0) return 'text';\n return deriveSinglePlacement(media, 'reel');\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'facebook_page',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst instagramHandler: PlatformHandler<'instagram'> = {\n derivePostType: (media) => {\n // Best-effort: empty media falls back to `single_image` (the server rules on\n // whether Instagram needs media). 2+ items is a carousel (the API allows it\n // to mix image/gif/video); a lone video is a reel, else a single image.\n if (media.length >= 2) return 'carousel';\n return countKinds(media).videos === 1 ? 'reel' : 'single_image';\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'instagram',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst tiktokHandler: PlatformHandler<'tiktok'> = {\n derivePostType: (media) => {\n // Best-effort: TikTok keeps video and photo posts separate (a video post is\n // one standalone video; photos form a single image or a multi-photo\n // carousel). A video present wins `video`; else 2+ images is a carousel and a\n // lone item is `single_image`. The SERVER rules on a blend / >1 video / docs /\n // empty media.\n const { images, videos } = countKinds(media);\n if (videos >= 1) return 'video';\n return images >= 2 ? 'carousel' : 'single_image';\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'tiktok',\n post_type: postType,\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 postType: config.postType ?? handler.derivePostType(media),\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 any media/post_type combination is\n // publishable — that authority 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,\n * attaching the shared/overridden media, and deriving a best-effort `post_type`\n * from that media (sugar — the server validates the real composition). The build\n * is TOTAL: it never throws for a media/post_type combination, so an in-progress\n * post can always be built to validate. The customer never assembles `variants[]`\n * or sees a `connection_id`, and owns each channel's typed `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/tiktok.ts","../src/linkedin.ts","../src/x.ts","../src/tiktok-options.ts","../src/compose.ts"],"names":[],"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 * Content Posting policy REQUIRES the publishing UI to render: `creator`\n * (nickname + avatar), the `privacy_options` to offer (with NO default), 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 * (`content_kind`, `visibility`, `article`, `poll`, `document`, `mentions`).\n * Narrowed from 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 * (`privacy_level`, `disable_comment`/`disable_duet`/`disable_stitch`,\n * `brand_content_toggle`/`brand_organic_toggle`, `is_aigc`, 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 * (`media_type`, `collaborators`, `user_tags`, `location_id`, and the reel-only\n * `share_to_feed`/`audio_name`/`cover_url`/`thumb_offset`). 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/** The allowed `post_type` union for a given platform. */\nexport type PostTypeFor<P extends PostPlatform> = Extract<\n PostVariantInput,\n { platform: P }\n>['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 — a post platform or `google_ads`. */\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 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 SettingsFor,\n TikTokCreatorInfo,\n TikTokPostVariant,\n} from './resources';\nimport { type TikTokPrivacyLevel } from './resources';\nimport { tiktokPrivacyLabel } from './tiktok';\n\n/**\n * TikTok's mandatory publish-options model — the framework-agnostic glue between\n * the live `creator-info` payload and a TikTok post's settings. This is the\n * SINGLE source for the no-default / creator-gated / commercial-disclosure rules\n * TikTok's Required UX mandates; the React panel and the dashboard composer both\n * consume it (never re-implement it).\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). */\nexport function captionMaxFor(postType: TikTokPostVariant['post_type']): number {\n return postType === 'video'\n ? TIKTOK_CAPTION_MAX.video\n : TIKTOK_CAPTION_MAX.photo;\n}\n\n/** The creator-gated interactions TikTok requires us to surface — DERIVED from\n * the creator-info contract (`interaction` keys), never hand-listed, so it can\n * never drift from 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 * optional because TikTok requires the user to pick — there is NO default (an\n * absent value is the \"must choose\" state the publish gate keys off). Interaction\n * flags are POSITIVE (\"allow\"); `tiktokSettings` inverts them to the API's\n * `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_aigc`. Video-only, off by\n * default. */\n aigc: boolean;\n}\n\nexport interface TikTokPrivacyChoice {\n level: TikTokPrivacyLevel;\n label: 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/** TikTok's required prompt when no audience has been picked yet. */\nexport const TIKTOK_AUDIENCE_REQUIRED_HINT =\n 'Choose who can view this video before publishing.';\n\n/** TikTok Required UX: clients must notify users a published post may take a few\n * minutes to process. No exact string is mandated; this conveys it. */\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. TikTok's Required UX\n * mandates the user actively choose — no default privacy, every interaction OFF,\n * no disclosure. 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 aigc: false,\n };\n}\n\n/* ------------------------------ audience ------------------------------ */\n\n/** The audience choices for the dropdown — one per level the creator allows. */\nexport function privacyChoices(\n options: readonly TikTokPrivacyLevel[],\n): TikTokPrivacyChoice[] {\n return options.map((level) => ({ level, label: tiktokPrivacyLabel(level) }));\n}\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 levels to offer. Branded content can ONLY be public/friends —\n * TikTok forbids posting it SELF_ONLY (private) — so that option is dropped when\n * branded content is disclosed. Otherwise every creator-allowed level is offered.\n */\nexport function audiencePrivacyOptions(\n options: readonly TikTokPrivacyLevel[],\n value: TikTokOptionsValue,\n): TikTokPrivacyLevel[] {\n return discloses(value, 'branded_content')\n ? options.filter((option) => option !== 'SELF_ONLY')\n : [...options];\n}\n\n/** No audience picked yet — must BLOCK publish (TikTok mandates an explicit\n * choice with no default; the API rejects a missing `privacy_level` too). */\nexport function audienceUnselected(value: TikTokOptionsValue): boolean {\n return 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/**\n * Set one brand option. Enabling branded content while a private audience is\n * picked clears the audience (branded content can't be SELF_ONLY; no default — the\n * user re-picks from the now-narrowed options).\n */\nexport function setBrandKind(\n value: TikTokOptionsValue,\n kind: TikTokBrandKind,\n on: boolean,\n): TikTokOptionsValue {\n const clearPrivacy =\n kind === 'branded_content' && on && value.privacy_level === 'SELF_ONLY';\n return {\n ...value,\n [kind]: on,\n ...(clearPrivacy ? { privacy_level: undefined } : {}),\n };\n}\n\n/* ------------------------------ interactions ------------------------------ */\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-remix features — TikTok states they are NOT applicable\n * to photo posts (\"only 'Allow Comment' can be displayed\"), and the API rejects\n * `disable_duet`/`disable_stitch` on a non-video post. So a photo post shows only\n * 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_*`. `disable_duet`/`disable_stitch`/`is_aigc`\n * are video-only (the API rejects them on a photo post), so a photo post sends\n * just `privacy_level` + `disable_comment` + the brand toggles.\n */\nexport function tiktokSettings(\n value: TikTokOptionsValue,\n isVideo: boolean,\n): NonNullable<SettingsFor<'tiktok'>> {\n const base = {\n privacy_level: value.privacy_level,\n disable_comment: !value.allow_comment,\n brand_organic_toggle: discloses(value, 'your_brand'),\n brand_content_toggle: 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_aigc: value.aigc,\n }\n : base;\n}\n\n/* ------------------------------ readiness ------------------------------ */\n\n/**\n * Is the TikTok post ready to publish (client-side gate mirroring the API)?\n * Audience must be chosen and, if disclosure is on, a brand kind must be picked.\n * The caption-cap + media-count checks live with their own components.\n */\nexport function tiktokOptionsReady(value: TikTokOptionsValue): boolean {\n // Branded content cannot be SELF_ONLY (the API rejects it) — mirror that here so\n // the Post button never enables on a state the wire will refuse, even if a host\n // constructs the options object directly (bypassing setBrandKind's clearing).\n const brandedPrivate =\n discloses(value, 'branded_content') && value.privacy_level === 'SELF_ONLY';\n return (\n !audienceUnselected(value) &&\n !commercialDisclosureIncomplete(value) &&\n !brandedPrivate\n );\n}\n","import type {\n Connection,\n CreatePostInput,\n MediaResource,\n PostMetadata,\n PostPlatform,\n PostTypeFor,\n PostVariantInput,\n PublishMode,\n SettingsFor,\n UpdatePostInput,\n} from './resources';\n\n/* --------------------------------- types --------------------------------- */\n\n/** A media attachment — the uploaded asset (or just its id + kind). */\nexport type MediaInput = Pick<MediaResource, 'id' | 'kind'>;\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 * and derives `post_type` from the media; the CUSTOMER owns `settings` — the\n * platform's native, fully-typed config. An Instagram setting on an X channel is\n * a compile error. `postType` is an optional override of the derived value.\n */\nexport interface ChannelConfig<P extends PostPlatform> {\n settings: SettingsFor<P>;\n postType?: PostTypeFor<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/`post_type` combination: validity (which media pair with which\n * post_type, document support, count limits, …) is the SERVER's job, surfaced as\n * typed `ValidationIssue`s by `POST /v1/posts/validate`. `derivePostType` is\n * best-effort SUGAR, 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 once `post_type` is resolved (derived or explicit). */\ninterface ResolvedChannel<P extends PostPlatform> {\n settings: SettingsFor<P>;\n postType: PostTypeFor<P>;\n connectionId: string;\n body: string | undefined;\n media: readonly { media_id: string }[];\n}\n\n/**\n * Each platform implements two layers:\n * - `derivePostType` — the SUGAR: a BEST-EFFORT default `post_type` guessed from\n * the media. Pure and TOTAL — it NEVER throws and never decides validity; it\n * only runs when the caller doesn't pass `postType`. The SERVER\n * (`POST /v1/posts/validate`) is the sole authority on whether the\n * composition is publishable, returning typed `ValidationIssue`s.\n * - `buildVariant` — the explicit CORE: assemble the typed variant from a\n * resolved channel. Passes `settings` straight through (the customer owns it).\n * Splitting them keeps the core stable and the derivation purely additive.\n */\ninterface PlatformHandler<P extends PostPlatform> {\n derivePostType(media: readonly MediaInput[]): PostTypeFor<P>;\n buildVariant(resolved: ResolvedChannel<P>): VariantFor<P>;\n}\n\nfunction countKinds(media: readonly MediaInput[]) {\n let images = 0;\n let videos = 0;\n let documents = 0;\n for (const item of media) {\n if (item.kind === 'video') videos += 1;\n else if (item.kind === 'document') documents += 1;\n else images += 1; // image | gif\n }\n return { images, videos, documents };\n}\n\n/**\n * X / LinkedIn / Facebook have no mixed-media placement: a post is text, images,\n * OR a single video. This is a BEST-EFFORT guess — it never throws. When a video\n * is present it wins the placement (`video`/`reel`), else 2+ images is\n * `multi_image` and a lone item is `single_image`. The SERVER rules on whether\n * the actual combination (a blend, >1 video, a document) is publishable.\n */\nfunction deriveSinglePlacement<V extends 'video' | 'reel'>(\n media: readonly MediaInput[],\n videoType: V,\n): 'single_image' | 'multi_image' | V {\n const { images, videos } = countKinds(media);\n if (videos >= 1) return videoType;\n return images >= 2 ? 'multi_image' : 'single_image';\n}\n\nconst xHandler: PlatformHandler<'x'> = {\n derivePostType: (media) => {\n if (media.length === 0) return 'text';\n return deriveSinglePlacement(media, 'video');\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'x',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst linkedInHandler: PlatformHandler<'linkedin'> = {\n derivePostType: (media) => {\n if (media.length === 0) return 'text';\n if (countKinds(media).documents > 0) {\n // A document rides as a `single_image` post_type; the customer sets\n // `content_kind: 'document'` in settings — we don't infer it. Best-effort:\n // multi-document is a server concern, not a local throw.\n return 'single_image';\n }\n return deriveSinglePlacement(media, 'video');\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'linkedin',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst facebookHandler: PlatformHandler<'facebook_page'> = {\n derivePostType: (media) => {\n if (media.length === 0) return 'text';\n return deriveSinglePlacement(media, 'reel');\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'facebook_page',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst instagramHandler: PlatformHandler<'instagram'> = {\n derivePostType: (media) => {\n // Best-effort: empty media falls back to `single_image` (the server rules on\n // whether Instagram needs media). 2+ items is a carousel (the API allows it\n // to mix image/gif/video); a lone video is a reel, else a single image.\n if (media.length >= 2) return 'carousel';\n return countKinds(media).videos === 1 ? 'reel' : 'single_image';\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'instagram',\n post_type: postType,\n connection_id: connectionId,\n body,\n media: [...media],\n settings,\n }),\n};\n\nconst tiktokHandler: PlatformHandler<'tiktok'> = {\n derivePostType: (media) => {\n // Best-effort: TikTok keeps video and photo posts separate (a video post is\n // one standalone video; photos form a single image or a multi-photo\n // carousel). A video present wins `video`; else 2+ images is a carousel and a\n // lone item is `single_image`. The SERVER rules on a blend / >1 video / docs /\n // empty media.\n const { images, videos } = countKinds(media);\n if (videos >= 1) return 'video';\n return images >= 2 ? 'carousel' : 'single_image';\n },\n buildVariant: ({ settings, postType, connectionId, body, media }) => ({\n platform: 'tiktok',\n post_type: postType,\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 postType: config.postType ?? handler.derivePostType(media),\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 any media/post_type combination is\n // publishable — that authority 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,\n * attaching the shared/overridden media, and deriving a best-effort `post_type`\n * from that media (sugar — the server validates the real composition). The build\n * is TOTAL: it never throws for a media/post_type combination, so an in-progress\n * post can always be built to validate. The customer never assembles `variants[]`\n * or sees a `connection_id`, and owns each channel's typed `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"]}
|