naystack 1.8.18 → 1.9.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 (122) hide show
  1. package/README.md +94 -34
  2. package/dist/auth/client.cjs.js +2 -2
  3. package/dist/auth/client.esm.js +2 -2
  4. package/dist/auth/email/client.cjs.js +2 -2
  5. package/dist/auth/email/client.esm.js +2 -2
  6. package/dist/auth/email/index.cjs.js +2 -2
  7. package/dist/auth/email/index.esm.js +2 -2
  8. package/dist/auth/email/next.cjs.js +2 -2
  9. package/dist/auth/email/next.esm.js +2 -2
  10. package/dist/auth/email/routes/delete.cjs.js +2 -2
  11. package/dist/auth/email/routes/delete.esm.js +2 -2
  12. package/dist/auth/email/routes/get.cjs.js +2 -2
  13. package/dist/auth/email/routes/get.esm.js +2 -2
  14. package/dist/auth/email/routes/post.cjs.js +2 -2
  15. package/dist/auth/email/routes/post.esm.js +2 -2
  16. package/dist/auth/email/routes/put.cjs.js +2 -2
  17. package/dist/auth/email/routes/put.esm.js +2 -2
  18. package/dist/auth/email/token.cjs.js +2 -2
  19. package/dist/auth/email/token.esm.js +2 -2
  20. package/dist/auth/email/utils.cjs.js +2 -2
  21. package/dist/auth/email/utils.esm.js +2 -2
  22. package/dist/auth/google/get.cjs.js +2 -2
  23. package/dist/auth/google/get.esm.js +2 -2
  24. package/dist/auth/google/index.cjs.js +2 -2
  25. package/dist/auth/google/index.esm.js +2 -2
  26. package/dist/auth/index.cjs.js +4 -297
  27. package/dist/auth/index.d.mts +0 -5
  28. package/dist/auth/index.d.ts +0 -5
  29. package/dist/auth/index.esm.js +3 -293
  30. package/dist/auth/token-store.cjs.js +2 -2
  31. package/dist/auth/token-store.esm.js +2 -2
  32. package/dist/env.cjs.js +3 -3
  33. package/dist/env.d.mts +3 -2
  34. package/dist/env.d.ts +3 -2
  35. package/dist/env.esm.js +3 -3
  36. package/dist/file/client.cjs.js +2 -2
  37. package/dist/file/client.esm.js +2 -2
  38. package/dist/file/index.cjs.js +2 -2
  39. package/dist/file/index.esm.js +2 -2
  40. package/dist/file/put.cjs.js +2 -2
  41. package/dist/file/put.esm.js +2 -2
  42. package/dist/file/setup.cjs.js +2 -2
  43. package/dist/file/setup.esm.js +2 -2
  44. package/dist/file/utils.cjs.js +2 -2
  45. package/dist/file/utils.esm.js +2 -2
  46. package/dist/graphql/client.cjs.js +2 -2
  47. package/dist/graphql/client.esm.js +2 -2
  48. package/dist/graphql/index.cjs.js +2 -2
  49. package/dist/graphql/index.esm.js +2 -2
  50. package/dist/graphql/init.cjs.js +2 -2
  51. package/dist/graphql/init.esm.js +2 -2
  52. package/dist/graphql/links.cjs.js +2 -2
  53. package/dist/graphql/links.esm.js +2 -2
  54. package/dist/graphql/next.cjs.js +2 -2
  55. package/dist/graphql/next.esm.js +2 -2
  56. package/dist/graphql/server.cjs.js +2 -2
  57. package/dist/graphql/server.esm.js +2 -2
  58. package/dist/graphql/utils.cjs.js +2 -2
  59. package/dist/graphql/utils.esm.js +2 -2
  60. package/dist/socials/connect.cjs.js +151 -0
  61. package/dist/socials/connect.d.mts +70 -0
  62. package/dist/socials/connect.d.ts +70 -0
  63. package/dist/socials/connect.esm.js +126 -0
  64. package/dist/socials/index.cjs.js +367 -35
  65. package/dist/socials/index.d.mts +8 -2
  66. package/dist/socials/index.d.ts +8 -2
  67. package/dist/socials/index.esm.js +357 -34
  68. package/dist/socials/instagram/adapter.cjs.js +275 -0
  69. package/dist/socials/instagram/adapter.d.mts +30 -0
  70. package/dist/socials/instagram/adapter.d.ts +30 -0
  71. package/dist/socials/instagram/adapter.esm.js +248 -0
  72. package/dist/{auth/instagram/utils.cjs.js → socials/instagram/auth.cjs.js} +43 -41
  73. package/dist/socials/instagram/auth.d.mts +52 -0
  74. package/dist/socials/instagram/auth.d.ts +52 -0
  75. package/dist/{auth/instagram/utils.esm.js → socials/instagram/auth.esm.js} +37 -35
  76. package/dist/socials/instagram/getters.d.mts +1 -0
  77. package/dist/socials/instagram/getters.d.ts +1 -0
  78. package/dist/socials/instagram/setters.cjs.js +56 -31
  79. package/dist/socials/instagram/setters.d.mts +1 -0
  80. package/dist/socials/instagram/setters.d.ts +1 -0
  81. package/dist/socials/instagram/setters.esm.js +56 -31
  82. package/dist/socials/instagram/types.d.mts +2 -1
  83. package/dist/socials/instagram/types.d.ts +2 -1
  84. package/dist/socials/meta/container.cjs.js +57 -30
  85. package/dist/socials/meta/container.d.mts +15 -28
  86. package/dist/socials/meta/container.d.ts +15 -28
  87. package/dist/socials/meta/container.esm.js +56 -31
  88. package/dist/socials/provider.cjs.js +18 -0
  89. package/dist/socials/provider.d.mts +52 -0
  90. package/dist/socials/provider.d.ts +52 -0
  91. package/dist/socials/provider.esm.js +0 -0
  92. package/dist/socials/threads/getters.d.mts +1 -0
  93. package/dist/socials/threads/getters.d.ts +1 -0
  94. package/dist/socials/threads/setters.cjs.js +56 -31
  95. package/dist/socials/threads/setters.d.mts +1 -0
  96. package/dist/socials/threads/setters.d.ts +1 -0
  97. package/dist/socials/threads/setters.esm.js +56 -31
  98. package/dist/socials/threads/types.d.mts +2 -1
  99. package/dist/socials/threads/types.d.ts +2 -1
  100. package/dist/socials/types.cjs.js +43 -0
  101. package/dist/socials/types.d.mts +99 -0
  102. package/dist/socials/types.d.ts +99 -0
  103. package/dist/socials/types.esm.js +17 -0
  104. package/dist/socials/utils/poll.cjs.js +59 -0
  105. package/dist/socials/utils/poll.d.mts +85 -0
  106. package/dist/socials/utils/poll.d.ts +85 -0
  107. package/dist/socials/utils/poll.esm.js +33 -0
  108. package/dist/utils/client/index.cjs.js +2 -2
  109. package/dist/utils/client/index.esm.js +2 -2
  110. package/dist/utils/client/seo.cjs.js +2 -2
  111. package/dist/utils/client/seo.esm.js +2 -2
  112. package/package.json +1 -1
  113. package/dist/auth/instagram/index.cjs.js +0 -379
  114. package/dist/auth/instagram/index.d.mts +0 -58
  115. package/dist/auth/instagram/index.d.ts +0 -58
  116. package/dist/auth/instagram/index.esm.js +0 -350
  117. package/dist/auth/instagram/route.cjs.js +0 -359
  118. package/dist/auth/instagram/route.d.mts +0 -15
  119. package/dist/auth/instagram/route.d.ts +0 -15
  120. package/dist/auth/instagram/route.esm.js +0 -334
  121. package/dist/auth/instagram/utils.d.mts +0 -57
  122. package/dist/auth/instagram/utils.d.ts +0 -57
