@tabsircg/fb-sdk 1.0.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.
Files changed (58) hide show
  1. package/README.md +384 -0
  2. package/dist/client.js +20 -0
  3. package/dist/client.js.map +1 -0
  4. package/dist/httpClient.js +39 -0
  5. package/dist/httpClient.js.map +1 -0
  6. package/dist/internal/error.js +9 -0
  7. package/dist/internal/error.js.map +1 -0
  8. package/dist/internal/fetchers.js +56 -0
  9. package/dist/internal/fetchers.js.map +1 -0
  10. package/dist/internal/poller.js +55 -0
  11. package/dist/internal/poller.js.map +1 -0
  12. package/dist/internal/utils.js +25 -0
  13. package/dist/internal/utils.js.map +1 -0
  14. package/dist/lib/edge.js +33 -0
  15. package/dist/lib/edge.js.map +1 -0
  16. package/dist/lib/transformCase.js +39 -0
  17. package/dist/lib/transformCase.js.map +1 -0
  18. package/dist/resources/CommentResource.js +65 -0
  19. package/dist/resources/CommentResource.js.map +1 -0
  20. package/dist/resources/InsightResource.js +43 -0
  21. package/dist/resources/InsightResource.js.map +1 -0
  22. package/dist/resources/PageResource.js +123 -0
  23. package/dist/resources/PageResource.js.map +1 -0
  24. package/dist/resources/PostResource.js +26 -0
  25. package/dist/resources/PostResource.js.map +1 -0
  26. package/dist/resources/UserResource.js +18 -0
  27. package/dist/resources/UserResource.js.map +1 -0
  28. package/dist/resources/comment/CommentResource.js +58 -0
  29. package/dist/resources/comment/CommentResource.js.map +1 -0
  30. package/dist/resources/comment/PageCommentResouorce.js +79 -0
  31. package/dist/resources/comment/PageCommentResouorce.js.map +1 -0
  32. package/dist/store/memory.js +38 -0
  33. package/dist/store/memory.js.map +1 -0
  34. package/dist/store/redis.js +16 -0
  35. package/dist/store/redis.js.map +1 -0
  36. package/dist/store/types.js +2 -0
  37. package/dist/store/types.js.map +1 -0
  38. package/dist/tests/index.js +23 -0
  39. package/dist/tests/index.js.map +1 -0
  40. package/dist/types/facebookinsights.js +2 -0
  41. package/dist/types/facebookinsights.js.map +1 -0
  42. package/dist/types/facebookmedia.js +14 -0
  43. package/dist/types/facebookmedia.js.map +1 -0
  44. package/dist/types/facebookpage.js +2 -0
  45. package/dist/types/facebookpage.js.map +1 -0
  46. package/dist/types/facebookpost.js +2 -0
  47. package/dist/types/facebookpost.js.map +1 -0
  48. package/dist/types/facebookuser.js +2 -0
  49. package/dist/types/facebookuser.js.map +1 -0
  50. package/dist/types/shared.js +6 -0
  51. package/dist/types/shared.js.map +1 -0
  52. package/dist/types/webhook.js +2 -0
  53. package/dist/types/webhook.js.map +1 -0
  54. package/dist/utils.js +25 -0
  55. package/dist/utils.js.map +1 -0
  56. package/dist/webhook/handler.js +49 -0
  57. package/dist/webhook/handler.js.map +1 -0
  58. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,384 @@
