@adeli/sdk 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +30 -2
- package/dist/comments-C_vDnwix.d.cts +8268 -0
- package/dist/comments-Dx3195Ty.d.ts +8268 -0
- package/dist/helpers/index.cjs +482 -0
- package/dist/helpers/index.cjs.map +1 -0
- package/dist/helpers/index.d.cts +207 -0
- package/dist/helpers/index.d.ts +207 -0
- package/dist/helpers/index.js +471 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.cjs +878 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +360 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +860 -0
- package/dist/index.js.map +1 -0
- package/dist/uploads-MGVSU99R.d.cts +100 -0
- package/dist/uploads-MGVSU99R.d.ts +100 -0
- package/dist/uploads.cjs +45 -0
- package/dist/uploads.cjs.map +1 -0
- package/dist/uploads.d.cts +1 -0
- package/dist/uploads.d.ts +1 -0
- package/dist/uploads.js +41 -0
- package/dist/uploads.js.map +1 -0
- package/docs/api-reference.md +72 -0
- package/docs/availability.md +26 -0
- package/docs/client.md +72 -0
- package/docs/maintenance.md +28 -0
- package/docs/publishing.md +39 -0
- package/docs/releasing.md +73 -0
- package/openapi/adeli.openapi.json +20505 -0
- package/openapi/docs-links.json +71 -0
- package/openapi/provenance.json +8 -0
- package/package.json +85 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/uploads.ts"],"names":[],"mappings":";;;;AAYA,eAAsB,YAAA,CAAa,IAAA,EAAc,OAAA,GAAkD,EAAC,EAAkB;AACpH,EAAA,OAAO,UAAA,CAAW,IAAA,EAAM,OAAA,CAAQ,WAAA,KAAgB,MAAA,GAAY,SAAY,EAAE,IAAA,EAAM,OAAA,CAAQ,WAAA,EAAa,CAAA;AACvG;AAkBA,eAAsB,iBACpB,SAAA,EACA,WAAA,EACA,KAAA,EACA,OAAA,GAA+B,EAAC,EACJ;AAC5B,EAAA,IAAI,YAAY,MAAA,KAAW,KAAA,CAAM,QAAQ,MAAM,IAAI,UAAU,0CAA0C,CAAA;AACvG,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,WAAA,CAAY,GAAA,CAAI,CAAC,UAAA,KAAe,CAAC,UAAA,CAAW,QAAA,EAAU,UAAU,CAAC,CAAC,CAAA;AACvF,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,CAAA;AAC3C,EAAA,IAAI,CAAC,MAAA,CAAO,aAAA,CAAc,WAAW,CAAA,IAAK,WAAA,GAAc,CAAA,IAAK,WAAA,GAAc,EAAA,EAAI,MAAM,IAAI,SAAA,CAAU,iDAAiD,CAAA;AACpJ,EAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,CAAC,KAAK,IAAA,KAAS,GAAA,GAAM,IAAA,CAAK,IAAA,EAAM,CAAC,CAAA;AAC5D,EAAA,IAAI,cAAA,GAAiB,CAAA;AACrB,EAAA,IAAI,cAAA,GAAiB,CAAA;AACrB,EAAA,MAAM,MAAA,GAAmB,IAAI,KAAA,CAAM,KAAA,CAAM,MAAM,CAAA;AAC/C,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,MAAM,SAAS,YAAY;AACzB,IAAA,OAAO,IAAA,GAAO,MAAM,MAAA,EAAQ;AAC1B,MAAA,MAAM,KAAA,GAAQ,IAAA,EAAA;AACd,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,KAAK,CAAC,CAAA;AACzC,MAAA,MAAM,IAAA,GAAO,MAAM,KAAK,CAAA;AACxB,MAAA,IAAI,CAAC,UAAA,IAAc,CAAC,MAAM,MAAM,IAAI,UAAU,0CAA0C,CAAA;AACxF,MAAA,MAAM,SAAA,CAAU,OAAO,UAAA,CAAW,GAAA,EAAK,WAAW,MAAA,EAAQ,UAAA,CAAW,OAAA,EAAS,IAAA,EAAM,OAAO,CAAA;AAC3F,MAAA,cAAA,IAAkB,IAAA,CAAK,IAAA;AACvB,MAAA,cAAA,IAAkB,CAAA;AAClB,MAAA,OAAA,CAAQ,UAAA,GAAa,EAAE,MAAA,EAAQ,cAAA,EAAgB,OAAO,cAAA,EAAgB,UAAA,EAAY,KAAA,CAAM,MAAA,EAAQ,CAAA;AAChG,MAAA,MAAA,CAAO,KAAK,IAAI,UAAA,CAAW,QAAA;AAAA,IAC7B;AAAA,EACF,CAAA;AACA,EAAA,MAAM,QAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,EAAE,QAAQ,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,KAAA,CAAM,MAAM,CAAA,EAAE,EAAG,MAAM,MAAA,EAAQ,CAAC,CAAA;AAC7F,EAAA,OAAO,MAAA;AACT;AAGA,eAAsB,YAAA,CAAa,IAAA,EAAc,OAAA,GAA6B,EAAC,EAAkB;AAC/F,EAAA,MAAM,IAAA,GAAO,MAAM,YAAA,CAAa,IAAA,EAAM,OAAO,CAAA;AAC7C,EAAA,OAAO,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,OAAA,CAAQ,QAAQ,QAAA,CAAS,IAAI,CAAA,EAAG,OAAA,CAAQ,gBAAgB,MAAA,GAAY,MAAA,GAAY,EAAE,IAAA,EAAM,OAAA,CAAQ,aAAa,CAAA;AACvI","file":"uploads.js","sourcesContent":["import { basename } from 'node:path';\nimport { openAsBlob } from 'node:fs';\nimport type { RequestOptions } from './core/types.js';\nimport type { Transport } from './core/transport.js';\n\n/** Options for a file-backed upload. The bytes remain backed by the source file until consumed by fetch. */\nexport interface FileUploadOptions {\n readonly contentType?: string;\n readonly name?: string;\n}\n\n/** Open a local file as a native Blob without reading the media into memory. */\nexport async function blobFromPath(path: string, options: Pick<FileUploadOptions, 'contentType'> = {}): Promise<Blob> {\n return openAsBlob(path, options.contentType === undefined ? undefined : { type: options.contentType });\n}\n\nexport interface DirectUploadDescriptor {\n readonly clientId: string;\n readonly uploadId: string;\n readonly method: 'PUT';\n readonly url: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly expiresAt: string;\n}\n\nexport interface DirectUploadProgress { readonly loaded: number; readonly total: number; readonly completedFiles: number; readonly totalFiles: number }\nexport interface DirectUploadOptions extends RequestOptions {\n readonly concurrency?: number;\n readonly onProgress?: (progress: DirectUploadProgress) => void;\n}\n\n/** PUT file/blob bodies with exact signed headers, bounded concurrency, cancellation, and stable result order. */\nexport async function putDirectUploads(\n transport: Transport,\n descriptors: readonly DirectUploadDescriptor[],\n files: readonly Blob[],\n options: DirectUploadOptions = {},\n): Promise<readonly string[]> {\n if (descriptors.length !== files.length) throw new TypeError('The media upload manifest was incomplete');\n const byId = new Map(descriptors.map((descriptor) => [descriptor.clientId, descriptor]));\n const concurrency = options.concurrency ?? 3;\n if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 10) throw new TypeError('concurrency must be an integer between 1 and 10');\n const total = files.reduce((sum, file) => sum + file.size, 0);\n let completedBytes = 0;\n let completedFiles = 0;\n const result: string[] = new Array(files.length);\n let next = 0;\n const worker = async () => {\n while (next < files.length) {\n const index = next++;\n const descriptor = byId.get(String(index));\n const file = files[index];\n if (!descriptor || !file) throw new TypeError('The media upload manifest was incomplete');\n await transport.upload(descriptor.url, descriptor.method, descriptor.headers, file, options);\n completedBytes += file.size;\n completedFiles += 1;\n options.onProgress?.({ loaded: completedBytes, total, completedFiles, totalFiles: files.length });\n result[index] = descriptor.uploadId;\n }\n };\n await Promise.all(Array.from({ length: Math.min(concurrency, files.length) }, () => worker()));\n return result;\n}\n\n/** Open a local file as a native File, retaining its filename and MIME type for direct upload. */\nexport async function fileFromPath(path: string, options: FileUploadOptions = {}): Promise<File> {\n const blob = await blobFromPath(path, options);\n return new File([blob], options.name ?? basename(path), options.contentType === undefined ? undefined : { type: options.contentType });\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
Generated from `src/operations.ts` metadata, AST-extracted resource method declarations, and the checked-in verified public docs mapping `openapi/docs-links.json`; this file is not hand-maintained.
|
|
4
|
+
|
|
5
|
+
- Contract status: **candidate-unreleased**
|
|
6
|
+
- Candidate OpenAPI SHA-256: `e1a4cd95b91e14c2732275213493caebaa7ee3d83dffec27c001511eda32fc35`
|
|
7
|
+
- Public endpoint documentation: https://www.tryadeli.com/docs/
|
|
8
|
+
|
|
9
|
+
| Resource | Method signature | HTTP | Path | Endpoint docs |
|
|
10
|
+
| --- | --- | --- | --- | --- |
|
|
11
|
+
| Adeli | `getMe(args: { readonly query?: GetMeQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<Identity>>` | GET | `/me` | [api/me](https://www.tryadeli.com/docs/api/me) |
|
|
12
|
+
| profiles | `listProfiles(_args: EmptyArgs = {}, options: RequestOptions = {}): Promise<ApiResponse<ListProfilesResponse>>` | GET | `/profiles` | [api/profiles/list-profiles](https://www.tryadeli.com/docs/api/profiles/list-profiles) |
|
|
13
|
+
| profiles | `createProfile(args: { readonly body: CreateProfileBody }, options: RequestOptions & { readonly idempotencyKey?: string } = {}): Promise<ApiResponse<Profile>>` | POST | `/profiles` | [api/profiles/create-profile](https://www.tryadeli.com/docs/api/profiles/create-profile) |
|
|
14
|
+
| profiles | `getProfile(args: { readonly path: ProfilePath }, options: RequestOptions = {}): Promise<ApiResponse<Profile>>` | GET | `/profiles/{profileId}` | [api/profiles/get-profile](https://www.tryadeli.com/docs/api/profiles/get-profile) |
|
|
15
|
+
| profiles | `updateProfile(args: { readonly path: ProfilePath; readonly body: UpdateProfileBody }, options: RequestOptions = {}): Promise<ApiResponse<Profile>>` | PUT | `/profiles/{profileId}` | [api/profiles/update-profile](https://www.tryadeli.com/docs/api/profiles/update-profile) |
|
|
16
|
+
| profiles | `deleteProfile(args: { readonly path: ProfilePath }, options: RequestOptions = {}): Promise<ApiResponse<DeleteProfileResponse>>` | DELETE | `/profiles/{profileId}` | [api/profiles/delete-profile](https://www.tryadeli.com/docs/api/profiles/delete-profile) |
|
|
17
|
+
| profiles | `startConnection(args: { readonly path: ProfilePath; readonly body: StartConnectionBody }, options: RequestOptions = {}): Promise<ApiResponse<ConnectionSession>>` | POST | `/profiles/{profileId}/connect` | [api/connect/start-connection](https://www.tryadeli.com/docs/api/connect/start-connection) |
|
|
18
|
+
| profiles | `getConnection(args: { readonly path: ProfilePath & { readonly connectionSessionId: string } }, options: RequestOptions = {}): Promise<ApiResponse<ConnectionSession>>` | GET | `/profiles/{profileId}/connect/{connectionSessionId}` | [api/connect/get-connection-session](https://www.tryadeli.com/docs/api/connect/get-connection-session) |
|
|
19
|
+
| accounts | `listAccounts(args: { readonly query?: ListAccountsQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<AccountsAggregateResponse>>` | GET | `/accounts` | [api/accounts/list-accounts](https://www.tryadeli.com/docs/api/accounts/list-accounts) |
|
|
20
|
+
| accounts | `listProfileAccounts(args: { readonly path: { readonly profileId: string } }, options: RequestOptions = {}): Promise<ApiResponse<AccountsAggregateResponse>>` | GET | `/profiles/{profileId}/accounts` | [api/accounts/list-profile-accounts](https://www.tryadeli.com/docs/api/accounts/list-profile-accounts) |
|
|
21
|
+
| accounts | `getAccount(args: { readonly path: AccountPath }, options: RequestOptions = {}): Promise<ApiResponse<AccountDetailResponse>>` | GET | `/profiles/{profileId}/accounts/{accountId}` | [api/accounts/get-account](https://www.tryadeli.com/docs/api/accounts/get-account) |
|
|
22
|
+
| accounts | `updateAccount(args: { readonly path: AccountPath; readonly body: UpdateAccountBody }, options: RequestOptions = {}): Promise<ApiResponse<AccountUpdateResponse>>` | PATCH | `/profiles/{profileId}/accounts/{accountId}` | [api/accounts/update-x-account](https://www.tryadeli.com/docs/api/accounts/update-x-account) |
|
|
23
|
+
| accounts | `disconnectAccount(args: { readonly path: AccountPath }, options: RequestOptions = {}): Promise<ApiResponse<DisconnectAccountResponse>>` | DELETE | `/profiles/{profileId}/accounts/{accountId}` | [api/accounts/disconnect-account](https://www.tryadeli.com/docs/api/accounts/disconnect-account) |
|
|
24
|
+
| accounts | `refreshAccounts(args: { readonly path: { readonly profileId: string } }, options: RequestOptions = {}): Promise<ApiResponse<RefreshAccountsResponse>>` | POST | `/profiles/{profileId}/accounts/refresh` | [api/accounts/refresh-tokens](https://www.tryadeli.com/docs/api/accounts/refresh-tokens) |
|
|
25
|
+
| posts | `listPosts(args: { readonly query?: PostsQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<PostListResponse>>` | GET | `/posts` | [api/posts/list-posts](https://www.tryadeli.com/docs/api/posts/list-posts) |
|
|
26
|
+
| posts | `createMediaUploads(args: { readonly body: CreateMediaUploadsBody }, options: RequestOptions = {}): Promise<ApiResponse<CreateMediaUploadsResponse>>` | POST | `/media/uploads` | [api/posts/create-media-uploads](https://www.tryadeli.com/docs/api/posts/create-media-uploads) |
|
|
27
|
+
| posts | `createPost(args: { readonly body: CreatePostWithFiles \| TikTokMultipartBody }, options: DirectUploadOptions = {}): Promise<ApiResponse<SuccessBody<'createPost'>>>` | POST | `/posts` | [api/posts/publish-instagram](https://www.tryadeli.com/docs/api/posts/publish-instagram)<br>[api/posts/publish-facebook](https://www.tryadeli.com/docs/api/posts/publish-facebook)<br>[api/posts/publish-tiktok](https://www.tryadeli.com/docs/api/posts/publish-tiktok)<br>[api/posts/publish-youtube](https://www.tryadeli.com/docs/api/posts/publish-youtube)<br>[api/posts/publish-x](https://www.tryadeli.com/docs/api/posts/publish-x)<br>[api/posts/publish-bluesky](https://www.tryadeli.com/docs/api/posts/publish-bluesky) |
|
|
28
|
+
| posts | `uploadYouTubeVideo(args: { readonly body: YouTubeUploadBody \| YouTubeStagedUploadBody }, options: DirectUploadOptions = {}): Promise<ApiResponse<YouTubeUploadResponse>>` | POST | `/posts/youtube` | [api/posts/upload-youtube-video-file](https://www.tryadeli.com/docs/api/posts/upload-youtube-video-file) |
|
|
29
|
+
| posts | `deleteXPost(args: { readonly path: Path<'deleteXPost'>; readonly query: JsonQuery<'deleteXPost'> }, options: RequestOptions = {}): Promise<ApiResponse<DeletePostResponse>>` | DELETE | `/posts/x/{postId}` | [api/posts/delete-x-post](https://www.tryadeli.com/docs/api/posts/delete-x-post) |
|
|
30
|
+
| posts | `deleteBlueskyPost(args: { readonly path: Path<'deleteBlueskyPost'>; readonly query: JsonQuery<'deleteBlueskyPost'> }, options: RequestOptions = {}): Promise<ApiResponse<SuccessBody<'deleteBlueskyPost'>>>` | DELETE | `/posts/bluesky/{postId}` | [api/posts/delete-bluesky-post](https://www.tryadeli.com/docs/api/posts/delete-bluesky-post) |
|
|
31
|
+
| posts | `getInstagramPostStatus(args: { readonly query: JsonQuery<'getInstagramPostStatus'> }, options: RequestOptions = {}): Promise<ApiResponse<InstagramPostStatusResponse>>` | GET | `/posts/instagram/status` | [api/posts/get-instagram-publish-status](https://www.tryadeli.com/docs/api/posts/get-instagram-publish-status) |
|
|
32
|
+
| posts | `getFacebookPostStatus(args: { readonly query: JsonQuery<'getFacebookPostStatus'> }, options: RequestOptions = {}): Promise<ApiResponse<FacebookPostStatusResponse>>` | GET | `/posts/facebook/status` | [api/posts/get-facebook-video-status](https://www.tryadeli.com/docs/api/posts/get-facebook-video-status) |
|
|
33
|
+
| posts | `getTikTokPostStatus(args: { readonly query: JsonQuery<'getTikTokPostStatus'> }, options: RequestOptions = {}): Promise<ApiResponse<TikTokPostStatusResponse>>` | GET | `/posts/tiktok/status` | [api/posts/get-tiktok-publish-status](https://www.tryadeli.com/docs/api/posts/get-tiktok-publish-status) |
|
|
34
|
+
| posts | `getTikTokCreatorInfo(args: { readonly query: JsonQuery<'getTikTokCreatorInfo'> }, options: RequestOptions = {}): Promise<ApiResponse<TikTokCreatorInfoResponse>>` | GET | `/posts/tiktok/creator-info` | [api/posts/get-tiktok-creator-info](https://www.tryadeli.com/docs/api/posts/get-tiktok-creator-info) |
|
|
35
|
+
| posts | `getYouTubeVideoStatus(args: { readonly query: JsonQuery<'getYouTubeVideoStatus'> }, options: RequestOptions = {}): Promise<ApiResponse<YouTubeVideoStatusResponse>>` | GET | `/posts/youtube/status` | [api/posts/get-youtube-upload-status](https://www.tryadeli.com/docs/api/posts/get-youtube-upload-status) |
|
|
36
|
+
| comments | `listComments(args: { readonly query: CommentsQuery }, options: RequestOptions = {}): Promise<ApiResponse<CommentsResponse>>` | GET | `/comments` | [api/comments/list-comments](https://www.tryadeli.com/docs/api/comments/list-comments) |
|
|
37
|
+
| comments | `createComment(args: { readonly body: CreateCommentBody }, options: RequestOptions = {}): Promise<ApiResponse<CommentMutationResponse>>` | POST | `/comments` | [api/comments/create-comment](https://www.tryadeli.com/docs/api/comments/create-comment) |
|
|
38
|
+
| comments | `updateComment(args: { readonly path: Path<'updateComment'>; readonly body: UpdateCommentBody }, options: RequestOptions = {}): Promise<ApiResponse<UpdatedCommentResponse>>` | PATCH | `/comments/{commentId}` | [api/comments/update-comment](https://www.tryadeli.com/docs/api/comments/update-comment) |
|
|
39
|
+
| comments | `deleteComment(args: { readonly path: Path<'deleteComment'>; readonly query: JsonQuery<'deleteComment'> }, options: RequestOptions = {}): Promise<ApiResponse<undefined>>` | DELETE | `/comments/{commentId}` | [api/comments/delete-comment](https://www.tryadeli.com/docs/api/comments/delete-comment) |
|
|
40
|
+
| messages | `listMessages(args: { readonly query?: MessagesQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<MessagesResponse>>` | GET | `/messages` | [api/messages/list-messages](https://www.tryadeli.com/docs/api/messages/list-messages) |
|
|
41
|
+
| messages | `sendMessage(args: { readonly body: SendMessageBody }, options: RequestOptions = {}): Promise<ApiResponse<SentMessage>>` | POST | `/messages` | [api/messages/send-message](https://www.tryadeli.com/docs/api/messages/send-message) |
|
|
42
|
+
| messages | `sendInstagramMessage(args: { readonly body: SendInstagramMessageBody }, options: RequestOptions = {}): Promise<ApiResponse<SentInstagramMessage>>` | POST | `/messages/instagram` | [api/messages/send-instagram-message](https://www.tryadeli.com/docs/api/messages/send-instagram-message) |
|
|
43
|
+
| analytics | `getInstagramAccountInsights(args: { readonly query: InstagramAccountInsightsQuery }, options: RequestOptions = {}): Promise<ApiResponse<InstagramAccountInsightsResponse>>` | GET | `/analytics/instagram/account-insights` | [api/analytics/instagram-account-insights](https://www.tryadeli.com/docs/api/analytics/instagram-account-insights) |
|
|
44
|
+
| analytics | `getFacebookPageMetrics(args: { readonly query?: FacebookPageMetricsQuery }, options: RequestOptions = {}): Promise<ApiResponse<FacebookPageMetricsResponse>>` | GET | `/analytics/facebook/page-metrics` | [api/analytics/facebook-page-metrics](https://www.tryadeli.com/docs/api/analytics/facebook-page-metrics) |
|
|
45
|
+
| analytics | `getTikTokAccountAnalytics(args: { readonly query: TikTokAccountAnalyticsQuery }, options: RequestOptions = {}): Promise<ApiResponse<TikTokAccountAnalyticsResponse>>` | GET | `/analytics/tiktok/account-analytics` | [api/analytics/tiktok-account-analytics](https://www.tryadeli.com/docs/api/analytics/tiktok-account-analytics) |
|
|
46
|
+
| analytics | `getTikTokAccountInsights(args: { readonly query: TikTokAccountInsightsQuery }, options: RequestOptions = {}): Promise<ApiResponse<TikTokAccountInsightsResponse>>` | GET | `/analytics/tiktok/account-insights` | [api/analytics/tiktok-account-insights](https://www.tryadeli.com/docs/api/analytics/tiktok-account-insights) |
|
|
47
|
+
| analytics | `getTikTokVideoInsights(args: { readonly query: TikTokVideoInsightsQuery }, options: RequestOptions = {}): Promise<ApiResponse<TikTokVideoInsightsResponse>>` | GET | `/analytics/tiktok/video-insights` | [api/analytics/tiktok-post-insights](https://www.tryadeli.com/docs/api/analytics/tiktok-post-insights) |
|
|
48
|
+
| analytics | `getYouTubeChannelInsights(args: { readonly query: YouTubeChannelInsightsQuery }, options: RequestOptions = {}): Promise<ApiResponse<YouTubeChannelInsightsResponse>>` | GET | `/analytics/youtube/channel-insights` | [api/analytics/youtube-channel-insights](https://www.tryadeli.com/docs/api/analytics/youtube-channel-insights) |
|
|
49
|
+
| analytics | `getYouTubeVideoInsights(args: { readonly query: YouTubeVideoInsightsQuery }, options: RequestOptions = {}): Promise<ApiResponse<YouTubeVideoInsightsResponse>>` | GET | `/analytics/youtube/video-insights` | [api/analytics/youtube-video-insights](https://www.tryadeli.com/docs/api/analytics/youtube-video-insights) |
|
|
50
|
+
| ads | `listAdBusinesses(args: { readonly query?: AdBusinessesQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<ListAdBusinessesResponse>>` | GET | `/ads/businesses` | [api/ads/list-businesses](https://www.tryadeli.com/docs/api/ads/list-businesses) |
|
|
51
|
+
| ads | `listAdAccounts(args: { readonly query?: AdAccountsQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<ListAdAccountsResponse>>` | GET | `/ads/accounts` | [api/ads/list-ad-accounts](https://www.tryadeli.com/docs/api/ads/list-ad-accounts) |
|
|
52
|
+
| ads | `createAdAccount(args: { readonly query?: CreateAdAccountQuery; readonly body: CreateAdAccountBody }, options: IdempotentOptions): Promise<ApiResponse<CreateAdAccountResponse>>` | POST | `/ads/accounts` | [api/ads/create-ad-account](https://www.tryadeli.com/docs/api/ads/create-ad-account) |
|
|
53
|
+
| ads | `getAdAccount(args: { readonly path: AdAccountPath; readonly query?: AdAccountQuery }, options: RequestOptions = {}): Promise<ApiResponse<AdAccountResponse>>` | GET | `/ads/accounts/{adAccountId}` | [api/ads/get-ad-account](https://www.tryadeli.com/docs/api/ads/get-ad-account) |
|
|
54
|
+
| ads | `updateAdAccount(args: { readonly path: UpdateAdAccountPath; readonly query?: AdAccountQuery; readonly body: UpdateAdAccountBody }, options: RequestOptions = {}): Promise<ApiResponse<AdAccountResponse>>` | PATCH | `/ads/accounts/{adAccountId}` | [api/ads/update-ad-account](https://www.tryadeli.com/docs/api/ads/update-ad-account) |
|
|
55
|
+
| ads | `getAdTree(args: { readonly query: AdTreeQuery }, options: RequestOptions = {}): Promise<ApiResponse<AdTreeResponse>>` | GET | `/ads/tree` | [api/ads/get-campaign-tree](https://www.tryadeli.com/docs/api/ads/get-campaign-tree) |
|
|
56
|
+
| ads | `getAdInsights(args: { readonly query: AdInsightsQuery }, options: RequestOptions = {}): Promise<ApiResponse<AdInsightsResponse>>` | GET | `/ads/insights` | [api/ads/get-insights](https://www.tryadeli.com/docs/api/ads/get-insights) |
|
|
57
|
+
| ads | `listAdLeads(args: { readonly query?: AdLeadsQuery } = {}, options: RequestOptions = {}): Promise<ApiResponse<AdLeadsResponse>>` | GET | `/ads/leads` | [api/ads/list-leads](https://www.tryadeli.com/docs/api/ads/list-leads) |
|
|
58
|
+
| ads | `createCampaign(args: { readonly query?: CreateCampaignQuery; readonly body: CreateCampaignBody }, options: IdempotentOptions): Promise<ApiResponse<CampaignResponse>>` | POST | `/ads/campaigns` | [api/ads/create-campaign](https://www.tryadeli.com/docs/api/ads/create-campaign) |
|
|
59
|
+
| ads | `deleteCampaign(args: { readonly path: DeleteCampaignPath; readonly query: JsonQuery<'deleteCampaign'> }, options: RequestOptions = {}): Promise<ApiResponse<undefined>>` | DELETE | `/ads/campaigns/{campaignId}` | [api/ads/delete-campaign](https://www.tryadeli.com/docs/api/ads/delete-campaign) |
|
|
60
|
+
| ads | `setAdStatus(args: { readonly query?: JsonQuery<'setAdStatus'>; readonly body: SetAdStatusBody }, options: RequestOptions = {}): Promise<ApiResponse<AdStatusResponse>>` | PUT | `/ads/status` | [api/ads/update-status](https://www.tryadeli.com/docs/api/ads/update-status) |
|
|
61
|
+
| ads | `updateAdGroup(args: { readonly path: UpdateAdGroupPath; readonly query?: UpdateAdGroupQuery; readonly body: UpdateAdGroupBody }, options: RequestOptions = {}): Promise<ApiResponse<AdGroupResponse>>` | PATCH | `/ads/ad-groups/{adGroupId}` | [api/ads/update-ad-group](https://www.tryadeli.com/docs/api/ads/update-ad-group) |
|
|
62
|
+
| ads | `listAdTargeting(args: { readonly query: AdTargetingQuery }, options: RequestOptions = {}): Promise<ApiResponse<AdTargetingResponse>>` | GET | `/ads/targeting` | [api/ads/list-targeting](https://www.tryadeli.com/docs/api/ads/list-targeting) |
|
|
63
|
+
| ads | `listAdIdentities(args: { readonly query: AdIdentitiesQuery }, options: RequestOptions = {}): Promise<ApiResponse<AdIdentitiesResponse>>` | GET | `/ads/identities` | [api/ads/list-identities](https://www.tryadeli.com/docs/api/ads/list-identities) |
|
|
64
|
+
| ads | `createAdIdentity(args: { readonly query?: CreateAdIdentityQuery; readonly body: CreateAdIdentityBody }, options: RequestOptions = {}): Promise<ApiResponse<AdIdentityResponse>>` | POST | `/ads/identities` | [api/ads/create-identity](https://www.tryadeli.com/docs/api/ads/create-identity) |
|
|
65
|
+
| usage | `getXUsage(_args: Record<never, never> = {}, options: RequestOptions = {}): Promise<ApiResponse<XUsageResponse>>` | GET | `/usage/x` | [pricing/x](https://www.tryadeli.com/docs/pricing/x) |
|
|
66
|
+
| webhooks | `listWebhooks(args: { readonly query: ListWebhooksQuery }, options: RequestOptions = {}): Promise<ApiResponse<WebhooksResponse>>` | GET | `/webhooks` | [api/webhooks/list-webhooks](https://www.tryadeli.com/docs/api/webhooks/list-webhooks) |
|
|
67
|
+
| webhooks | `createWebhook(args: { readonly body: CreateWebhookBody }, options: RequestOptions = {}): Promise<ApiResponse<CreatedWebhook>>` | POST | `/webhooks` | [api/webhooks/create-webhook](https://www.tryadeli.com/docs/api/webhooks/create-webhook) |
|
|
68
|
+
| webhooks | `deleteWebhook(args: { readonly path: Path<'deleteWebhook'> }, options: RequestOptions = {}): Promise<ApiResponse<undefined>>` | DELETE | `/webhooks/{webhookId}` | [api/webhooks/delete-webhook](https://www.tryadeli.com/docs/api/webhooks/delete-webhook) |
|
|
69
|
+
|
|
70
|
+
## Compatibility note
|
|
71
|
+
|
|
72
|
+
The SHA-256 above identifies a local, unreleased candidate snapshot. It is provenance, not proof of deployment or npm publication. Regenerate after a reviewed contract update and inspect status, nullability, error, and provider-outcome changes.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Feature availability
|
|
2
|
+
|
|
3
|
+
The SDK types the whole public contract, but not every operation is live on every Adeli deployment. Production (`https://app.tryadeli.com`) offers only approved features. The beta deployment also offers features that are in provider review. Availability is not part of the OpenAPI contract, so the SDK cannot check it at compile time. Read the runtime signals below.
|
|
4
|
+
|
|
5
|
+
## Production
|
|
6
|
+
|
|
7
|
+
| Platform | Available in production | Beta only |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `instagram` | Connect, profile, `listPosts`, messages | Publishing, analytics, comments |
|
|
10
|
+
| `facebook` | Connect, profile, Page selection, `listPosts`, messages | Publishing, analytics, comments |
|
|
11
|
+
| `tiktok` | Connect, profile, `listPosts`, publishing, analytics, comments, refresh | — |
|
|
12
|
+
| `youtube` | Connect, profile, `listPosts`, publishing, analytics, comments, refresh | — |
|
|
13
|
+
| `x` | Connect, profile, publishing, refresh | — |
|
|
14
|
+
| `bluesky` | Connect, profile, publishing | — |
|
|
15
|
+
| `meta_ads` | — | Connect and every `client.ads` operation |
|
|
16
|
+
| `tiktok_ads` | — | Connect and every `client.ads` operation |
|
|
17
|
+
|
|
18
|
+
`whatsapp` appears in the account and connect platform enums, but `startConnection` does not support it yet and answers `422 provider_not_supported`.
|
|
19
|
+
|
|
20
|
+
## Runtime signals
|
|
21
|
+
|
|
22
|
+
- **Ads.** On a deployment without Meta Ads or TikTok Ads, `client.ads` calls for that network answer `404 not_found`, and `startConnection` with `platform: 'meta_ads'` answers `422 provider_not_configured`.
|
|
23
|
+
- **Instagram and Facebook.** Publishing, analytics, and comments in production depend on Meta-approved permissions. Read the account's `capabilities`, `missingPermissions`, and `missingResources` before calling; a call without them throws `PermissionRequiredError` (`permission_required`).
|
|
24
|
+
- **Other platforms.** A capability the account was not granted reports `false` in `capabilities` and fails with `permission_required`.
|
|
25
|
+
|
|
26
|
+
`baseURL: 'https://beta.tryadeli.com'` reaches the beta deployment. It is an opt-in App Review target, not a release channel, an API version, or the SDK's default origin; a feature there is not yet available in production.
|
package/docs/client.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Client and request semantics
|
|
2
|
+
|
|
3
|
+
`@adeli/sdk` is a server-only SDK for Node.js 24+. It is not supported in browsers, Bun, Deno, or edge runtimes. Construct `Adeli` only in a caller-invoked server function; module import does not read environment variables or make requests.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Adeli } from '@adeli/sdk';
|
|
7
|
+
const client = new Adeli({ apiKey: process.env.ADELI_API_KEY });
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`apiKey` wins over `ADELI_API_KEY`. `baseURL` is an origin (not an origin plus `/api/v1`); production origins must use HTTPS, while HTTP is reserved for explicit local development. `timeoutMs` is a total local deadline and `maxRetries` is opt-in. Timer values must be positive, finite, and no greater than `2_147_483_647` milliseconds (about 24.8 days); larger values are rejected before a request rather than being clamped by Node. The default retry count is zero. With opt-in retries, audited safe reads and profile creation with a stable idempotency key qualify. Daily quota exhaustion, permission/billing/semantic conflicts, and other known non-transient failures are never retried. `listPosts` is conservatively excluded because an X read can be billed; `getXUsage` is a safe read of Adeli's stored usage ledger and does not call X itself, so it is eligible for the ordinary transient-read policy.
|
|
11
|
+
|
|
12
|
+
## Requests and responses
|
|
13
|
+
|
|
14
|
+
Resource methods use named `{ path, query, body }` arguments. Paths and query values are encoded by the transport. The SDK preserves the wire body: provider-specific casing such as `contentType` and `content_type`, `null`, privacy/visibility, warnings, and partial records remain unchanged. Every successful call returns `{ data, status, headers }`; `202`, `207`, and `204` are valid success statuses (`204` has `data: undefined`). Collection `status: partial` is still a successful response and contains usable records plus per-account errors.
|
|
15
|
+
|
|
16
|
+
Most operations require a profile or connected account scope. Organization-wide account listing is distinct from profile-scoped listing. Permissions/capabilities can be partial or nullable; inspect `missingPermissions`, `missingResources`, `capabilities`, and reconnect guidance rather than assuming a connected account can perform every operation. Public connection starts the typed flow but does not replace Facebook or Meta Ads Page selection or WhatsApp dashboard Embedded Signup. `client.ads` operations take a Meta Ads account (`platform: 'meta_ads'`), which is connected separately from the profile's Facebook account; a Facebook `accountId` returns `account_not_found`.
|
|
17
|
+
|
|
18
|
+
## Polling and comment iteration
|
|
19
|
+
|
|
20
|
+
Polling helpers are explicit bounded workflows: pass both a positive `intervalMs` and positive total `timeoutMs` (each no greater than `2_147_483_647` milliseconds), and optionally an `AbortSignal`. `waitForConnection`, `waitForInstagramPost`, `waitForTikTokPost`, `waitForFacebookPost`, and `waitForYouTubeVideo` return the terminal API response, including provider failure states; they throw only for local timeout, cancellation, or an unfamiliar provider status. Facebook video `READY`/`ERROR` is separate from a connection's `pending_page_selection` dashboard handoff.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { waitForYouTubeVideo } from '@adeli/sdk/helpers';
|
|
24
|
+
|
|
25
|
+
const status = await waitForYouTubeVideo(
|
|
26
|
+
client,
|
|
27
|
+
{ query: { accountId, videoId } },
|
|
28
|
+
{ intervalMs: 2_000, timeoutMs: 300_000, signal },
|
|
29
|
+
);
|
|
30
|
+
if (status.data.status === 'failed') throw new Error(status.data.failureReason ?? 'YouTube processing failed');
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`iterateCommentPages` and `iterateComments` support TikTok and YouTube only. They accept `maxPages` (default `100`, always finite), optional total `timeoutMs` no greater than `2_147_483_647` milliseconds, and optional `signal`; they stop on no cursor, an empty/repeated cursor, or the page limit.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { iterateCommentPages } from '@adeli/sdk/helpers';
|
|
37
|
+
|
|
38
|
+
for await (const page of iterateCommentPages(
|
|
39
|
+
client,
|
|
40
|
+
{ query: { platform: 'youtube', accountId } },
|
|
41
|
+
{ maxPages: 100, timeoutMs: 60_000, signal },
|
|
42
|
+
)) {
|
|
43
|
+
for (const comment of page.data.comments) console.log(comment.id);
|
|
44
|
+
// page.data.complete/syncedAt describe cache freshness, not pagination.
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The iterator pins the first authoritative `postId` when the initial query omitted one and preserves page cache metadata such as `complete`/`syncedAt`; that metadata describes freshness/completeness of the returned page, not whether `nextCursor` exists.
|
|
49
|
+
|
|
50
|
+
## Deadlines, cancellation, and errors
|
|
51
|
+
|
|
52
|
+
Pass an `AbortSignal` and/or per-call `timeoutMs` in the second argument. Cancellation stops this client request; it cannot undo work already accepted by Adeli. Polling helpers have a separate total deadline and never fetch a returned OAuth/status URL directly. Treat an aborted write as an ambiguous remote outcome and inspect the provider before resubmitting.
|
|
53
|
+
|
|
54
|
+
Catch `AdeliApiError` for an HTTP/API response. Its `status`, `code`, `details`, and `headers` are preserved; subclasses include `AuthenticationError`, `ValidationError`, `PermissionRequiredError`, and `RateLimitError`. `ConfigurationError`, `NetworkError`, `TimeoutError`, `CancellationError`, and `MalformedResponseError` represent local/client failures. Do not log API keys, OAuth URLs, media, message bodies, or provider payloads by default.
|
|
55
|
+
|
|
56
|
+
Error `details` remain `unknown`: validation arrays and provider-specific objects are not interchangeable. Use the exported `isPermissionRequiredDetails(error.details)` guard before reading known permission fields. It accepts sparse reconnect guidance, validates known fields, and leaves unknown extensions untouched. A false result does not invalidate the API error. Even recognized `reconnect.href` values are advisory data, not URLs the SDK will automatically fetch; restart authorization through the typed connection methods with explicit scope.
|
|
57
|
+
|
|
58
|
+
## Scope and safety
|
|
59
|
+
|
|
60
|
+
Profile creation accepts an optional idempotency key. Ad-account and campaign creation require keys and do not auto-retry. Publishing has no generic idempotency guarantee. The X usage response is a stored Adeli ledger: `totalCostMicros` and each `operations[].costMicros` are millionths of a US dollar. Preserve these integer values precisely; the SDK does not implicitly convert or round them to dollars. Actual X post/analytics provider calls can incur charges; Ads and destructive calls require separate approval and cleanup planning. The SDK does not claim to enforce every evolving provider-side semantic rule; the API remains authoritative.
|
|
61
|
+
|
|
62
|
+
## Ads on Meta and TikTok
|
|
63
|
+
|
|
64
|
+
`client.ads` serves both networks. Pass the ads connection's `accountId` (a Meta Ads or a `tiktok_ads` account); with only `profileId`, a profile holding both networks answers `400 ambiguous_account` unless the query also names `platform`. Each response reports its `platform`. TikTok advertiser ids are digits only, and TikTok ad groups arrive under the tree's `adSets`. `createCampaign` takes the Meta body (a Page post) or the TikTok body (video creative, targeting, identity); TikTok objects are created disabled, and `setAdStatus` needs `level` for TikTok. `updateAdGroup`, `listAdTargeting`, `listAdIdentities`, and `createAdIdentity` are TikTok-only; Meta-only operations on a TikTok account, and the reverse, answer `400 unsupported_for_platform`.
|
|
65
|
+
|
|
66
|
+
## Direct media uploads
|
|
67
|
+
|
|
68
|
+
Pass `Blob`/`File` values in media fields (including Instagram/Facebook media, TikTok video/photos, YouTube video/thumbnail, X media/thread parts, and Bluesky images/video). The posts resource resolves the profile when necessary, calls `createMediaUploads`, PUTs each raw body to its presigned S3 URL with the returned headers unchanged, and sends only staged references to Adeli. Uploads use concurrency three by default; set `concurrency`, `onProgress`, `signal`, or `timeoutMs` in the method options. Presigned PUTs are idempotent and honor the client's bounded retry count; post publication itself is never retried automatically.
|
|
69
|
+
|
|
70
|
+
A file-backed Instagram `createPost` returns a `202` job response. Call `client.posts.getInstagramPostStatus(...)` directly or use bounded `waitForInstagramPost(...)`. Its terminal states are `published`, `failed`, and `outcome_unknown`; the last state is intentionally not retried because Meta may already have accepted the final publish write.
|
|
71
|
+
|
|
72
|
+
Use `client.posts.createMediaUploads(...)` directly when another process manages the PUTs. API keys are sent only to Adeli and never to signed S3 URLs.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SDK maintenance and release handoff
|
|
2
|
+
|
|
3
|
+
This repository is a standalone npm package. The pinned contract in `openapi/` is the only normal build input; builds do not fetch the live API document. `openapi/provenance.json` records whether a snapshot is a deployed public document or an unreleased working-tree candidate. A local candidate must never be described as deployed.
|
|
4
|
+
|
|
5
|
+
## Reciprocal API/SDK workflow
|
|
6
|
+
|
|
7
|
+
1. Change the API implementation and response/request contract together in the private core repository, including serialized-handler contract tests and public documentation.
|
|
8
|
+
2. Export a deterministic candidate OpenAPI document from that working tree. In the SDK checkout, use `npm run sync:openapi -- --file <export> --source-label adeli-core-working-tree`, inspect the diff, then run `npm run generate`, `npm run generate:api-reference`, and `npm run check:generated`. The last command checks both generated types and the API reference.
|
|
9
|
+
3. Update affected handwritten resources, serializers, helpers, package tests, examples, and the Changeset. `check:contract` must report missing operations or weak success schemas; do not replace it with a count or an always-green stub. It performs local structural checks only (including local references and compositions) and never fetches references. It is not a second semantic compatibility comparator: core's coordinated compatibility review and drift reporting remain required.
|
|
10
|
+
4. Run generation, contract, typecheck, lint, and tests on Node 24. Validate a real tarball with `npm run build`, `npm run check:package`, `npm run test:package`, and `npm run typecheck:examples`. Consumers must resolve only packed files, never monorepo aliases or source paths. The packed gate runs offline with lifecycle scripts disabled, publint, Are the Types Wrong (`--profile node16` against the existing tarball), ESM/CJS imports, and NodeNext/bundler TypeScript consumers.
|
|
11
|
+
5. Helpers and uploads must import shared runtime errors through the `@adeli/sdk` self-reference. `tsup` externalizes that self-reference so `splitting: false` cannot duplicate error classes; development typechecking resolves it through the explicit `tsconfig` path aliases. Do not add error-class re-exports to the helper public API.
|
|
12
|
+
6. When core's per-environment feature availability changes, update [`availability.md`](availability.md) in the same companion change. The OpenAPI contract does not carry availability, so no generated check catches this drift.
|
|
13
|
+
7. From the core checkout, validate SDK-tagged public MDX against that same tarball with `node scripts/check-sdk-doc-examples.mjs --sdk-package <tarball>`. The full coordinated gate is `yarn sdk:check --sdk-dir <sdk-checkout> --sdk-ref <full-reviewed-commit>` and requires a clean immutable candidate; read-only local preparation uses `yarn sdk:prepare --sdk-dir <sdk-checkout> --working-tree`.
|
|
14
|
+
8. Return the tested SDK revision, candidate contract hash, package version, tarball filename/hash, exact commands, blocked gates, and unresolved remote-write outcomes to the private change record. Do not copy private PR links, customer data, credentials, or internal implementation details into this repository.
|
|
15
|
+
|
|
16
|
+
The reverse handoff from the SDK to core includes the public method/type changes, compatibility classification, migration guidance, and the candidate provenance. A behavioral API change with no OpenAPI diff still requires review. A required-field, status, error, nullability, or union change requires explicit breaking-change handling.
|
|
17
|
+
|
|
18
|
+
## Compatibility and rollout
|
|
19
|
+
|
|
20
|
+
The supported runtime is Node.js 24. The package has zero runtime dependencies and exposes ESM and CommonJS entry points. Additive API changes deploy and are verified before a new SDK method is advertised. Breaking changes require a compatibility window or staged API migration; releasing a new SDK major alone does not protect existing API consumers. If publication is delayed, leave the private handoff open and keep existing clients supported.
|
|
21
|
+
|
|
22
|
+
Releases follow [`releasing.md`](releasing.md): the step-by-step checklist, how `release.yml` protects a release, and the `0.1.0` bootstrap history. The package is MIT-licensed.
|
|
23
|
+
|
|
24
|
+
The scheduled/manual drift workflow preserves raw SHA evidence but compares parsed canonical OpenAPI JSON. Formatting/key-order-only changes are reported without failing; meaningful canonical changes and malformed/non-OpenAPI responses fail with review evidence. Fetching is bounded and workflow-only; no drift update, publication, or auto-merge occurs.
|
|
25
|
+
|
|
26
|
+
## Evidence format
|
|
27
|
+
|
|
28
|
+
Record the exact SDK revision, package version, contract SHA-256, source label, commands and results, tarball filename/hash, known blocked gates, release owner, and follow-up owner. A successful local build is not release readiness, API deployment, npm publication, or runtime smoke evidence. Close the private handoff only after an authorized publication, API availability, public documentation/migration notes, and verified tag/version are confirmed.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Publishing and uploads
|
|
2
|
+
|
|
3
|
+
Publishing is server-side and provider-specific. Pass the real platform payload rather than assuming one universal post model. JSON Instagram image/carousel/Reel/story payloads retain `contentType`, base64 or supported URL media, captions, collaborators, user tags, and Reel feed options. Facebook text/image/video/Reel, TikTok JSON or multipart, YouTube multipart, X, and Bluesky have different fields and outcomes; preserve their response fields unchanged.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Adeli } from '@adeli/sdk';
|
|
7
|
+
import { fileFromPath } from '@adeli/sdk/uploads';
|
|
8
|
+
|
|
9
|
+
export async function uploadYouTube(input: { accountId: string; path: string }) {
|
|
10
|
+
const client = new Adeli({ apiKey: process.env.ADELI_API_KEY });
|
|
11
|
+
const response = await client.posts.uploadYouTubeVideo({ body: {
|
|
12
|
+
accountId: input.accountId,
|
|
13
|
+
video: await fileFromPath(input.path, { contentType: 'video/mp4' }),
|
|
14
|
+
title: 'Example', privacy_status: 'private', made_for_kids: false,
|
|
15
|
+
} });
|
|
16
|
+
return response; // 202 is accepted work, not proof of public visibility.
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use `Blob`/`File` values or the Node file-backed adapter. File-backed uploads avoid silently base64-encoding multi-GB media; supported URL media is sent to Adeli, not fetched by this SDK. Multipart field names, repeated `photos[]`, booleans, zero values, thumbnail, privacy, kids, scheduling, and retained filenames are serialized as documented by each operation. The current candidate API caps are: TikTok multipart accepts one video up to 1,000,000,000 bytes, or 1–35 photos with each photo at most 20 MiB; YouTube multipart accepts a video up to 4 GiB and an optional JPEG/PNG thumbnail up to 50 MiB, with the total multipart request capped at 4 GiB including fields and multipart overhead. JSON `POST /api/v1/posts` is capped at 96 MiB, and an inline base64 video must decode to at most 64 MiB. These are local candidate API caps, not a global provider limit or SDK-side preflight; provider restrictions and validation remain authoritative, and file plus thumbnail plus fields must fit the YouTube total request cap. See the public [TikTok publishing page](https://www.tryadeli.com/docs/api/posts/publish-tiktok), [YouTube file upload page](https://www.tryadeli.com/docs/api/posts/upload-youtube-video-file), and [YouTube upload guide](https://developers.google.com/youtube/v3/guides/implementation/videos) for the current candidate and provider behavior. The SDK does not expose dashboard-only Instagram/Facebook multipart routes.
|
|
21
|
+
|
|
22
|
+
A `201`/`200` result may be synchronous; `202` means accepted processing; it does not guarantee final provider visibility. File-backed Instagram requests use staged references and return a durable job: use `waitForInstagramPost` or `client.posts.getInstagramPostStatus`, and treat `outcome_unknown` as terminal without automatic resubmission. Use `@adeli/sdk/helpers` status helpers explicitly. TikTok `SEND_TO_USER_INBOX` is delivery to the creator inbox, not public publication. `PUBLISH_COMPLETE` is terminal even without public IDs. YouTube `published` means provider processing completed, not that a video is public: retain `privacyStatus`, `publishAt`, and permalink. Facebook video status `PROCESSING` is provider video processing and terminates as `READY` or `ERROR`; it is not the OAuth connection status `pending_page_selection`. A Facebook connection can separately require Page selection through the dashboard before publishing, and the SDK does not complete that handoff.
|
|
23
|
+
|
|
24
|
+
Thread and aggregate operations may return `207` with successful records and per-item/provider failures. Consume successful results and surface errors; never automatically retry a partial or ambiguous write. A local timeout or cancellation only ends the SDK request and leaves remote outcome unknown. Inspect the provider/API before any resubmission. X reads can cost money. Both `createAdAccount` and `createCampaign` require an idempotency key and neither auto-retries; profile creation's key is optional.
|
|
25
|
+
|
|
26
|
+
## Instagram DMs and private replies
|
|
27
|
+
|
|
28
|
+
`client.messages.sendInstagramMessage` sends an Instagram DM to a person (`recipient: { id }`, their Instagram-scoped id) or a private reply to a comment (`recipient: { commentId }`), with up to three optional `postback` or `url` buttons. `sendMessage` is unchanged and does not take buttons or comment recipients. The `201` result's `recipient` is the person's Instagram-scoped id, including for a private reply, so a later DM can use `recipient: { id }`. A comment's `authorId` from `listComments` is the same id.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const reply = await client.messages.sendInstagramMessage({ body: {
|
|
32
|
+
accountId,
|
|
33
|
+
recipient: { commentId },
|
|
34
|
+
text: 'Thanks for commenting! Want the link?',
|
|
35
|
+
buttons: [{ type: 'postback', title: 'Send it', payload: 'send_link' }],
|
|
36
|
+
} });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Instagram allows one private reply per comment, within 7 days of the comment, and only for accounts connected with Instagram Login. A second reply to the same comment fails with `409 private_reply_already_sent`. After a private reply, send a follow-up DM only once the person responds, for example by tapping a postback button; otherwise the send fails with `409 window_closed`. An account connected with Facebook Login returns `400 unsupported_for_platform` for a private reply. Like every write, the SDK never retries a send automatically.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Releasing `@adeli/sdk`
|
|
2
|
+
|
|
3
|
+
Releasing is one click: **Release SDK** bumps the version, runs every check, tags the release and publishes it to npm.
|
|
4
|
+
|
|
5
|
+
> **Never run `npm publish` locally or with a token.** Every release after `0.1.0` goes through Release SDK, which publishes with npm trusted publishing (OIDC) and provenance.
|
|
6
|
+
|
|
7
|
+
## Before you release: add changesets
|
|
8
|
+
|
|
9
|
+
Every PR that should appear in the release adds a changeset:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx changeset
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Pick `patch`, `minor` or `major` and write one line for the changelog. Commit the generated `.changeset/*.md` file with the PR.
|
|
16
|
+
|
|
17
|
+
## Release
|
|
18
|
+
|
|
19
|
+
**Actions → Release SDK → Run workflow** (from `main`), or:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
gh workflow run release.yml --ref main
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That's it. The run has three jobs, one after another:
|
|
26
|
+
|
|
27
|
+
1. **Bump, check and tag.** It turns the pending changesets into the next version (`package.json`, `CHANGELOG.md`, lockfile) and runs every check: generation drift, contract, lint, typecheck, tests, audit, build and package checks. **Only if all of them pass** does it commit **chore(release): X.Y.Z** to `main` and tag it `vX.Y.Z`, in one atomic push. If anything fails, nothing is pushed. It also stops if there are no changesets, or if the version is already tagged or on npm.
|
|
28
|
+
2. **Build tarball from the tag.** It checks out `vX.Y.Z`, verifies it is exactly the version bump on top of the commit the run started from (only `package.json`, `package-lock.json`, `CHANGELOG.md` and consumed changesets may change), and packs the tarball.
|
|
29
|
+
3. **Publish to npm.** It rechecks the tarball and publishes it with provenance.
|
|
30
|
+
|
|
31
|
+
npm's provenance names the commit the run started from, the one just before the version bump. Job 2's check guarantees the published code is that commit's code plus only the version bump.
|
|
32
|
+
|
|
33
|
+
Then confirm it's live (the registry can take a few minutes):
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm view @adeli/sdk@X.Y.Z version
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## If something fails
|
|
40
|
+
|
|
41
|
+
**Bump, check and tag failed:** nothing was pushed. Fix the problem on `main` with a normal PR and run Release SDK again.
|
|
42
|
+
|
|
43
|
+
**Build or Publish failed:** the tag exists but nothing is on npm yet.
|
|
44
|
+
|
|
45
|
+
- If the failure was flaky, or a setting outside the repo needed fixing, use **Re-run failed jobs** on that run. It reuses the same tag.
|
|
46
|
+
- If the code needs a fix, undo the release so the next run starts clean, **only if nothing was published**:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
git push origin :refs/tags/vX.Y.Z # delete the unpublished tag
|
|
50
|
+
git revert <the chore(release): X.Y.Z commit> # restores the changesets and the old version; merge via PR
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Merge the fix and the revert, then run Release SDK again.
|
|
54
|
+
|
|
55
|
+
**Never move a tag or reuse a version number once it has been published.** Release a new patch version instead.
|
|
56
|
+
|
|
57
|
+
## Doing it by hand
|
|
58
|
+
|
|
59
|
+
Don't publish by hand: every release goes through this workflow. If Release SDK is broken, fix it with a PR and run it again.
|
|
60
|
+
|
|
61
|
+
## How the workflow protects a release
|
|
62
|
+
|
|
63
|
+
- **Checked before it's tagged.** The first job runs the full gate before anything reaches `main`, and refuses versions that are already tagged or on npm.
|
|
64
|
+
- **The tag is only the version bump.** The build and publish jobs check that the tag is the commit directly on top of the run's starting commit, that it matches `package.json`, and that it changes nothing but release files.
|
|
65
|
+
- **Validate, then publish the same bytes.** The build job packs the tarball and records its SHA-256 and identity. `publish` re-checks that identity and publishes only that tarball. It never rebuilds.
|
|
66
|
+
- **Who can release.** Anyone who can run workflows (write access) can publish. The `npm-release` environment has no reviewers; it only allows `main`, and npm's trusted publisher is bound to it.
|
|
67
|
+
- **Pinned toolchain.** Every job runs Node 24.22.1, whose bundled npm 11.19.1 meets the trusted-publishing minimum. The workflow checks that exact npm version instead of upgrading npm.
|
|
68
|
+
|
|
69
|
+
External prerequisites (configured outside this repo): the `npm-release` environment limited to `main`, `@adeli` scope ownership on npm, and the npm trusted publisher (`kingscrosslabs/adeli-sdk`, workflow `release.yml`, environment `npm-release`) with token publishing disabled. The first job pushes directly to `main`; if `main` gets a protection ruleset, add GitHub Actions as a bypass actor.
|
|
70
|
+
|
|
71
|
+
## History: the `0.1.0` bootstrap
|
|
72
|
+
|
|
73
|
+
npm trusted publishing only works for a package that already exists on the registry. So `0.1.0` was published once by hand: the workflow (then started manually with version inputs) ran on `v0.1.0`, its `publish` job was rejected at what was then an approval gate on `npm-release`, and the validated CI tarball was downloaded, checked against `artifact.sha256`, and published byte for byte with `npm publish --access public` from an interactive 2FA login. This was a one-time step and is not repeatable.
|