@@ -23,24 +23,43 @@ __export(container_exports, {
23
23
  createPublisher: () => createPublisher
24
24
  });
25
25
  module.exports = __toCommonJS(container_exports);
26
+
27
+ // src/socials/utils/poll.ts
26
28
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
27
- async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
29
+ async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
28
30
  const deadline = Date.now() + timeoutMS;
29
- let state = await getState();
31
+ let state = await read();
30
32
  if (!state) {
31
33
  await sleep(2e3);
32
34
  return null;
33
35
  }
34
- while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
36
+ while (state && isPending(state) && Date.now() < deadline) {
35
37
  await sleep(intervalMS);
36
- state = await getState();
38
+ state = await read();
37
39
  }
38
40
  return state;
39
41
  }
42
+ async function withRetry(run, options = {}) {
43
+ const attempts = Math.max(1, options.attempts ?? 3);
44
+ const backoffMS = options.backoffMS ?? 2e3;
45
+ for (let attempt = 1; attempt <= attempts; attempt++) {
46
+ const result = await run(attempt);
47
+ if (result) return result;
48
+ if (attempt < attempts) {
49
+ const delayMS = backoffMS * 2 ** (attempt - 1);
50
+ options.onRetry?.(attempt, attempts, delayMS);
51
+ await sleep(delayMS);
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ // src/socials/meta/container.ts
40
58
  function createPublisher(platform) {
41
59
  const isReady = async (token, id, label, wait) => {
42
- const state = await waitForContainer(
60
+ const state = await pollUntilReady(
43
61
  () => platform.getStatus(token, id),
62
+ (current) => current.status === "IN_PROGRESS",
44
63
  wait
45
64
  );
46
65
  if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
@@ -51,22 +70,21 @@ function createPublisher(platform) {
51
70
  }
52
71
  return true;
53
72
  };
54
- const createReady = async (token, params, label, wait, retry) => {
55
- const attempts = Math.max(1, retry?.attempts ?? 3);
56
- const backoffMS = retry?.backoffMS ?? 2e3;
57
- for (let attempt = 1; attempt <= attempts; attempt++) {
73
+ const createReady = (token, params, label, wait, retry) => withRetry(
74
+ async () => {
58
75
  const id = await platform.createContainer(token, params);
59
- if (id && await isReady(token, id, label, wait)) return id;
60
- if (attempt < attempts) {
61
- const delay = backoffMS * 2 ** (attempt - 1);
76
+ if (!id || !await isReady(token, id, label, wait)) return null;
77
+ return id;
78
+ },
79
+ {
80
+ ...retry,
81
+ onRetry: (attempt, attempts, delayMS) => {
62
82
  console.warn(
63
- `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delay}ms`
83
+ `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
64
84
  );
65
- await sleep(delay);
66
85
  }
67
86
  }
68
- return null;
69
- };
87
+ );
70
88
  return {
71
89
  /** Creates a container, waits for it to finish processing, then publishes it. */
72
90
  publish: async (token, params, wait, retry) => {
@@ -80,21 +98,30 @@ function createPublisher(platform) {
80
98
  if (!containerID) return null;
81
99
  return platform.publish(token, containerID);
82
100
  },
83
- /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
101
+ /**
102
+ * Creates and awaits every carousel child **in parallel**, resolving to
103
+ * their ids in input order — or `null` if any failed.
104
+ *
105
+ * Parallel because each child is an independent create-then-poll and Meta
106
+ * spends ~5s accepting a single container, so ten sequential children cost
107
+ * ~50s of mostly waiting — past what a serverless caller can fit in its
108
+ * timeout. `Promise.all` preserves input order, which the carousel's slide
109
+ * order depends on.
110
+ */
84
111
  createChildren: async (token, items, wait, retry) => {
85
- const children = [];
86
- for (const item of items) {
87
- const childID = await createReady(
88
- token,
89
- { ...item, is_carousel_item: true },
90
- "carousel item",
91
- wait,
92
- retry
93
- );
94
- if (!childID) return null;
95
- children.push(childID);
96
- }
97
- return children;
112
+ const children = await Promise.all(
113
+ items.map(
114
+ (item) => createReady(
115
+ token,
116
+ { ...item, is_carousel_item: true },
117
+ "carousel item",
118
+ wait,
119
+ retry
120
+ )
121
+ )
122
+ );
123
+ const created = children.filter((childID) => childID !== null);
124
+ return created.length === children.length ? created : null;
98
125
  }
99
126
  };
100
127
  }
@@ -1,35 +1,13 @@
1
1
  import { GraphParams, ContainerState } from './types.mjs';
2
+ import { PollOptions, RetryOptions } from '../utils/poll.mjs';
2
3
 
3
4
  /**
4
- * Polling settings for a media container.
5
- *
6
- * @property intervalMS - Delay between status checks. Default: `5000`.
7
- * @property timeoutMS - Give up after this long. Default: `300000` (Meta stops processing after ~5 min).
5
+ * Polling settings for a media container. The default timeout matches Meta,
6
+ * which stops processing after ~5 min.
8
7
  *
9
8
  * @category Socials
10
9
  */
11
- type WaitForContainerOptions = {
12
- intervalMS?: number;
13
- timeoutMS?: number;
14
- };
15
- /**
16
- * Retry settings for creating a media container.
17
- *
18
- * Meta intermittently rejects a perfectly valid container — most visibly on
19
- * carousels, where one arbitrary child fails with "Only photo or video can be
20
- * accepted as media type." and the identical call succeeds moments later. The
21
- * failure is transient and carries no distinguishing error code, so container
22
- * creation is simply retried rather than pattern-matched against Meta's copy.
23
- *
24
- * @property attempts - Total tries, including the first. Default: `3`.
25
- * @property backoffMS - Delay before the second try, doubled each time after. Default: `2000`.
26
- *
27
- * @category Socials
28
- */
29
- type RetryOptions = {
30
- attempts?: number;
31
- backoffMS?: number;
32
- };
10
+ type WaitForContainerOptions = PollOptions;
33
11
  /**
34
12
  * Wires up the create → wait → publish flow Meta platforms share. Instagram and
35
13
  * Threads differ only in their endpoints, so each builds one publisher and every
@@ -49,8 +27,17 @@ declare function createPublisher(platform: {
49
27
  }): {
50
28
  /** Creates a container, waits for it to finish processing, then publishes it. */
51
29
  publish: (token: string, params: GraphParams, wait?: WaitForContainerOptions, retry?: RetryOptions) => Promise<string | null>;
52
- /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
30
+ /**
31
+ * Creates and awaits every carousel child **in parallel**, resolving to
32
+ * their ids in input order — or `null` if any failed.
33
+ *
34
+ * Parallel because each child is an independent create-then-poll and Meta
35
+ * spends ~5s accepting a single container, so ten sequential children cost
36
+ * ~50s of mostly waiting — past what a serverless caller can fit in its
37
+ * timeout. `Promise.all` preserves input order, which the carousel's slide
38
+ * order depends on.
39
+ */
53
40
  createChildren: (token: string, items: GraphParams[], wait?: WaitForContainerOptions, retry?: RetryOptions) => Promise<string[] | null>;
54
41
  };
55
42
 
56
- export { type RetryOptions, type WaitForContainerOptions, createPublisher };
43
+ export { RetryOptions, type WaitForContainerOptions, createPublisher };
@@ -1,35 +1,13 @@
1
1
  import { GraphParams, ContainerState } from './types.js';
2
+ import { PollOptions, RetryOptions } from '../utils/poll.js';
2
3
 
3
4
  /**
4
- * Polling settings for a media container.
5
- *
6
- * @property intervalMS - Delay between status checks. Default: `5000`.
7
- * @property timeoutMS - Give up after this long. Default: `300000` (Meta stops processing after ~5 min).
5
+ * Polling settings for a media container. The default timeout matches Meta,
6
+ * which stops processing after ~5 min.
8
7
  *
9
8
  * @category Socials
10
9
  */
11
- type WaitForContainerOptions = {
12
- intervalMS?: number;
13
- timeoutMS?: number;
14
- };
15
- /**
16
- * Retry settings for creating a media container.
17
- *
18
- * Meta intermittently rejects a perfectly valid container — most visibly on
19
- * carousels, where one arbitrary child fails with "Only photo or video can be
20
- * accepted as media type." and the identical call succeeds moments later. The
21
- * failure is transient and carries no distinguishing error code, so container
22
- * creation is simply retried rather than pattern-matched against Meta's copy.
23
- *
24
- * @property attempts - Total tries, including the first. Default: `3`.
25
- * @property backoffMS - Delay before the second try, doubled each time after. Default: `2000`.
26
- *
27
- * @category Socials
28
- */
29
- type RetryOptions = {
30
- attempts?: number;
31
- backoffMS?: number;
32
- };
10
+ type WaitForContainerOptions = PollOptions;
33
11
  /**
34
12
  * Wires up the create → wait → publish flow Meta platforms share. Instagram and
35
13
  * Threads differ only in their endpoints, so each builds one publisher and every
@@ -49,8 +27,17 @@ declare function createPublisher(platform: {
49
27
  }): {
50
28
  /** Creates a container, waits for it to finish processing, then publishes it. */
51
29
  publish: (token: string, params: GraphParams, wait?: WaitForContainerOptions, retry?: RetryOptions) => Promise<string | null>;
52
- /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
30
+ /**
31
+ * Creates and awaits every carousel child **in parallel**, resolving to
32
+ * their ids in input order — or `null` if any failed.
33
+ *
34
+ * Parallel because each child is an independent create-then-poll and Meta
35
+ * spends ~5s accepting a single container, so ten sequential children cost
36
+ * ~50s of mostly waiting — past what a serverless caller can fit in its
37
+ * timeout. `Promise.all` preserves input order, which the carousel's slide
38
+ * order depends on.
39
+ */
53
40
  createChildren: (token: string, items: GraphParams[], wait?: WaitForContainerOptions, retry?: RetryOptions) => Promise<string[] | null>;
54
41
  };
55
42
 
56
- export { type RetryOptions, type WaitForContainerOptions, createPublisher };
43
+ export { RetryOptions, type WaitForContainerOptions, createPublisher };
@@ -1,22 +1,39 @@
1
- // src/socials/meta/container.ts
1
+ // src/socials/utils/poll.ts
2
2
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3
- async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
3
+ async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
4
4
  const deadline = Date.now() + timeoutMS;
5
- let state = await getState();
5
+ let state = await read();
6
6
  if (!state) {
7
7
  await sleep(2e3);
8
8
  return null;
9
9
  }
10
- while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
10
+ while (state && isPending(state) && Date.now() < deadline) {
11
11
  await sleep(intervalMS);
12
- state = await getState();
12
+ state = await read();
13
13
  }
14
14
  return state;
15
15
  }
16
+ async function withRetry(run, options = {}) {
17
+ const attempts = Math.max(1, options.attempts ?? 3);
18
+ const backoffMS = options.backoffMS ?? 2e3;
19
+ for (let attempt = 1; attempt <= attempts; attempt++) {
20
+ const result = await run(attempt);
21
+ if (result) return result;
22
+ if (attempt < attempts) {
23
+ const delayMS = backoffMS * 2 ** (attempt - 1);
24
+ options.onRetry?.(attempt, attempts, delayMS);
25
+ await sleep(delayMS);
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+
31
+ // src/socials/meta/container.ts
16
32
  function createPublisher(platform) {
17
33
  const isReady = async (token, id, label, wait) => {
18
- const state = await waitForContainer(
34
+ const state = await pollUntilReady(
19
35
  () => platform.getStatus(token, id),
36
+ (current) => current.status === "IN_PROGRESS",
20
37
  wait
21
38
  );
22
39
  if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
@@ -27,22 +44,21 @@ function createPublisher(platform) {
27
44
  }
28
45
  return true;
29
46
  };
30
- const createReady = async (token, params, label, wait, retry) => {
31
- const attempts = Math.max(1, retry?.attempts ?? 3);
32
- const backoffMS = retry?.backoffMS ?? 2e3;
33
- for (let attempt = 1; attempt <= attempts; attempt++) {
47
+ const createReady = (token, params, label, wait, retry) => withRetry(
48
+ async () => {
34
49
  const id = await platform.createContainer(token, params);
35
- if (id && await isReady(token, id, label, wait)) return id;
36
- if (attempt < attempts) {
37
- const delay = backoffMS * 2 ** (attempt - 1);
50
+ if (!id || !await isReady(token, id, label, wait)) return null;
51
+ return id;
52
+ },
53
+ {
54
+ ...retry,
55
+ onRetry: (attempt, attempts, delayMS) => {
38
56
  console.warn(
39
- `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delay}ms`
57
+ `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
40
58
  );
41
- await sleep(delay);
42
59
  }
43
60
  }
44
- return null;
45
- };
61
+ );
46
62
  return {
47
63
  /** Creates a container, waits for it to finish processing, then publishes it. */
48
64
  publish: async (token, params, wait, retry) => {
@@ -56,21 +72,30 @@ function createPublisher(platform) {
56
72
  if (!containerID) return null;
57
73
  return platform.publish(token, containerID);
58
74
  },
59
- /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
75
+ /**
76
+ * Creates and awaits every carousel child **in parallel**, resolving to
77
+ * their ids in input order — or `null` if any failed.
78
+ *
79
+ * Parallel because each child is an independent create-then-poll and Meta
80
+ * spends ~5s accepting a single container, so ten sequential children cost
81
+ * ~50s of mostly waiting — past what a serverless caller can fit in its
82
+ * timeout. `Promise.all` preserves input order, which the carousel's slide
83
+ * order depends on.
84
+ */
60
85
  createChildren: async (token, items, wait, retry) => {
61
- const children = [];
62
- for (const item of items) {
63
- const childID = await createReady(
64
- token,
65
- { ...item, is_carousel_item: true },
66
- "carousel item",
67
- wait,
68
- retry
69
- );
70
- if (!childID) return null;
71
- children.push(childID);
72
- }
73
- return children;
86
+ const children = await Promise.all(
87
+ items.map(
88
+ (item) => createReady(
89
+ token,
90
+ { ...item, is_carousel_item: true },
91
+ "carousel item",
92
+ wait,
93
+ retry
94
+ )
95
+ )
96
+ );
97
+ const created = children.filter((childID) => childID !== null);
98
+ return created.length === children.length ? created : null;
74
99
  }
75
100
  };
76
101
  }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/socials/provider.ts
17
+ var provider_exports = {};
18
+ module.exports = __toCommonJS(provider_exports);
@@ -0,0 +1,52 @@
1
+ import { SocialPlatform, SocialTokens, SocialProfile, SocialPost } from './types.mjs';
2
+
3
+ /**
4
+ * One platform's implementation of connect, read profile, read content.
5
+ *
6
+ * A plain object with no per-instance state — credentials come from the
7
+ * environment at call time, tokens are arguments — so each platform exports one
8
+ * ready-made constant. `InstagramProvider` is the reference implementation.
9
+ *
10
+ * Capability is method presence: a platform that can't refresh tokens omits
11
+ * `refresh`, one with no content-listing API omits `fetchMedia`, and callers
12
+ * narrow with `if (provider.fetchMedia)`. This describes the platform, not a
13
+ * token — a token missing a scope still fails at runtime with a `null` return.
14
+ *
15
+ * @property auth.authorizationURL - `state` comes back on the callback; `redirectURI` must match what the platform has registered.
16
+ * @property auth.exchangeCode - Trades the callback `code` for tokens. `platformUserId` is `null` on platforms whose token response carries no id (Google); the route then takes it from `fetchProfile`.
17
+ * @property auth.refresh - Omitted by platforms whose tokens don't expire or can't be refreshed.
18
+ * @property fetchMedia - Omitted by platforms with no content-listing API.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { InstagramProvider } from "naystack/socials";
23
+ *
24
+ * const profile = await InstagramProvider.fetchProfile(accessToken);
25
+ * const posts = await InstagramProvider.fetchMedia?.(accessToken, { limit: 12 });
26
+ * ```
27
+ *
28
+ * @category Socials
29
+ */
30
+ type SocialProvider = {
31
+ platform: SocialPlatform;
32
+ auth: {
33
+ authorizationURL: (input: {
34
+ state: string;
35
+ redirectURI: string;
36
+ scopes?: string[];
37
+ }) => string;
38
+ exchangeCode: (input: {
39
+ code: string;
40
+ redirectURI: string;
41
+ }) => Promise<(SocialTokens & {
42
+ platformUserId: string | null;
43
+ }) | null>;
44
+ refresh?: (tokens: SocialTokens) => Promise<SocialTokens | null>;
45
+ };
46
+ fetchProfile: (accessToken: string) => Promise<SocialProfile | null>;
47
+ fetchMedia?: (accessToken: string, options?: {
48
+ limit?: number;
49
+ }) => Promise<SocialPost[]>;
50
+ };
51
+
52
+ export type { SocialProvider };
@@ -0,0 +1,52 @@
1
+ import { SocialPlatform, SocialTokens, SocialProfile, SocialPost } from './types.js';
2
+
3
+ /**
4
+ * One platform's implementation of connect, read profile, read content.
5
+ *
6
+ * A plain object with no per-instance state — credentials come from the
7
+ * environment at call time, tokens are arguments — so each platform exports one
8
+ * ready-made constant. `InstagramProvider` is the reference implementation.
9
+ *
10
+ * Capability is method presence: a platform that can't refresh tokens omits
11
+ * `refresh`, one with no content-listing API omits `fetchMedia`, and callers
12
+ * narrow with `if (provider.fetchMedia)`. This describes the platform, not a
13
+ * token — a token missing a scope still fails at runtime with a `null` return.
14
+ *
15
+ * @property auth.authorizationURL - `state` comes back on the callback; `redirectURI` must match what the platform has registered.
16
+ * @property auth.exchangeCode - Trades the callback `code` for tokens. `platformUserId` is `null` on platforms whose token response carries no id (Google); the route then takes it from `fetchProfile`.
17
+ * @property auth.refresh - Omitted by platforms whose tokens don't expire or can't be refreshed.
18
+ * @property fetchMedia - Omitted by platforms with no content-listing API.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { InstagramProvider } from "naystack/socials";
23
+ *
24
+ * const profile = await InstagramProvider.fetchProfile(accessToken);
25
+ * const posts = await InstagramProvider.fetchMedia?.(accessToken, { limit: 12 });
26
+ * ```
27
+ *
28
+ * @category Socials
29
+ */
30
+ type SocialProvider = {
31
+ platform: SocialPlatform;
32
+ auth: {
33
+ authorizationURL: (input: {
34
+ state: string;
35
+ redirectURI: string;
36
+ scopes?: string[];
37
+ }) => string;
38
+ exchangeCode: (input: {
39
+ code: string;
40
+ redirectURI: string;
41
+ }) => Promise<(SocialTokens & {
42
+ platformUserId: string | null;
43
+ }) | null>;
44
+ refresh?: (tokens: SocialTokens) => Promise<SocialTokens | null>;
45
+ };
46
+ fetchProfile: (accessToken: string) => Promise<SocialProfile | null>;
47
+ fetchMedia?: (accessToken: string, options?: {
48
+ limit?: number;
49
+ }) => Promise<SocialPost[]>;
50
+ };
51
+
52
+ export type { SocialProvider };
File without changes
@@ -1,6 +1,7 @@
1
1
  import { GraphError, ContainerState } from '../meta/types.mjs';
2
2
  import { ThreadsPost } from './types.mjs';
3
3
  import '../meta/container.mjs';
4
+ import '../utils/poll.mjs';
4
5
 
5
6
  /**
6
7
  * Fetches a single Threads post by id.
@@ -1,6 +1,7 @@
1
1
  import { GraphError, ContainerState } from '../meta/types.js';
2
2
  import { ThreadsPost } from './types.js';
3
3
  import '../meta/container.js';
4
+ import '../utils/poll.js';
4
5
 
5
6
  /**
6
7
  * Fetches a single Threads post by id.
@@ -25,25 +25,42 @@ __export(setters_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(setters_exports);
27
27
 
28
- // src/socials/meta/container.ts
28
+ // src/socials/utils/poll.ts
29
29
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
30
- async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
30
+ async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
31
31
  const deadline = Date.now() + timeoutMS;
32
- let state = await getState();
32
+ let state = await read();
33
33
  if (!state) {
34
34
  await sleep(2e3);
35
35
  return null;
36
36
  }
37
- while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
37
+ while (state && isPending(state) && Date.now() < deadline) {
38
38
  await sleep(intervalMS);
39
- state = await getState();
39
+ state = await read();
40
40
  }
41
41
  return state;
42
42
  }
43
+ async function withRetry(run, options = {}) {
44
+ const attempts = Math.max(1, options.attempts ?? 3);
45
+ const backoffMS = options.backoffMS ?? 2e3;
46
+ for (let attempt = 1; attempt <= attempts; attempt++) {
47
+ const result = await run(attempt);
48
+ if (result) return result;
49
+ if (attempt < attempts) {
50
+ const delayMS = backoffMS * 2 ** (attempt - 1);
51
+ options.onRetry?.(attempt, attempts, delayMS);
52
+ await sleep(delayMS);
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+
58
+ // src/socials/meta/container.ts
43
59
  function createPublisher(platform) {
44
60
  const isReady = async (token, id, label, wait) => {
45
- const state = await waitForContainer(
61
+ const state = await pollUntilReady(
46
62
  () => platform.getStatus(token, id),
63
+ (current) => current.status === "IN_PROGRESS",
47
64
  wait
48
65
  );
49
66
  if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
@@ -54,22 +71,21 @@ function createPublisher(platform) {
54
71
  }
55
72
  return true;
56
73
  };
57
- const createReady = async (token, params, label, wait, retry) => {
58
- const attempts = Math.max(1, retry?.attempts ?? 3);
59
- const backoffMS = retry?.backoffMS ?? 2e3;
60
- for (let attempt = 1; attempt <= attempts; attempt++) {
74
+ const createReady = (token, params, label, wait, retry) => withRetry(
75
+ async () => {
61
76
  const id = await platform.createContainer(token, params);
62
- if (id && await isReady(token, id, label, wait)) return id;
63
- if (attempt < attempts) {
64
- const delay = backoffMS * 2 ** (attempt - 1);
77
+ if (!id || !await isReady(token, id, label, wait)) return null;
78
+ return id;
79
+ },
80
+ {
81
+ ...retry,
82
+ onRetry: (attempt, attempts, delayMS) => {
65
83
  console.warn(
66
- `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delay}ms`
84
+ `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
67
85
  );
68
- await sleep(delay);
69
86
  }
70
87
  }
71
- return null;
72
- };
88
+ );
73
89
  return {
74
90
  /** Creates a container, waits for it to finish processing, then publishes it. */
75
91
  publish: async (token, params, wait, retry) => {
@@ -83,21 +99,30 @@ function createPublisher(platform) {
83
99
  if (!containerID) return null;
84
100
  return platform.publish(token, containerID);
85
101
  },
86
- /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
102
+ /**
103
+ * Creates and awaits every carousel child **in parallel**, resolving to
104
+ * their ids in input order — or `null` if any failed.
105
+ *
106
+ * Parallel because each child is an independent create-then-poll and Meta
107
+ * spends ~5s accepting a single container, so ten sequential children cost
108
+ * ~50s of mostly waiting — past what a serverless caller can fit in its
109
+ * timeout. `Promise.all` preserves input order, which the carousel's slide
110
+ * order depends on.
111
+ */
87
112
  createChildren: async (token, items, wait, retry) => {
88
- const children = [];
89
- for (const item of items) {
90
- const childID = await createReady(
91
- token,
92
- { ...item, is_carousel_item: true },
93
- "carousel item",
94
- wait,
95
- retry
96
- );
97
- if (!childID) return null;
98
- children.push(childID);
99
- }
100
- return children;
113
+ const children = await Promise.all(
114
+ items.map(
115
+ (item) => createReady(
116
+ token,
117
+ { ...item, is_carousel_item: true },
118
+ "carousel item",
119
+ wait,
120
+ retry
121
+ )
122
+ )
123
+ );
124
+ const created = children.filter((childID) => childID !== null);
125
+ return created.length === children.length ? created : null;
101
126
  }
102
127
  };
103
128
  }
@@ -1,6 +1,7 @@
1
1
  import { ThreadsPostInput } from './types.mjs';
2
2
  import '../meta/container.mjs';
3
3
  import '../meta/types.mjs';
4
+ import '../utils/poll.mjs';
4
5
 
5
6
  /**
6
7
  * Publishes to Threads — text, image, video or carousel, chosen from the media you
@@ -1,6 +1,7 @@
1
1
  import { ThreadsPostInput } from './types.js';
2
2
  import '../meta/container.js';
3
3
  import '../meta/types.js';
4
+ import '../utils/poll.js';
4
5
 
5
6
  /**
6
7
  * Publishes to Threads — text, image, video or carousel, chosen from the media you