@postrun/react 2.0.0 → 2.1.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/index.d.cts CHANGED
@@ -3,7 +3,7 @@ import { ReactNode, CSSProperties } from 'react';
3
3
  import * as _tanstack_react_query from '@tanstack/react-query';
4
4
  import { QueryClient, QueryKey } from '@tanstack/react-query';
5
5
  import { PostrunClient, ListProfilesQuery, DiscoverableAccountList, Connection, ConnectionKind, ConnectionStatus, ListMediaQuery, ListPostsQuery, ConnectablePlatform, MediaResource, MediaTarget, MediaKind, Metadata, ComposePostInput, PostValidation, XPostVariant, LinkedInPostVariant } from '@postrun/js';
6
- export { PostValidation, ValidationIssue } from '@postrun/js';
6
+ export { PostValidation, PostVariant, PostVariantError, ValidationIssue } from '@postrun/js';
7
7
  import { TwitterComponents } from 'react-tweet';
8
8
 
9
9
  /**
@@ -1181,6 +1181,25 @@ declare function usePost(id: string, options?: LiveOptions): _tanstack_react_que
1181
1181
  * variant set from `{ content, channels }` (per the `buildCreatePost` rules), and
1182
1182
  * sends it — the customer never assembles variants or passes a `connection_id`.
1183
1183
  * `connectedChannels` is the set of posting platforms this profile can reach.
1184
+ *
1185
+ * ## Reading the publish outcome (do NOT stop at "it returned")
1186
+ * On `publish: 'now'` the API returns the created post with a rollup `status` and
1187
+ * a per-variant `status` + typed `error`. A 201 does NOT mean every platform
1188
+ * published — one can silently fail while another succeeds. Surface the outcome
1189
+ * via the derived fields, never assume success from a resolved `create`:
1190
+ *
1191
+ * - **`status`** — the rollup (`published | partially_published | failed |
1192
+ * draft | scheduled`), `undefined` until the first `create` resolves.
1193
+ * - **`isPublished`** — `true` ONLY when `status === 'published'`. This is the
1194
+ * one "fully published" state; `partially_published` and `failed` are NOT
1195
+ * success. Branch on this, not on the promise resolving.
1196
+ * - **`failedVariants`** — the variants that failed, each with its typed
1197
+ * `error` (`{ code, message }`); `[]` when none. Render "X failed: <message>".
1198
+ *
1199
+ * `create` still resolves with the FULL `Post` (read everything if you need to),
1200
+ * and it NEVER throws on a failed/partial publish — a throw would discard the
1201
+ * platforms that DID publish. A transport error (network / 4xx) still surfaces
1202
+ * via `error` (and rejects `create`), exactly as before.
1184
1203
  */