1
+ # fb-sdk
2
+
3
+ A typed Node.js SDK for the Facebook Graph API (v25.0). Select only the fields you need, get full autocomplete while writing the selector, strict compile-time validation that rejects unknown fields, and a return type narrowed to exactly what you selected — all without runtime overhead.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install fb-sdk
9
+ ```
10
+
11
+ **Requirements:** Node.js 18+, TypeScript 5.9+
12
+
13
+ **Dependencies:** `axios`, `form-data`
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { fbGraph } from "fb-sdk";
19
+
20
+ const fb = fbGraph("your-access-token");
21
+
22
+ // Get a post — only the fields you select exist on the result
23
+ const post = await fb.post("postId").get({
24
+ id: true,
25
+ message: true,
26
+ comments: {
27
+ options: { filter: "toplevel" },
28
+ fields: { id: true, message: true },
29
+ },
30
+ });
31
+
32
+ post.id; // string
33
+ post.message; // string | undefined
34
+ post.comments.data[0].message; // string
35
+ post.comments.paging; // Paging
36
+ // post.fullPicture — compile error, not selected
37
+ ```
38
+
39
+ ## Field Selection
40
+
41
+ Every `get` and `list` method accepts a **field selector** — an object whose shape mirrors the resource type. You pick fields by setting them to `true`. The SDK converts this object into the Graph API `fields` query parameter and infers a return type containing only the selected fields.
42
+
43
+ ### Scalar fields
44
+
45
+ ```typescript
46
+ { id: true, message: true }
47
+ // → fields=id,message
48
+ ```
49
+
50
+ ### Nested objects
51
+
52
+ For non-collection nested objects, nest the selector directly:
53
+
54
+ ```typescript
55
+ { picture: { data: { url: true, height: true } } }
56
+ // → fields=picture{data{url,height}}
57
+ ```
58
+
59
+ You can also pass `true` to select all fields of a nested object:
60
+
61
+ ```typescript
62
+ {
63
+ picture: true;
64
+ }
65
+ // → fields=picture
66
+ ```
67
+
68
+ ### Edges (collections)
69
+
70
+ Edges — like `comments` on a post — are paginated collections. They require the `{ fields }` wrapper:
71
+
72
+ ```typescript
73
+ {
74
+ comments: {
75
+ fields: { id: true, message: true }
76
+ }
77
+ }
78
+ // → fields=comments{id,message}
79
+ ```
80
+
81
+ Passing `true` selects all fields on the edge's items:
82
+
83
+ ```typescript
84
+ {
85
+ comments: true;
86
+ }
87
+ // → fields=comments
88
+ ```
89
+
90
+ ### Edge options
91
+
92
+ Some edges accept extra parameters (limit, pagination cursors, filters). These go in `options`:
93
+
94
+ ```typescript
95
+ {
96
+ comments: {
97
+ options: { filter: "toplevel", limit: 10 },
98
+ fields: { id: true, message: true }
99
+ }
100
+ }
101
+ // → fields=comments.filter(toplevel).limit(10){id,message}
102
+ ```
103
+
104
+ Available options vary per edge. The `comments` edge supports `filter` and `summary` on top of the base `limit`, `after`, and `before`. The SDK infers the correct options type for each edge — you get autocomplete for what's available.
105
+
106
+ ### Return type narrowing
107
+
108
+ The return type contains **only** what you selected. If you select `{ id: true, message: true }` on a `FacebookPost`, the result type is `{ id: string; message: string | undefined }` — not the full `FacebookPost`. This applies recursively to nested objects and edges.
109
+
110
+ When an edge has option-dependent response fields (e.g., `comments` includes a `summary` object), those fields appear in the return type only when `options` is provided in the selector.
111
+
112
+ ## Available Resources
113
+
114
+ ### `fb.post(postId)`
115
+
116
+ Operations on a specific post by ID.
117
+
118
+ #### `fb.post(postId).get(fields)`
119
+
120
+ Fetch a single post.
121
+
122
+ ```typescript
123
+ const post = await fb.post("postId").get({
124
+ id: true,
125
+ statusType: true,
126
+ createdTime: true,
127
+ shares: true,
128
+ reactions: true,
129
+ });
130
+ ```
131
+
132
+ #### `fb.post(postId).expire(time, type)`
133
+
134
+ Set an expiration on a post.
135
+
136
+ ```typescript
137
+ await fb.post("postId").expire(Date.now() + 86400000, "expire_only");
138
+ ```
139
+
140
+ #### `fb.post(postId).comments.list(fields)`
141
+
142
+ Fetch comments on a post. Also provides a `.create(data)` method to add a comment.
143
+
144
+ ```typescript
145
+ const comments = await fb.post("postId").comments.list({
146
+ id: true,
147
+ message: true,
148
+ from: { name: true },
149
+ });
150
+ // comments.data — Comment[]
151
+ // comments.paging — Paging
152
+
153
+ // Create a comment
154
+ const newComment = await fb.post("postId").comments.create({
155
+ message: "Hello world!"
156
+ });
157
+ ```
158
+
159
+ ---
160
+
161
+ ### `fb.me`
162
+
163
+ Operations on the authenticated user.
164
+
165
+ #### `fb.me.get(fields)`
166
+
167
+ Fetch the current user's profile.
168
+
169
+ ```typescript
170
+ const me = await fb.me.get({ id: true, name: true });
171
+ ```
172
+
173
+ #### `fb.me.accounts(fields)`
174
+
175
+ List Facebook Pages the user manages.
176
+
177
+ ```typescript
178
+ const pages = await fb.me.accounts({
179
+ id: true,
180
+ name: true,
181
+ accessToken: true,
182
+ });
183
+ // pages.data — FacebookPage[]
184
+ // pages.paging — Paging
185
+ ```
186
+
187
+ ---
188
+
189
+ ### `fb.page(pageId)`
190
+
191
+ Operations scoped to a specific Page. Returns sub-resources for posts, videos, reels, images, and aggregated comments.
192
+
193
+ ```typescript
194
+ const page = fb.page("pageId");
195
+ ```
196
+
197
+ #### `page.posts.list(query)`
198
+
199
+ List posts on a Page. Note: to fetch a specific post, use `fb.post(postId).get()`.
200
+
201
+ ```typescript
202
+ const feed = await page.posts.list({
203
+ fields: { id: true, message: true, createdTime: true },
204
+ });
205
+ ```
206
+
207
+ #### `page.comments.list(query, config)`
208
+
209
+ Fetch an aggregated stream of comments across recent page posts.
210
+
211
+ ```typescript
212
+ const allComments = await page.comments.list({
213
+ fields: { id: true, message: true, createdTime: true }
214
+ });
215
+ ```
216
+
217
+ #### `page.videos.list(query)` / `page.videos.publish(data)`
218
+
219
+ List videos or publish a new video.
220
+
221
+ ```typescript
222
+ const videos = await page.videos.list({
223
+ fields: { id: true, title: true, status: true },
224
+ });
225
+
226
+ const { postId } = await page.videos.publish({
227
+ fileUrl: "https://example.com/video.mp4",
228
+ title: "My Video",
229
+ description: "A description",
230
+ thumbnailUrl: "https://example.com/thumb.jpg",
231
+ });
232
+ ```
233
+
234
+ Publishing handles the upload, waits for processing via polling, and returns the resulting post ID. Throws `FacebookUploadError` on failure.
235
+
236
+ #### `page.reels.list(query)` / `page.reels.publish(data)`
237
+
238
+ List or publish reels. Publishing uses Facebook's resumable upload protocol (start session → upload file → finish session) and polls until the reel is processed.
239
+
240
+ ```typescript
241
+ const { postId } = await page.reels.publish({
242
+ fileUrl: "https://example.com/reel.mp4",
243
+ title: "My Reel",
244
+ thumbnailUrl: "https://example.com/thumb.jpg",
245
+ });
246
+ ```
247
+
248
+ #### `page.images.list(query)` / `page.images.publish(data)`
249
+
250
+ List or publish images.
251
+
252
+ ```typescript
253
+ const { postId } = await page.images.publish({
254
+ url: "https://example.com/image.jpg",
255
+ caption: "My image",
256
+ });
257
+ ```
258
+
259
+ ---
260
+
261
+ ### `fb.comment(commentId)`
262
+
263
+ Operations to manage a single comment node.
264
+
265
+ ```typescript
266
+ const myComment = fb.comment("commentId");
267
+
268
+ // Update message
269
+ await myComment.update({ message: "new message" });
270
+
271
+ // Like / unlike
272
+ await myComment.like();
273
+ await myComment.unlike();
274
+
275
+ // Delete
276
+ await myComment.delete();
277
+
278
+ // Get nested replies
279
+ const subComments = await myComment.replies.list({ id: true, message: true });
280
+ ```
281
+
282
+ ## Adding New Endpoints
283
+
284
+ To add a new resource or edge, follow this pattern:
285
+
286
+ ### 1. Define the raw type
287
+
288
+ Create or update a file in `src/types/`. Define the raw interface with snake_case keys matching the Facebook API response:
289
+
290
+ ```typescript
291
+ // src/types/facebookstory.ts
292
+ import { KeysToCamel } from "../lib/transformCase.js";
293
+
294
+ interface FacebookStoryRaw {
295
+ id: string;
296
+ created_time: string;
297
+ media_url: string;
298
+ }
299
+ export type FacebookStory = KeysToCamel<FacebookStoryRaw>;
300
+ ```
301
+
302
+ ### 2. Define edge options (if needed)
303
+
304
+ If the edge supports custom parameters beyond the base `limit`/`after`/`before`, extend `EdgeOptions` in `src/types/shared.ts`:
305
+
306
+ ```typescript
307
+ export interface StoryEdgeOptions extends EdgeOptions {
308
+ since?: number;
309
+ }
310
+ ```
311
+
312
+ ### 3. Use `CollectionOf` for edges with option-dependent fields
313
+
314
+ If the edge's response includes extra fields only when certain options are passed, use `CollectionOf` with an intersection:
315
+
316
+ ```typescript
317
+ interface ParentRaw {
318
+ stories: CollectionOf<FacebookStoryRaw, StoryEdgeOptions> & {
319
+ extra_field: string; // only present when options are provided
320
+ };
321
+ }
322
+ ```
323
+
324
+ ### 4. Create the resource
325
+
326
+ In `src/resources/`, create a factory function using `GetNode` and `ListEdge` types:
327
+
328
+ ```typescript
329
+ import { GetNode, ListEdge } from "../types/shared.js";
330
+ import { FacebookStory } from "../types/facebookstory.js";
331
+
332
+ export type ListStories = ListEdge<FacebookStory>;
333
+ export type GetStory = GetNode<FacebookStory>;
334
+
335
+ export const createStoryResource = (http: HttpClient, pageId: string) => {
336
+ const list: ListStories = async (query) =>
337
+ http.get(`/${pageId}/stories`, {
338
+ params: { fields: toGraphFields(query.fields), ...query.options },
339
+ });
340
+
341
+ const get: GetStory = async (fields) =>
342
+ http.get(`/${pageId}`, {
343
+ params: { fields: toGraphFields(fields) },
344
+ });
345
+
346
+ return { list, get };
347
+ };
348
+ ```
349
+
350
+ ### 5. Wire into the SDK
351
+
352
+ Add the resource to the appropriate factory in `src/client.ts`:
353
+
354
+ ```typescript
355
+ export function fbGraph(accessToken: string) {
356
+ const http = createHttpClient(accessToken);
357
+ return {
358
+ post: (postId: string) => createPostResource(http, postId),
359
+ page: (pageId: string) => ({
360
+ ...createPageResource(http, pageId),
361
+ stories: createStoryResource(http, pageId),
362
+ }),
363
+ me: createUserResource(http),
364
+ };
365
+ }
366
+ ```
367
+
368
+ ## Type System Overview
369
+
370
+ The SDK's type system follows a three-stage pipeline:
371
+
372
+ ### 1. Selector generation (`FbFieldSelector<T>`)
373
+
374
+ Given a resource type `T`, `FbFieldSelector<T>` generates the shape of a valid field selector object. Scalar fields become `true | undefined`. Nested objects become recursive selectors or `true`. Collection edges become `{ options?: O; fields: FbFieldSelector<Inner> } | true`, where `O` is the edge-specific options type extracted from the `CollectionOf` phantom type. Recursion depth is bounded by a `Decrement` counter (default depth: 1 level of nesting).
375
+
376
+ ### 2. Strict validation (`DeepStrict<Valid, Inferred>`)
377
+
378
+ TypeScript's structural type system doesn't reject excess properties when generics are involved. `DeepStrict` solves this by mapping every key in the inferred selector — if the key exists in the valid selector shape, it passes through; otherwise it becomes `never`, causing a compile error. This gives you red squiggles on typos and invalid fields.
379
+
380
+ ### 3. Return type narrowing (`FbPickDeep<T, F>`)
381
+
382
+ `FbPickDeep<T, F>` walks the resource type `T` in parallel with your selector `F` and keeps only the fields you selected. For collections, it wraps the picked inner type in `{ data: Picked[]; paging: Paging }`. If the selector included `options`, option-dependent fields (defined via `& { ... }` on `CollectionOf`) are also included via `CleanCollection`.
383
+
384
+ The result is end-to-end type safety: autocomplete guides you to valid fields, the compiler rejects invalid ones, and the return type contains exactly what you asked for.
package/dist/client.js ADDED
@@ -0,0 +1,20 @@
1
+ import { createHttpClient } from "./httpClient.js";
2
+ import { createPostResource } from "./resources/PostResource.js";
3
+ import { createPageResource } from "./resources/PageResource.js";
4
+ import { createUserResource } from "./resources/UserResource.js";
5
+ import { createCommentResource } from "./resources/comment/CommentResource.js";
6
+ export function createFbSdk(config = {}) {
7
+ return (accessToken) => {
8
+ const http = createHttpClient(accessToken);
9
+ return {
10
+ post: (postId) => createPostResource({ http, id: postId, config }),
11
+ page: (pageId) => createPageResource({ http, id: pageId, config }),
12
+ comment: (commentId) => createCommentResource({ http, id: commentId, config }),
13
+ me: createUserResource({ http, config, id: "me" }),
14
+ };
15
+ };
16
+ }
17
+ export { createMemoryStore } from "./store/memory.js";
18
+ export { createRedisStore } from "./store/redis.js";
19
+ export { createWebhookHandler } from "./webhook/handler.js";
20
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAc,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAa/E,MAAM,UAAU,WAAW,CAAC,SAAsB,EAAE;IAClD,OAAO,CAAC,WAAmB,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC3C,OAAO;YACL,IAAI,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAC1E,IAAI,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAC1E,OAAO,EAAE,CAAC,SAAiB,EAAE,EAAE,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;YACtF,EAAE,EAAE,kBAAkB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;SACnD,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import axios from "axios";
2
+ import { toCamel, toSnakeObj } from "./lib/transformCase.js";
3
+ import FormData from "form-data";
4
+ export const api = axios.create({ family: 4 });
5
+ const fbApi = axios.create({
6
+ baseURL: "https://graph.facebook.com/v25.0",
7
+ family: 4,
8
+ headers: { "Accept-Encoding": "gzip, deflate, br" },
9
+ });
10
+ export function createHttpClient(accessToken) {
11
+ return {
12
+ get: async (path, options) => {
13
+ const res = await fbApi.get(path, {
14
+ params: { access_token: accessToken, ...options?.params },
15
+ });
16
+ const data = toCamel(res.data);
17
+ return options?.safe ? { data, status: res.status } : data;
18
+ },
19
+ post: async (path, data, options) => {
20
+ const isForm = data instanceof FormData;
21
+ const res = await fbApi.post(path, isForm ? data : toSnakeObj(data), {
22
+ headers: isForm ? data.getHeaders() : {},
23
+ ...(options?.safe && { validateStatus: (s) => s === 200 || s === 504 }),
24
+ params: { access_token: accessToken },
25
+ });
26
+ const body = toCamel(res.data);
27
+ return options?.safe ? { data: body, status: res.status } : body;
28
+ },
29
+ delete: async (path, options) => {
30
+ const res = await fbApi.delete(path, {
31
+ params: { access_token: accessToken, ...options?.params },
32
+ });
33
+ const data = toCamel(res.data);
34
+ return options?.safe ? { data, status: res.status } : data;
35
+ },
36
+ getToken: () => accessToken,
37
+ };
38
+ }
39
+ //# sourceMappingURL=httpClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpClient.js","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,QAAQ,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;AAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;IACzB,OAAO,EAAE,kCAAkC;IAC3C,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,EAAE,iBAAiB,EAAE,mBAAmB,EAAE;CACpD,CAAC,CAAC;AAwBH,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAC3B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;gBAChC,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE;aAC1D,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7D,CAAC;QAED,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAClC,MAAM,MAAM,GAAG,IAAI,YAAY,QAAQ,CAAC;YACxC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;gBACnE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE;gBACxC,GAAG,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,cAAc,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC/E,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE;aACtC,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAE/B,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAC9B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;gBACnC,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE;aAC1D,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7D,CAAC;QAED,QAAQ,EAAE,GAAG,EAAE,CAAC,WAAW;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export class FacebookUploadError extends Error {
2
+ status;
3
+ constructor(message, status) {
4
+ super(message);
5
+ this.status = status;
6
+ this.name = "FacebookUploadError";
7
+ }
8
+ }
9
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/internal/error.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAG1B;IAFlB,YACE,OAAe,EACC,MAAgC;QAEhD,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,WAAM,GAAN,MAAM,CAA0B;QAGhD,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,56 @@
1
+ import FormData from "form-data";
2
+ import { ORDER, } from "../types/shared.js";
3
+ export const fetchComments = async (http, { postIds, fieldsStr, options, cursors }) => {
4
+ const allComments = [];
5
+ const nextCursors = {};
6
+ const remaining = [];
7
+ const chunks = [];
8
+ for (let i = 0; i < postIds.length; i += 50) {
9
+ chunks.push(postIds.slice(i, i + 50));
10
+ }
11
+ for (const chunk of chunks) {
12
+ const batch = chunk.map((postId) => {
13
+ const params = new URLSearchParams();
14
+ params.set("fields", fieldsStr);
15
+ params.set("order", options?.order || ORDER.NEWEST);
16
+ params.set("limit", "5");
17
+ if (options?.filter)
18
+ params.set("filter", options.filter);
19
+ if (options?.summary !== undefined)
20
+ params.set("summary", String(options.summary));
21
+ if (options?.since)
22
+ params.set("since", String(options.since));
23
+ if (options?.until)
24
+ params.set("until", String(options.until));
25
+ const cursor = cursors[postId];
26
+ if (cursor)
27
+ params.set("after", cursor);
28
+ console.log(params.toString());
29
+ return {
30
+ method: "GET",
31
+ relative_url: `${postId}/comments?${params.toString()}`,
32
+ };
33
+ });
34
+ const form = new FormData();
35
+ form.append("batch", JSON.stringify(batch));
36
+ form.append("include_headers", "false");
37
+ const responses = await http.post("/", form);
38
+ const responseArray = Array.isArray(responses) ? responses : [];
39
+ for (let i = 0; i < responseArray.length; i++) {
40
+ const resp = responseArray[i];
41
+ const postId = chunk[i];
42
+ if (!resp || resp.code !== 200)
43
+ continue;
44
+ const parsed = JSON.parse(resp.body);
45
+ for (const comment of parsed.data) {
46
+ allComments.push({ ...comment, _postId: postId });
47
+ }
48
+ if (parsed.paging?.next && parsed.paging.cursors?.after) {
49
+ nextCursors[postId] = parsed.paging.cursors.after;
50
+ remaining.push(postId);
51
+ }
52
+ }
53
+ }
54
+ return { comments: allComments, nextCursors, remaining };
55
+ };
56
+ //# sourceMappingURL=fetchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchers.js","sourceRoot":"","sources":["../../src/internal/fetchers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { FacebookUploadError } from "./error.js";
2
+ const getProcessingError = (status) => {
3
+ if (status.videoStatus === "error")
4
+ return "Video upload failed";
5
+ const phase = status.uploadingPhase?.status === "error"
6
+ ? status.uploadingPhase
7
+ : status.processingPhase?.status === "error"
8
+ ? status.processingPhase
9
+ : status.publishingPhase?.status === "error"
10
+ ? status.publishingPhase
11
+ : null;
12
+ return phase?.errors?.[0]?.message;
13
+ };
14
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
15
+ export function poll(fn, config = {}) {
16
+ const { maxAttempts = 30, intervalMs = 10000 } = config;
17
+ return async (...args) => {
18
+ for (let i = 0; i < maxAttempts; i++) {
19
+ const result = await fn(...args);
20
+ if (result !== undefined)
21
+ return result;
22
+ await sleep(intervalMs);
23
+ }
24
+ throw new Error(`Polling timed out after ${maxAttempts} attempts (${(maxAttempts * intervalMs) / 60000} min)`);
25
+ };
26
+ }
27
+ export const pollVideoStatus = poll(async (listVideos, trackingId) => {
28
+ const videos = await listVideos({
29
+ fields: {
30
+ status: true,
31
+ postId: true,
32
+ universalVideoId: true,
33
+ },
34
+ });
35
+ const target = videos.data.find((v) => v.universalVideoId === trackingId);
36
+ if (!target)
37
+ return undefined;
38
+ const error = getProcessingError(target.status);
39
+ if (error)
40
+ throw new FacebookUploadError(error, target.status);
41
+ if (target.status.publishingPhase?.status === "complete") {
42
+ return { postId: target.postId };
43
+ }
44
+ return undefined;
45
+ }, { maxAttempts: 30, intervalMs: 20000 });
46
+ export const pollReelStatus = poll(async (getReel) => {
47
+ const { postId, status } = await getReel({ postId: true, status: true });
48
+ const error = getProcessingError(status);
49
+ if (error)
50
+ throw new FacebookUploadError(error, status);
51
+ if (postId)
52
+ return { postId };
53
+ return undefined;
54
+ }, { maxAttempts: 30, intervalMs: 10000 });
55
+ //# sourceMappingURL=poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poller.js","sourceRoot":"","sources":["../../src/internal/poller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { toSnakeCase } from "../lib/transformCase.js";
2
+ function serializeEdgeOptions(options) {
3
+ if (!options)
4
+ return "";
5
+ return Object.entries(options)
6
+ .filter(([, v]) => v !== undefined)
7
+ .map(([k, v]) => `.${toSnakeCase(k)}(${v})`)
8
+ .join("");
9
+ }
10
+ export function toGraphFields(fields) {
11
+ return Object.entries(fields)
12
+ .filter(([, value]) => value !== undefined && value !== false)
13
+ .map(([key, value]) => {
14
+ const snakeKey = toSnakeCase(key);
15
+ if (value === true)
16
+ return snakeKey;
17
+ if (value.fields) {
18
+ const opts = serializeEdgeOptions(value.options);
19
+ return `${snakeKey}${opts}{${toGraphFields(value.fields)}}`;
20
+ }
21
+ return `${snakeKey}{${toGraphFields(value)}}`;
22
+ })
23
+ .join(",");
24
+ }
25
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,SAAS,oBAAoB,CAAC,OAAiC;IAC7D,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;SAC3B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAA2B;IACvD,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,CAAC;SAC7D,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACpB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,QAAQ,CAAC;QAGpC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACjD,OAAO,GAAG,QAAQ,GAAG,IAAI,IAAI,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;QAC9D,CAAC;QAGD,OAAO,GAAG,QAAQ,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC;IAChD,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { toGraphFields } from "../utils.js";
2
+ function cleanParams(obj) {
3
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
4
+ }
5
+ export function listEdge(http, path, defaults) {
6
+ return ((fields, options) => {
7
+ return http.get(path, {
8
+ params: cleanParams({ fields: toGraphFields(fields), ...defaults, ...options }),
9
+ });
10
+ });
11
+ }
12
+ export function listByParentEdge(http, suffix, defaults) {
13
+ return ((parentId, fields, options) => {
14
+ return http.get(`/${parentId}${suffix}`, {
15
+ params: cleanParams({ fields: toGraphFields(fields), ...defaults, ...options }),
16
+ });
17
+ });
18
+ }
19
+ export function getEdge(http) {
20
+ return ((id, fields) => {
21
+ return http.get(`/${id}`, {
22
+ params: { fields: toGraphFields(fields) },
23
+ });
24
+ });
25
+ }
26
+ export function getFixedEdge(http, path) {
27
+ return ((fields) => {
28
+ return http.get(path, {
29
+ params: { fields: toGraphFields(fields) },
30
+ });
31
+ });
32
+ }
33
+ //# sourceMappingURL=edge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge.js","sourceRoot":"","sources":["../../src/lib/edge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgD5C,SAAS,WAAW,CAAC,GAA4B;IAC/C,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;AACpF,CAAC;AAKD,MAAM,UAAU,QAAQ,CACtB,IAAgB,EAChB,IAAY,EACZ,QAA0B;IAE1B,OAAO,CAAC,CAAC,MAA+B,EAAE,OAAgB,EAAE,EAAE;QAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YACpB,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;SAChF,CAAC,CAAC;IACL,CAAC,CAAmC,CAAC;AACvC,CAAC;AAGD,MAAM,UAAU,gBAAgB,CAC9B,IAAgB,EAChB,MAAc,EACd,QAA0B;IAE1B,OAAO,CAAC,CAAC,QAAgB,EAAE,MAA+B,EAAE,OAAgB,EAAE,EAAE;QAC9E,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,GAAG,MAAM,EAAE,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;SAChF,CAAC,CAAC;IACL,CAAC,CAA2C,CAAC;AAC/C,CAAC;AAGD,MAAM,UAAU,OAAO,CAAgC,IAAgB;IACrE,OAAO,CAAC,CAAC,EAAU,EAAE,MAA+B,EAAE,EAAE;QACtD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE;YACxB,MAAM,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE;SAC1C,CAAC,CAAC;IACL,CAAC,CAA0B,CAAC;AAC9B,CAAC;AAGD,MAAM,UAAU,YAAY,CAC1B,IAAgB,EAChB,IAAY;IAEZ,OAAO,CAAC,CAAC,MAA+B,EAAE,EAAE;QAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YACpB,MAAM,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE;SAC1C,CAAC,CAAC;IACL,CAAC,CAA+B,CAAC;AACnC,CAAC"}