@publishq/sdk 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -40,7 +40,7 @@ for (const account of data?.accounts ?? []) {
40
40
  ## Authentication
41
41
 
42
42
  Pass your API key (starts with `pq_live_`). Create one in the PublishQ web app
43
- under **Settings → API Keys** and select the scopes you need.
43
+ under **API Keys** and select the scopes you need.
44
44
 
45
45
  ```ts
46
46
  const publishq = new PublishQ({ apiKey: 'pq_live_...' });
@@ -95,7 +95,7 @@ await publishq.workspaces.get('<workspace-id>');
95
95
  ### Media
96
96
 
97
97
  ```ts
98
- // Upload a file, then attach the returned media id to a post via `mediaIds`
98
+ // Upload a file of up to 4 GB, then attach the returned media id to a post via `mediaIds`.
99
99
  const { data: media } = await publishq.media.upload({ file });
100
100
  await publishq.media.list({ mediaTypes: ['IMAGE'] });
101
101
  await publishq.media.get('<media-id>');
@@ -114,7 +114,7 @@ returns `403`.
114
114
  | `workspaces:read` | `workspaces.list`, `workspaces.get` |
115
115
  | `workspaces:write` | `workspaces.create` |
116
116
  | `media:read` | `media.list`, `media.get` |
117
- | `media:write` | `media.upload` |
117
+ | `media:write` | `media.upload`, `media.createUpload`, `media.completeUpload` |
118
118
 
119
119
  ## Configuration
120
120
 
package/dist/index.cjs CHANGED
@@ -34,14 +34,21 @@ __export(index_exports, {
34
34
  Media2: () => Media,
35
35
  Posts: () => Posts,
36
36
  PublishQ: () => PublishQ,
37
+ STORAGE_UPLOAD_THRESHOLD_BYTES: () => STORAGE_UPLOAD_THRESHOLD_BYTES,
37
38
  Workspaces: () => Workspaces,
38
39
  default: () => index_default,
40
+ withInferredContentType: () => withInferredContentType,
39
41
  zAccount: () => zAccount,
40
42
  zAccountsResponse: () => zAccountsResponse,
41
43
  zApiError: () => zApiError,
42
44
  zApiErrorCode: () => zApiErrorCode,
43
45
  zApiKeyScope: () => zApiKeyScope,
44
46
  zBlueskyPlatformSpecificSettings: () => zBlueskyPlatformSpecificSettings,
47
+ zCompleteMediaUploadPath: () => zCompleteMediaUploadPath,
48
+ zCompleteMediaUploadResponse: () => zCompleteMediaUploadResponse,
49
+ zCreateMediaUploadBody: () => zCreateMediaUploadBody,
50
+ zCreateMediaUploadRequest: () => zCreateMediaUploadRequest,
51
+ zCreateMediaUploadResponse: () => zCreateMediaUploadResponse,
45
52
  zCreatePostAccount: () => zCreatePostAccount,
46
53
  zCreatePostBody: () => zCreatePostBody,
47
54
  zCreatePostRequest: () => zCreatePostRequest,
@@ -74,13 +81,17 @@ __export(index_exports, {
74
81
  zMediaListResponse: () => zMediaListResponse,
75
82
  zMediaSource: () => zMediaSource,
76
83
  zMediaType: () => zMediaType,
84
+ zMediaUpload: () => zMediaUpload,
77
85
  zPagination: () => zPagination,
78
86
  zPlatformSpecificSettings: () => zPlatformSpecificSettings,
79
87
  zPost: () => zPost,
80
88
  zPostAccount: () => zPostAccount,
89
+ zPostAnalytics: () => zPostAnalytics,
81
90
  zPostOverrides: () => zPostOverrides,
91
+ zPostSource: () => zPostSource,
82
92
  zPostStatus: () => zPostStatus,
83
93
  zPostsResponse: () => zPostsResponse,
94
+ zPublishFailure: () => zPublishFailure,
84
95
  zRemovePostAccountPath: () => zRemovePostAccountPath,
85
96
  zRemovePostAccountResponse: () => zRemovePostAccountResponse,
86
97
  zSocialPlatform: () => zSocialPlatform,
@@ -920,7 +931,7 @@ var Posts = class {
920
931
  /**
921
932
  * List posts
922
933
  *
923
- * Returns a paginated list of posts.
934
+ * Returns a paginated list of the posts you created through PublishQ. Posts imported from a connected platform are excluded by default; pass includeImported=true to get both, or onlyImported=true for only the imported ones.
924
935
  */
925
936
  static listPosts(options) {
926
937
  return (options?.client ?? client).get({
@@ -1082,7 +1093,7 @@ var Media = class {
1082
1093
  /**
1083
1094
  * Upload media
1084
1095
  *
1085
- * Upload one media file with multipart form data. The returned media ID can then be attached to posts.
1096
+ * Upload a file smaller than 100 MB in a single request. It is the simplest way to upload: one call returns a media ID you can attach to posts. For bigger files, use Create media upload.
1086
1097
  */
1087
1098
  static uploadMedia(options) {
1088
1099
  return (options.client ?? client).post({
@@ -1108,6 +1119,39 @@ var Media = class {
1108
1119
  ...options
1109
1120
  });
1110
1121
  }
1122
+ /**
1123
+ * Create media upload
1124
+ *
1125
+ * Upload a file of up to 4 GB (500 MB on the Free plan) straight to storage:
1126
+ *
1127
+ * 1. Call this endpoint with `filename`, `contentType` and `fileSize`. You get back an `id` and an `uploadUrl`.
1128
+ * 2. `PUT` the file to `uploadUrl` with the same `Content-Type` header. Storage answers `200`, or `403` if the file is not exactly `fileSize` bytes.
1129
+ * 3. Optional: call Complete media upload to add the file to your media library now.
1130
+ * 4. Put the `id` in `mediaIds` when you create or update a post.
1131
+ */
1132
+ static createMediaUpload(options) {
1133
+ return (options.client ?? client).post({
1134
+ security: [{ scheme: "bearer", type: "http" }],
1135
+ url: "/media/uploads",
1136
+ ...options,
1137
+ headers: {
1138
+ "Content-Type": "application/json",
1139
+ ...options.headers
1140
+ }
1141
+ });
1142
+ }
1143
+ /**
1144
+ * Complete media upload
1145
+ *
1146
+ * Optional. Adds a file uploaded through Create media upload to your media library now and returns it. Without this call the file is added the first time you use its id in a post, and an upload nobody completes or uses within 1 day expires.
1147
+ */
1148
+ static completeMediaUpload(options) {
1149
+ return (options.client ?? client).post({
1150
+ security: [{ scheme: "bearer", type: "http" }],
1151
+ url: "/media/uploads/{id}/complete",
1152
+ ...options
1153
+ });
1154
+ }
1111
1155
  };
1112
1156
 
1113
1157
  // src/generated/zod.gen.ts
@@ -1127,6 +1171,7 @@ var zApiErrorCode = z.enum([
1127
1171
  "post_not_editable",
1128
1172
  "media_not_found",
1129
1173
  "media_in_use",
1174
+ "media_upload_incomplete",
1130
1175
  "storage_limit_exceeded",
1131
1176
  "free_media_limit_exceeded",
1132
1177
  "free_pending_media_limit_exceeded",
@@ -1137,6 +1182,9 @@ var zApiErrorCode = z.enum([
1137
1182
  "invalid_file_extension",
1138
1183
  "social_accounts_not_found",
1139
1184
  "social_accounts_expired",
1185
+ "social_account_limit_exceeded",
1186
+ "platform_temporarily_unavailable",
1187
+ "platform_permission_missing",
1140
1188
  "platform_validation_failed",
1141
1189
  "posting_limits_exceeded",
1142
1190
  "scheduled_at_required",
@@ -1146,10 +1194,12 @@ var zApiErrorCode = z.enum([
1146
1194
  "workspace_not_found",
1147
1195
  "workspace_forbidden",
1148
1196
  "workspace_owner_required",
1197
+ "workspace_limit_exceeded",
1149
1198
  "workspace_account_not_found",
1150
1199
  "workspace_account_conflict",
1151
1200
  "workspace_member_not_found",
1152
1201
  "workspace_member_conflict",
1202
+ "team_member_limit_exceeded",
1153
1203
  "workspace_invitation_invalid",
1154
1204
  "workspace_invitation_expired"
1155
1205
  ]);
@@ -1358,6 +1408,56 @@ var zTwitterPlatformSpecificSettings = z.object({
1358
1408
  }).register(z.globalRegistry, {
1359
1409
  description: "X (Twitter) specific post settings. All fields are optional."
1360
1410
  });
1411
+ var zPostSource = z.enum(["PUBLISHQ", "IMPORTED"]).register(z.globalRegistry, {
1412
+ description: "Where the post came from. PUBLISHQ: created in PublishQ. IMPORTED: published directly on the platform and discovered during an analytics sync, so the post history stays complete."
1413
+ });
1414
+ var zPostAnalytics = z.object({
1415
+ views: z.int().check(z.gte(0)).register(z.globalRegistry, {
1416
+ description: "Views the platform reports for this post."
1417
+ }),
1418
+ likes: z.int().check(z.gte(0)).register(z.globalRegistry, {
1419
+ description: "Likes the platform reports for this post."
1420
+ }),
1421
+ comments: z.int().check(z.gte(0)).register(z.globalRegistry, {
1422
+ description: "Comments the platform reports for this post."
1423
+ }),
1424
+ shares: z.int().check(z.gte(0)).register(z.globalRegistry, {
1425
+ description: "Shares the platform reports for this post. 0 on platforms that do not disclose it (including YouTube)."
1426
+ }),
1427
+ extraMetrics: z.record(z.string(), z.int().check(z.gte(0))).register(z.globalRegistry, {
1428
+ description: "Metrics only this platform reports, keyed by metric name \u2014 Bluesky bookmarks, for example. Every metric the platform reports is always present, 0 included, so no key has to be guessed. Empty on platforms that expose nothing beyond the fields above, and a metric that already has its own field is never repeated here."
1429
+ }),
1430
+ syncedAt: z.iso.datetime().register(z.globalRegistry, {
1431
+ description: "When these numbers were last recorded. Metrics are refreshed in the background roughly once a day, and only stored when they change."
1432
+ })
1433
+ });
1434
+ var zPublishFailure = z.object({
1435
+ kind: z.enum([
1436
+ "RECONNECT",
1437
+ "MEDIA",
1438
+ "ACCOUNT_STATE",
1439
+ "TRANSIENT",
1440
+ "MAYBE_PUBLISHED",
1441
+ "UNKNOWN"
1442
+ ]).register(z.globalRegistry, {
1443
+ description: "What kind of action this failure needs. `RECONNECT`: the account has to be reconnected. `MEDIA`: the platform refused the attached file. `ACCOUNT_STATE`: the platform is blocking this account, so the fix is on the platform. `TRANSIENT`: the platform was unavailable and the same request can succeed unchanged. `MAYBE_PUBLISHED`: the platform reported an error for a post that may have gone out anyway. `UNKNOWN`: unrecognised, so read `errorMessage`."
1444
+ }),
1445
+ title: z.string().register(z.globalRegistry, {
1446
+ description: "Short label for this failure."
1447
+ }),
1448
+ whatHappened: z.string().register(z.globalRegistry, {
1449
+ description: "One sentence about what the platform did."
1450
+ }),
1451
+ whatToDo: z.string().register(z.globalRegistry, {
1452
+ description: "One sentence naming the next action."
1453
+ }),
1454
+ canRetry: z.boolean().register(z.globalRegistry, {
1455
+ description: "Whether retrying can succeed. False when the platform is blocking the account, since the same request will be refused the same way."
1456
+ }),
1457
+ checkPlatformFirst: z.boolean().register(z.globalRegistry, {
1458
+ description: "Whether the post may already be live. A publish is not idempotent and no platform accepts a deduplication key, so when this is true a retry can create a second copy: check the profile before retrying."
1459
+ })
1460
+ });
1361
1461
  var zSocialPlatform = z.enum([
1362
1462
  "TWITTER",
1363
1463
  "INSTAGRAM",
@@ -1382,8 +1482,8 @@ var zPostOverrides = z.object({
1382
1482
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1383
1483
  description: "Video thumbnail timestamp in milliseconds. Applies to Instagram and TikTok."
1384
1484
  })),
1385
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1386
- description: "Replacement media for this account. Completely replaces the post-level media."
1485
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1486
+ description: "Replacement media for this account. Completely replaces the post-level media. Up to 35."
1387
1487
  }))
1388
1488
  });
1389
1489
  var zPlatformSpecificSettings = z.union([
@@ -1575,6 +1675,12 @@ var zPostAccount = z.object({
1575
1675
  errorMessage: z.optional(z.string().register(z.globalRegistry, {
1576
1676
  description: "Error message from the platform if publishing failed for this account."
1577
1677
  })),
1678
+ failure: z.optional(z.intersection(zPublishFailure, z.unknown().register(z.globalRegistry, {
1679
+ description: "The same failure read as an action: what happened and the one thing to do about it. Present whenever status is FAILED, including when the platform gave no reason. The wording is what the app and the failure email show, so a client does not have to interpret `errorMessage` itself."
1680
+ }))),
1681
+ analytics: z.optional(z.intersection(zPostAnalytics, z.unknown().register(z.globalRegistry, {
1682
+ description: "Latest metrics recorded for this delivery. Present only for published posts on platforms with analytics support, once a background sync has run."
1683
+ }))),
1578
1684
  postOverrides: z.optional(zPostOverrides),
1579
1685
  platformSpecificSettings: z.optional(zPlatformSpecificSettings)
1580
1686
  });
@@ -1586,6 +1692,7 @@ var zPost = z.object({
1586
1692
  description: "The shared text content of the post."
1587
1693
  }),
1588
1694
  status: zPostStatus,
1695
+ source: zPostSource,
1589
1696
  errorMessage: z.optional(z.string().register(z.globalRegistry, {
1590
1697
  description: "Human-readable error summary when one or more accounts failed."
1591
1698
  })),
@@ -1627,8 +1734,8 @@ var zCreatePostRequest = z.object({
1627
1734
  content: z._default(z.optional(z.string().register(z.globalRegistry, {
1628
1735
  description: "The text content of the post, shared across all accounts unless overridden. Optional when media is attached (platform-dependent)."
1629
1736
  })), ""),
1630
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1631
- description: "Media to attach to the post. Shared across all accounts unless an account provides its own mediaIds in postOverrides."
1737
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1738
+ description: "Media to attach to the post, up to 35. Shared across all accounts unless an account provides its own mediaIds in postOverrides. Accepts IDs from Upload media and from Create media upload."
1632
1739
  })),
1633
1740
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1634
1741
  description: "Default video thumbnail timestamp in milliseconds for Instagram and TikTok. Can be overridden per account."
@@ -1637,7 +1744,7 @@ var zCreatePostRequest = z.object({
1637
1744
  description: "Accounts to post to. Each entry can optionally include content overrides and platform-specific settings."
1638
1745
  }),
1639
1746
  scheduledAt: z.optional(z.iso.datetime().register(z.globalRegistry, {
1640
- description: "Schedule the post for a future date. If omitted and publishNow is false or absent, the post is saved as a draft."
1747
+ description: "Schedule the post for a future date. If omitted and publishNow is false or absent, the post is saved as a draft. Accepts UTC (2026-04-01T12:00:00Z) or an explicit UTC offset (2026-04-01T14:00:00+02:00); a time without zone information is rejected."
1641
1748
  })),
1642
1749
  publishNow: z.optional(z.boolean().register(z.globalRegistry, {
1643
1750
  description: "Publish immediately. Cannot be combined with scheduledAt."
@@ -1650,10 +1757,10 @@ var zUpdatePostRequest = z.object({
1650
1757
  description: "New shared text content for the post."
1651
1758
  })),
1652
1759
  scheduledAt: z.optional(z.iso.datetime().register(z.globalRegistry, {
1653
- description: "New scheduled date for the post. Must be at least 1 minute in the future."
1760
+ description: "New scheduled date for the post. Must be at least 1 minute in the future. Accepts UTC (2026-04-15T14:00:00Z) or an explicit UTC offset (2026-04-15T16:00:00+02:00); a time without zone information is rejected."
1654
1761
  })),
1655
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1656
- description: "Full replacement for the post media. If omitted, the existing media stays unchanged. If provided, it replaces the current media across all accounts in the post. Send an empty array to remove all media from the post."
1762
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1763
+ description: "Full replacement for the post media, up to 35. If omitted, the existing media stays unchanged. If provided, it replaces the current media across all accounts in the post. Send an empty array to remove all media from the post."
1657
1764
  })),
1658
1765
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1659
1766
  description: "New video thumbnail timestamp in milliseconds for Instagram and TikTok media. Does not change which media files are attached. Combine with mediaIds if you want to replace media and set the new thumbnail timestamp in the same request."
@@ -1762,6 +1869,28 @@ var zMediaListResponse = z.object({
1762
1869
  var zUploadMediaRequest = z.object({
1763
1870
  file: z.string()
1764
1871
  });
1872
+ var zCreateMediaUploadRequest = z.object({
1873
+ filename: z.string().check(z.minLength(1), z.maxLength(1024)).register(z.globalRegistry, {
1874
+ description: "Name of the file you are uploading."
1875
+ }),
1876
+ contentType: z.string().check(z.minLength(1)).register(z.globalRegistry, {
1877
+ description: "Content type of the file. One of: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/webm, video/quicktime, video/x-msvideo."
1878
+ }),
1879
+ fileSize: z.int().check(z.gt(0)).register(z.globalRegistry, {
1880
+ description: "Exact size of the file in bytes. Your plan's file size limit and your storage are checked before you upload, and storage refuses a file of any other size. Each platform's own size limits are checked when you attach the media to a post."
1881
+ })
1882
+ });
1883
+ var zMediaUpload = z.object({
1884
+ id: z.uuid().register(z.globalRegistry, {
1885
+ description: "Media ID of the file. Attach it to posts through mediaIds once the file is uploaded."
1886
+ }),
1887
+ uploadUrl: z.url().register(z.globalRegistry, {
1888
+ description: "Signed storage URL. PUT the file to it with the same Content-Type. It goes to storage, not to the PublishQ API, so send no Authorization header."
1889
+ }),
1890
+ expiresAt: z.iso.datetime().register(z.globalRegistry, {
1891
+ description: "When uploadUrl stops accepting the file, 1 hour after it is created."
1892
+ })
1893
+ });
1765
1894
  var zTikTokCreatorInfoResponse = z.object({
1766
1895
  privacyLevelOptions: z.array(z.string()).register(z.globalRegistry, {
1767
1896
  description: "Privacy levels available for this account. Use one of these values as `privacyLevel` in `platformSpecificSettings`."
@@ -1794,7 +1923,13 @@ var zListPostsQuery = z.object({
1794
1923
  })), 1),
1795
1924
  limit: z._default(z.optional(z.int().check(z.gt(0), z.lte(100)).register(z.globalRegistry, {
1796
1925
  description: "Page size. Max 100."
1797
- })), 20)
1926
+ })), 20),
1927
+ includeImported: z._default(z.optional(z.boolean().register(z.globalRegistry, {
1928
+ description: "Also return posts imported from a connected platform (posts with source IMPORTED). Defaults to false: only posts created through PublishQ are returned."
1929
+ })), false),
1930
+ onlyImported: z._default(z.optional(z.boolean().register(z.globalRegistry, {
1931
+ description: "Return ONLY posts imported from a connected platform. Cannot be combined with includeImported."
1932
+ })), false)
1798
1933
  });
1799
1934
  var zListPostsResponse = zPostsResponse;
1800
1935
  var zCreatePostBody = zCreatePostRequest;
@@ -1865,6 +2000,12 @@ var zGetMediaPath = z.object({
1865
2000
  id: z.uuid()
1866
2001
  });
1867
2002
  var zGetMediaResponse = zMedia;
2003
+ var zCreateMediaUploadBody = zCreateMediaUploadRequest;
2004
+ var zCreateMediaUploadResponse = zMediaUpload;
2005
+ var zCompleteMediaUploadPath = z.object({
2006
+ id: z.uuid()
2007
+ });
2008
+ var zCompleteMediaUploadResponse = zMedia;
1868
2009
 
1869
2010
  // src/index.ts
1870
2011
  var rawErrorBodies = /* @__PURE__ */ new WeakMap();
@@ -1966,12 +2107,92 @@ function createWorkspacesNamespace(client2) {
1966
2107
  get: (id) => preserveApiError(Workspaces.getWorkspace({ client: client2, path: { id } }))
1967
2108
  };
1968
2109
  }
2110
+ var UPLOAD_MIME_TYPE_BY_EXTENSION = {
2111
+ jpg: "image/jpeg",
2112
+ jpeg: "image/jpeg",
2113
+ png: "image/png",
2114
+ gif: "image/gif",
2115
+ webp: "image/webp",
2116
+ mp4: "video/mp4",
2117
+ webm: "video/webm",
2118
+ mov: "video/quicktime",
2119
+ avi: "video/x-msvideo"
2120
+ };
2121
+ function isUninformativeType(type) {
2122
+ return type === "" || type === "application/octet-stream";
2123
+ }
2124
+ function withInferredContentType(file) {
2125
+ const name = "name" in file ? file.name : void 0;
2126
+ if (!name || !isUninformativeType(file.type)) return file;
2127
+ const extension = name.split(".").pop()?.toLowerCase();
2128
+ const type = extension ? UPLOAD_MIME_TYPE_BY_EXTENSION[extension] : void 0;
2129
+ if (!type) return file;
2130
+ return new File([file], name, { type });
2131
+ }
2132
+ var STORAGE_UPLOAD_THRESHOLD_BYTES = 90 * 1024 * 1024;
2133
+ async function uploadThroughStorage(client2, file) {
2134
+ const created = await preserveApiError(
2135
+ Media.createMediaUpload({
2136
+ client: client2,
2137
+ body: {
2138
+ filename: "name" in file && file.name ? file.name : "upload",
2139
+ contentType: file.type,
2140
+ fileSize: file.size
2141
+ }
2142
+ })
2143
+ );
2144
+ if (!created.data) return created;
2145
+ const response = await fetch(created.data.uploadUrl, {
2146
+ method: "PUT",
2147
+ headers: { "Content-Type": file.type },
2148
+ body: file
2149
+ });
2150
+ if (!response.ok) {
2151
+ return {
2152
+ data: void 0,
2153
+ error: {
2154
+ error: {
2155
+ status: response.status,
2156
+ message: "Storage refused the file upload",
2157
+ mediaId: created.data.id,
2158
+ storageResponse: await response.text()
2159
+ }
2160
+ },
2161
+ request: new Request(created.data.uploadUrl, { method: "PUT" }),
2162
+ response
2163
+ };
2164
+ }
2165
+ return preserveApiError(
2166
+ Media.completeMediaUpload({ client: client2, path: { id: created.data.id } })
2167
+ );
2168
+ }
1969
2169
  function createMediaNamespace(client2) {
1970
2170
  return {
1971
2171
  /** List your uploaded and generated media files. Filter by id, type, source. Wraps `Media.listMedia`. */
1972
2172
  list: (query) => preserveApiError(Media.listMedia({ client: client2, query })),
1973
- /** Upload one media file (multipart) and get back a media ID to attach to posts. Wraps `Media.uploadMedia`. */
1974
- upload: (body) => preserveApiError(Media.uploadMedia({ client: client2, body })),
2173
+ /**
2174
+ * Upload one media file and get back a media ID to attach to posts. Wraps
2175
+ * `Media.uploadMedia`, or for files over `STORAGE_UPLOAD_THRESHOLD_BYTES`
2176
+ * runs `createUpload`, sends the file to storage and returns the finished
2177
+ * media from `completeUpload`. Both return the same `Media` object.
2178
+ *
2179
+ * The file's content type is filled in from its name when it has none, so a
2180
+ * file read off disk with `new File([buffer], 'photo.jpg')` is not sent as
2181
+ * `application/octet-stream` and refused. See `withInferredContentType`.
2182
+ */
2183
+ upload: (body) => {
2184
+ const file = withInferredContentType(body.file);
2185
+ if (file.size > STORAGE_UPLOAD_THRESHOLD_BYTES) {
2186
+ return uploadThroughStorage(client2, file);
2187
+ }
2188
+ return preserveApiError(
2189
+ Media.uploadMedia({ client: client2, body: { ...body, file } })
2190
+ );
2191
+ },
2192
+ /** Get a storage upload URL for a file of up to 4 GB. Send the file there, then use the returned media ID in posts. Wraps `Media.createMediaUpload`. */
2193
+ createUpload: (body) => preserveApiError(Media.createMediaUpload({ client: client2, body })),
2194
+ /** Add a file sent to a `createUpload` URL to the media library now and return it. Optional: using the ID in a post does the same. Wraps `Media.completeMediaUpload`. */
2195
+ completeUpload: (id) => preserveApiError(Media.completeMediaUpload({ client: client2, path: { id } })),
1975
2196
  /** Get one media file by ID. Wraps `Media.getMedia`. */
1976
2197
  get: (id) => preserveApiError(Media.getMedia({ client: client2, path: { id } }))
1977
2198
  };
@@ -1983,13 +2204,20 @@ var index_default = PublishQ;
1983
2204
  Media2,
1984
2205
  Posts,
1985
2206
  PublishQ,
2207
+ STORAGE_UPLOAD_THRESHOLD_BYTES,
1986
2208
  Workspaces,
2209
+ withInferredContentType,
1987
2210
  zAccount,
1988
2211
  zAccountsResponse,
1989
2212
  zApiError,
1990
2213
  zApiErrorCode,
1991
2214
  zApiKeyScope,
1992
2215
  zBlueskyPlatformSpecificSettings,
2216
+ zCompleteMediaUploadPath,
2217
+ zCompleteMediaUploadResponse,
2218
+ zCreateMediaUploadBody,
2219
+ zCreateMediaUploadRequest,
2220
+ zCreateMediaUploadResponse,
1993
2221
  zCreatePostAccount,
1994
2222
  zCreatePostBody,
1995
2223
  zCreatePostRequest,
@@ -2022,13 +2250,17 @@ var index_default = PublishQ;
2022
2250
  zMediaListResponse,
2023
2251
  zMediaSource,
2024
2252
  zMediaType,
2253
+ zMediaUpload,
2025
2254
  zPagination,
2026
2255
  zPlatformSpecificSettings,
2027
2256
  zPost,
2028
2257
  zPostAccount,
2258
+ zPostAnalytics,
2029
2259
  zPostOverrides,
2260
+ zPostSource,
2030
2261
  zPostStatus,
2031
2262
  zPostsResponse,
2263
+ zPublishFailure,
2032
2264
  zRemovePostAccountPath,
2033
2265
  zRemovePostAccountResponse,
2034
2266
  zSocialPlatform,