1185
1204
  declare function useCreatePost(profileId: string): {
1186
1205
  create: _tanstack_react_query.UseMutateAsyncFunction<{
@@ -1280,6 +1299,38 @@ declare function useCreatePost(profileId: string): {
1280
1299
  executed: boolean;
1281
1300
  } | undefined;
1282
1301
  reset: () => void;
1302
+ status: "draft" | "scheduled" | "publishing" | "partially_published" | "published" | "failed" | undefined;
1303
+ isPublished: boolean;
1304
+ failedVariants: {
1305
+ id: string;
1306
+ object: "post_variant";
1307
+ connection_id: string | null;
1308
+ platform: "x" | "linkedin" | "facebook_page" | "instagram" | "tiktok";
1309
+ post_type: "text" | "single_image" | "multi_image" | "video" | "reel" | "carousel";
1310
+ body: string | null;
1311
+ status: "draft" | "scheduled" | "publishing" | "published" | "failed";
1312
+ settings: {
1313
+ [key: string]: unknown;
1314
+ };
1315
+ schedule_at: string | null;
1316
+ result: {
1317
+ platform_post_id: string;
1318
+ permalink: string | null;
1319
+ published_at: string;
1320
+ } | null;
1321
+ error: {
1322
+ code: string;
1323
+ message: string;
1324
+ } | null;
1325
+ media: Array<{
1326
+ media_id: string;
1327
+ position: number;
1328
+ crop_box: {
1329
+ [key: string]: unknown;
1330
+ } | null;
1331
+ alt_text_override: string | null;
1332
+ }>;
1333
+ }[];
1283
1334
  isReady: boolean;
1284
1335
  connectedChannels: ("x" | "linkedin" | "facebook_page" | "instagram" | "tiktok")[];
1285
1336
  };
@@ -1300,7 +1351,7 @@ declare function useValidatePost(profileId: string): {
1300
1351
  validate: (input: Omit<ComposePostInput, "profileId">) => Promise<PostValidation>;
1301
1352
  publishable: boolean | undefined;
1302
1353
  issues: {
1303
- code: "unauthorized" | "forbidden" | "not_found" | "conflict" | "validation_failed" | "rate_limited" | "internal_error" | "idempotency_key_invalid" | "idempotency_key_reused" | "idempotency_request_in_progress" | "account_not_available" | "connection_reauth_required" | "connection_not_pending" | "not_implemented" | "connection_discovery_failed" | "media_processing" | "not_publishable" | "invalid_connection" | "invalid_media" | "profile_scope_invalid" | "media_unprobeable" | "media_too_large" | "media_aspect_ratio_unsupported" | "media_resolution_too_low" | "media_gif_unsupported" | "media_format_recompressed" | "media_resolution_downscaled" | "video_container_unsupported" | "video_codec_unsupported" | "video_audio_codec_unsupported" | "video_too_large" | "video_too_small" | "video_dimensions_unsupported" | "video_dimensions_too_large" | "video_fps_unsupported" | "video_fps_too_low" | "video_aspect_unsupported" | "video_duration_too_short" | "video_duration_exceeds_max" | "video_transform_failed" | "media_fetch_failed" | "document_format_unsupported" | "document_too_large" | "document_too_many_pages" | "media_format_indeterminate" | "media_count_invalid" | "body_too_long" | "content_missing" | "content_conflict" | "content_incomplete" | "content_kind_mismatch" | "media_type_mismatch" | "tag_limit_exceeded" | "reel_field_on_non_reel" | "field_placement_invalid" | "media_not_ready" | "media_failed" | "media_unsupported" | "media_kind_mismatch" | "variant_unparseable" | "publishing_unavailable" | "x_duplicate_content" | "x_not_authorized" | "x_rate_limited" | "x_publish_failed" | "x_media_upload_failed" | "linkedin_duplicate_content" | "linkedin_auth_expired" | "linkedin_permission_denied" | "linkedin_media_processing" | "linkedin_media_upload_failed" | "linkedin_publish_failed" | "instagram_media_processing" | "instagram_container_expired" | "instagram_container_failed" | "instagram_rate_limited" | "instagram_not_authorized" | "instagram_publish_failed" | "facebook_reel_processing" | "facebook_reel_failed" | "facebook_rate_limited" | "facebook_not_authorized" | "facebook_publish_failed" | "tiktok_privacy_not_allowed" | "tiktok_duration_exceeds_max" | "tiktok_media_processing" | "tiktok_not_authorized" | "tiktok_rate_limited" | "tiktok_publish_failed" | "connection_platform_mismatch" | "connection_removed";
1354
+ code: "unauthorized" | "forbidden" | "not_found" | "conflict" | "validation_failed" | "rate_limited" | "internal_error" | "idempotency_key_invalid" | "idempotency_key_reused" | "idempotency_request_in_progress" | "account_not_available" | "connection_reauth_required" | "connection_not_pending" | "not_implemented" | "connection_discovery_failed" | "media_processing" | "not_publishable" | "invalid_connection" | "invalid_media" | "profile_scope_invalid" | "media_unprobeable" | "media_too_large" | "media_aspect_ratio_unsupported" | "media_resolution_too_low" | "media_gif_unsupported" | "media_format_recompressed" | "media_resolution_downscaled" | "video_container_unsupported" | "video_codec_unsupported" | "video_audio_codec_unsupported" | "video_too_large" | "video_too_small" | "video_dimensions_unsupported" | "video_dimensions_too_large" | "video_fps_unsupported" | "video_fps_too_low" | "video_aspect_unsupported" | "video_duration_too_short" | "video_duration_exceeds_max" | "video_transform_failed" | "media_fetch_failed" | "document_format_unsupported" | "document_too_large" | "document_too_many_pages" | "media_format_indeterminate" | "media_count_invalid" | "body_too_long" | "content_missing" | "content_conflict" | "content_incomplete" | "content_kind_mismatch" | "media_type_mismatch" | "tag_limit_exceeded" | "reel_field_on_non_reel" | "field_placement_invalid" | "media_not_ready" | "media_failed" | "media_unsupported" | "media_kind_mismatch" | "variant_unparseable" | "publishing_unavailable" | "x_duplicate_content" | "x_not_authorized" | "x_access_not_permitted" | "x_rate_limited" | "x_publish_failed" | "x_media_upload_failed" | "linkedin_duplicate_content" | "linkedin_auth_expired" | "linkedin_permission_denied" | "linkedin_media_processing" | "linkedin_media_upload_failed" | "linkedin_publish_failed" | "instagram_media_processing" | "instagram_container_expired" | "instagram_container_failed" | "instagram_rate_limited" | "instagram_not_authorized" | "instagram_publish_failed" | "facebook_reel_processing" | "facebook_reel_failed" | "facebook_rate_limited" | "facebook_not_authorized" | "facebook_publish_failed" | "tiktok_privacy_not_allowed" | "tiktok_duration_exceeds_max" | "tiktok_media_processing" | "tiktok_not_authorized" | "tiktok_rate_limited" | "tiktok_publish_failed" | "connection_platform_mismatch" | "connection_removed";
1304
1355
  message: string;
1305
1356
  hint?: string;
1306
1357
  allowed?: Array<string>;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { ReactNode, CSSProperties } from 'react';
3
3
  import * as _tanstack_react_query from '@tanstack/react-query';
4
4
  import { QueryClient, QueryKey } from '@tanstack/react-query';
5
5
  import { PostrunClient, ListProfilesQuery, DiscoverableAccountList, Connection, ConnectionKind, ConnectionStatus, ListMediaQuery, ListPostsQuery, ConnectablePlatform, MediaResource, MediaTarget, MediaKind, Metadata, ComposePostInput, PostValidation, XPostVariant, LinkedInPostVariant } from '@postrun/js';
6
- export { PostValidation, ValidationIssue } from '@postrun/js';
6
+ export { PostValidation, PostVariant, PostVariantError, ValidationIssue } from '@postrun/js';
7
7
  import { TwitterComponents } from 'react-tweet';
8
8
 
9
9
  /**
@@ -1181,6 +1181,25 @@ declare function usePost(id: string, options?: LiveOptions): _tanstack_react_que
1181
1181
  * variant set from `{ content, channels }` (per the `buildCreatePost` rules), and
1182
1182
  * sends it — the customer never assembles variants or passes a `connection_id`.
1183
1183
  * `connectedChannels` is the set of posting platforms this profile can reach.
1184
+ *
1185
+ * ## Reading the publish outcome (do NOT stop at "it returned")
1186
+ * On `publish: 'now'` the API returns the created post with a rollup `status` and
1187
+ * a per-variant `status` + typed `error`. A 201 does NOT mean every platform
1188
+ * published — one can silently fail while another succeeds. Surface the outcome
1189
+ * via the derived fields, never assume success from a resolved `create`:
1190
+ *
1191
+ * - **`status`** — the rollup (`published | partially_published | failed |
1192
+ * draft | scheduled`), `undefined` until the first `create` resolves.
1193
+ * - **`isPublished`** — `true` ONLY when `status === 'published'`. This is the
1194
+ * one "fully published" state; `partially_published` and `failed` are NOT
1195
+ * success. Branch on this, not on the promise resolving.
1196
+ * - **`failedVariants`** — the variants that failed, each with its typed
1197
+ * `error` (`{ code, message }`); `[]` when none. Render "X failed: <message>".
1198
+ *
1199
+ * `create` still resolves with the FULL `Post` (read everything if you need to),
1200
+ * and it NEVER throws on a failed/partial publish — a throw would discard the
1201
+ * platforms that DID publish. A transport error (network / 4xx) still surfaces
1202
+ * via `error` (and rejects `create`), exactly as before.
1184
1203
  */
1185
1204
  declare function useCreatePost(profileId: string): {
1186
1205
  create: _tanstack_react_query.UseMutateAsyncFunction<{
@@ -1280,6 +1299,38 @@ declare function useCreatePost(profileId: string): {
1280
1299
  executed: boolean;
1281
1300
  } | undefined;
1282
1301
  reset: () => void;
1302
+ status: "draft" | "scheduled" | "publishing" | "partially_published" | "published" | "failed" | undefined;
1303
+ isPublished: boolean;
1304
+ failedVariants: {
1305
+ id: string;
1306
+ object: "post_variant";
1307
+ connection_id: string | null;
1308
+ platform: "x" | "linkedin" | "facebook_page" | "instagram" | "tiktok";
1309
+ post_type: "text" | "single_image" | "multi_image" | "video" | "reel" | "carousel";
1310
+ body: string | null;
1311
+ status: "draft" | "scheduled" | "publishing" | "published" | "failed";
1312
+ settings: {
1313
+ [key: string]: unknown;
1314
+ };
1315
+ schedule_at: string | null;
1316
+ result: {
1317
+ platform_post_id: string;
1318
+ permalink: string | null;
1319
+ published_at: string;
1320
+ } | null;
1321
+ error: {
1322
+ code: string;
1323
+ message: string;
1324
+ } | null;
1325
+ media: Array<{
1326
+ media_id: string;
1327
+ position: number;
1328
+ crop_box: {
1329
+ [key: string]: unknown;
1330
+ } | null;
1331
+ alt_text_override: string | null;
1332
+ }>;
1333
+ }[];
1283
1334
  isReady: boolean;
1284
1335
  connectedChannels: ("x" | "linkedin" | "facebook_page" | "instagram" | "tiktok")[];
1285
1336
  };
@@ -1300,7 +1351,7 @@ declare function useValidatePost(profileId: string): {
1300
1351
  validate: (input: Omit<ComposePostInput, "profileId">) => Promise<PostValidation>;
1301
1352
  publishable: boolean | undefined;
1302
1353
  issues: {
1303
- code: "unauthorized" | "forbidden" | "not_found" | "conflict" | "validation_failed" | "rate_limited" | "internal_error" | "idempotency_key_invalid" | "idempotency_key_reused" | "idempotency_request_in_progress" | "account_not_available" | "connection_reauth_required" | "connection_not_pending" | "not_implemented" | "connection_discovery_failed" | "media_processing" | "not_publishable" | "invalid_connection" | "invalid_media" | "profile_scope_invalid" | "media_unprobeable" | "media_too_large" | "media_aspect_ratio_unsupported" | "media_resolution_too_low" | "media_gif_unsupported" | "media_format_recompressed" | "media_resolution_downscaled" | "video_container_unsupported" | "video_codec_unsupported" | "video_audio_codec_unsupported" | "video_too_large" | "video_too_small" | "video_dimensions_unsupported" | "video_dimensions_too_large" | "video_fps_unsupported" | "video_fps_too_low" | "video_aspect_unsupported" | "video_duration_too_short" | "video_duration_exceeds_max" | "video_transform_failed" | "media_fetch_failed" | "document_format_unsupported" | "document_too_large" | "document_too_many_pages" | "media_format_indeterminate" | "media_count_invalid" | "body_too_long" | "content_missing" | "content_conflict" | "content_incomplete" | "content_kind_mismatch" | "media_type_mismatch" | "tag_limit_exceeded" | "reel_field_on_non_reel" | "field_placement_invalid" | "media_not_ready" | "media_failed" | "media_unsupported" | "media_kind_mismatch" | "variant_unparseable" | "publishing_unavailable" | "x_duplicate_content" | "x_not_authorized" | "x_rate_limited" | "x_publish_failed" | "x_media_upload_failed" | "linkedin_duplicate_content" | "linkedin_auth_expired" | "linkedin_permission_denied" | "linkedin_media_processing" | "linkedin_media_upload_failed" | "linkedin_publish_failed" | "instagram_media_processing" | "instagram_container_expired" | "instagram_container_failed" | "instagram_rate_limited" | "instagram_not_authorized" | "instagram_publish_failed" | "facebook_reel_processing" | "facebook_reel_failed" | "facebook_rate_limited" | "facebook_not_authorized" | "facebook_publish_failed" | "tiktok_privacy_not_allowed" | "tiktok_duration_exceeds_max" | "tiktok_media_processing" | "tiktok_not_authorized" | "tiktok_rate_limited" | "tiktok_publish_failed" | "connection_platform_mismatch" | "connection_removed";
1354
+ code: "unauthorized" | "forbidden" | "not_found" | "conflict" | "validation_failed" | "rate_limited" | "internal_error" | "idempotency_key_invalid" | "idempotency_key_reused" | "idempotency_request_in_progress" | "account_not_available" | "connection_reauth_required" | "connection_not_pending" | "not_implemented" | "connection_discovery_failed" | "media_processing" | "not_publishable" | "invalid_connection" | "invalid_media" | "profile_scope_invalid" | "media_unprobeable" | "media_too_large" | "media_aspect_ratio_unsupported" | "media_resolution_too_low" | "media_gif_unsupported" | "media_format_recompressed" | "media_resolution_downscaled" | "video_container_unsupported" | "video_codec_unsupported" | "video_audio_codec_unsupported" | "video_too_large" | "video_too_small" | "video_dimensions_unsupported" | "video_dimensions_too_large" | "video_fps_unsupported" | "video_fps_too_low" | "video_aspect_unsupported" | "video_duration_too_short" | "video_duration_exceeds_max" | "video_transform_failed" | "media_fetch_failed" | "document_format_unsupported" | "document_too_large" | "document_too_many_pages" | "media_format_indeterminate" | "media_count_invalid" | "body_too_long" | "content_missing" | "content_conflict" | "content_incomplete" | "content_kind_mismatch" | "media_type_mismatch" | "tag_limit_exceeded" | "reel_field_on_non_reel" | "field_placement_invalid" | "media_not_ready" | "media_failed" | "media_unsupported" | "media_kind_mismatch" | "variant_unparseable" | "publishing_unavailable" | "x_duplicate_content" | "x_not_authorized" | "x_access_not_permitted" | "x_rate_limited" | "x_publish_failed" | "x_media_upload_failed" | "linkedin_duplicate_content" | "linkedin_auth_expired" | "linkedin_permission_denied" | "linkedin_media_processing" | "linkedin_media_upload_failed" | "linkedin_publish_failed" | "instagram_media_processing" | "instagram_container_expired" | "instagram_container_failed" | "instagram_rate_limited" | "instagram_not_authorized" | "instagram_publish_failed" | "facebook_reel_processing" | "facebook_reel_failed" | "facebook_rate_limited" | "facebook_not_authorized" | "facebook_publish_failed" | "tiktok_privacy_not_allowed" | "tiktok_duration_exceeds_max" | "tiktok_media_processing" | "tiktok_not_authorized" | "tiktok_rate_limited" | "tiktok_publish_failed" | "connection_platform_mismatch" | "connection_removed";
1304
1355
  message: string;
1305
1356
  hint?: string;
1306
1357
  allowed?: Array<string>;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { createContext, memo, useMemo, useState, useEffect, Fragment as Fragment$1, useRef, createElement, useContext, useCallback } from 'react';
3
3
  import { useInfiniteQuery, useQuery, useMutation, QueryClient } from '@tanstack/react-query';
4
- import { createPostrunClient, profilesList, profilesGet, profilesCreate, profilesUpdate, profilesDelete, connectionsConnect, connectionsListByProfile, connectionsGet, connectionsListAccounts, connectionsSelect, connectionsDelete, mediaGet, mediaList, mediaUpdate, mediaDelete, postsList, postsGet, postsCreate, buildCreatePost, isPostPlatform, postsValidate, postsUpdate, postsDelete, mediaCreate, PostrunError } from '@postrun/js';
4
+ import { createPostrunClient, profilesList, profilesGet, profilesCreate, profilesUpdate, profilesDelete, connectionsConnect, connectionsListByProfile, connectionsGet, connectionsListAccounts, connectionsSelect, connectionsDelete, mediaGet, mediaList, mediaUpdate, mediaDelete, postsList, postsGet, postsCreate, buildCreatePost, isPostPlatform, failedVariants, isPublished, postsValidate, postsUpdate, postsDelete, mediaCreate, PostrunError } from '@postrun/js';
5
5
  import Nango, { AuthError } from '@nangohq/frontend';
6
6
  import pWaitFor, { TimeoutError } from 'p-wait-for';
7
7
  import pLimit from 'p-limit';
@@ -878,12 +878,18 @@ function useCreatePost(profileId) {
878
878
  },
879
879
  queryClient
880
880
  );
881
+ const post = mutation.data;
881
882
  return {
882
883
  create: mutation.mutateAsync,
883
884
  isPending: mutation.isPending,
884
885
  error: mutation.error,
885
- data: mutation.data,
886
+ data: post,
886
887
  reset: mutation.reset,
888
+ // Derived publish outcome (see the JSDoc above) — `undefined`/`false`/`[]`
889
+ // until the first create resolves; computed from the returned post only.
890
+ status: post?.status,
891
+ isPublished: post ? isPublished(post) : false,
892
+ failedVariants: post ? failedVariants(post) : [],
887
893
  // The profile's connections must load before `create` can resolve a channel;
888
894
  // gate on this so a call during loading isn't mislabeled "not connected".
889
895
  isReady: connections.isSuccess,