naystack 1.8.19 → 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 -306
  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 -302
  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 +344 -21
  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 +334 -20
  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 +33 -17
  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 +33 -17
  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 +34 -16
  85. package/dist/socials/meta/container.d.mts +5 -27
  86. package/dist/socials/meta/container.d.ts +5 -27
  87. package/dist/socials/meta/container.esm.js +33 -17
  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 +33 -17
  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 +33 -17
  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 -388
  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 -359
  117. package/dist/auth/instagram/route.cjs.js +0 -368
  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 -343
  121. package/dist/auth/instagram/utils.d.mts +0 -57
  122. package/dist/auth/instagram/utils.d.ts +0 -57
@@ -71,25 +71,42 @@ var getInstagramContainerStatus = async (token, id) => {
71
71
  };
72
72
  };
73
73
 
74
- // src/socials/meta/container.ts
74
+ // src/socials/utils/poll.ts
75
75
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
76
- async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
76
+ async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
77
77
  const deadline = Date.now() + timeoutMS;
78
- let state = await getState();
78
+ let state = await read();
79
79
  if (!state) {
80
80
  await sleep(2e3);
81
81
  return null;
82
82
  }
83
- while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
83
+ while (state && isPending(state) && Date.now() < deadline) {
84
84
  await sleep(intervalMS);
85
- state = await getState();
85
+ state = await read();
86
86
  }
87
87
  return state;
88
88
  }
89
+ async function withRetry(run, options = {}) {
90
+ const attempts = Math.max(1, options.attempts ?? 3);
91
+ const backoffMS = options.backoffMS ?? 2e3;
92
+ for (let attempt = 1; attempt <= attempts; attempt++) {
93
+ const result = await run(attempt);
94
+ if (result) return result;
95
+ if (attempt < attempts) {
96
+ const delayMS = backoffMS * 2 ** (attempt - 1);
97
+ options.onRetry?.(attempt, attempts, delayMS);
98
+ await sleep(delayMS);
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+
104
+ // src/socials/meta/container.ts
89
105
  function createPublisher(platform) {
90
106
  const isReady = async (token, id, label, wait) => {
91
- const state = await waitForContainer(
107
+ const state = await pollUntilReady(
92
108
  () => platform.getStatus(token, id),
109
+ (current) => current.status === "IN_PROGRESS",
93
110
  wait
94
111
  );
95
112
  if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
@@ -100,22 +117,21 @@ function createPublisher(platform) {
100
117
  }
101
118
  return true;
102
119
  };
103
- const createReady = async (token, params, label, wait, retry) => {
104
- const attempts = Math.max(1, retry?.attempts ?? 3);
105
- const backoffMS = retry?.backoffMS ?? 2e3;
106
- for (let attempt = 1; attempt <= attempts; attempt++) {
120
+ const createReady = (token, params, label, wait, retry) => withRetry(
121
+ async () => {
107
122
  const id = await platform.createContainer(token, params);
108
- if (id && await isReady(token, id, label, wait)) return id;
109
- if (attempt < attempts) {
110
- const delay = backoffMS * 2 ** (attempt - 1);
123
+ if (!id || !await isReady(token, id, label, wait)) return null;
124
+ return id;
125
+ },
126
+ {
127
+ ...retry,
128
+ onRetry: (attempt, attempts, delayMS) => {
111
129
  console.warn(
112
- `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delay}ms`
130
+ `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
113
131
  );
114
- await sleep(delay);
115
132
  }
116
133
  }
117
- return null;
118
- };
134
+ );
119
135
  return {
120
136
  /** Creates a container, waits for it to finish processing, then publishes it. */
121
137
  publish: async (token, params, wait, retry) => {
@@ -1,6 +1,7 @@
1
1
  import { GraphError } from '../meta/types.mjs';
2
2
  import { InstagramPostInput } from './types.mjs';
3
3
  import '../meta/container.mjs';
4
+ import '../utils/poll.mjs';
4
5
 
5
6
  /**
6
7
  * Publishes to Instagram — feed image, reel, story or carousel, chosen from the
@@ -1,6 +1,7 @@
1
1
  import { GraphError } from '../meta/types.js';
2
2
  import { InstagramPostInput } from './types.js';
3
3
  import '../meta/container.js';
4
+ import '../utils/poll.js';
4
5
 
5
6
  /**
6
7
  * Publishes to Instagram — feed image, reel, story or carousel, chosen from the
@@ -43,25 +43,42 @@ var getInstagramContainerStatus = async (token, id) => {
43
43
  };
44
44
  };
45
45
 
46
- // src/socials/meta/container.ts
46
+ // src/socials/utils/poll.ts
47
47
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
48
- async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
48
+ async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
49
49
  const deadline = Date.now() + timeoutMS;
50
- let state = await getState();
50
+ let state = await read();
51
51
  if (!state) {
52
52
  await sleep(2e3);
53
53
  return null;
54
54
  }
55
- while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
55
+ while (state && isPending(state) && Date.now() < deadline) {
56
56
  await sleep(intervalMS);
57
- state = await getState();
57
+ state = await read();
58
58
  }
59
59
  return state;
60
60
  }
61
+ async function withRetry(run, options = {}) {
62
+ const attempts = Math.max(1, options.attempts ?? 3);
63
+ const backoffMS = options.backoffMS ?? 2e3;
64
+ for (let attempt = 1; attempt <= attempts; attempt++) {
65
+ const result = await run(attempt);
66
+ if (result) return result;
67
+ if (attempt < attempts) {
68
+ const delayMS = backoffMS * 2 ** (attempt - 1);
69
+ options.onRetry?.(attempt, attempts, delayMS);
70
+ await sleep(delayMS);
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+
76
+ // src/socials/meta/container.ts
61
77
  function createPublisher(platform) {
62
78
  const isReady = async (token, id, label, wait) => {
63
- const state = await waitForContainer(
79
+ const state = await pollUntilReady(
64
80
  () => platform.getStatus(token, id),
81
+ (current) => current.status === "IN_PROGRESS",
65
82
  wait
66
83
  );
67
84
  if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
@@ -72,22 +89,21 @@ function createPublisher(platform) {
72
89
  }
73
90
  return true;
74
91
  };
75
- const createReady = async (token, params, label, wait, retry) => {
76
- const attempts = Math.max(1, retry?.attempts ?? 3);
77
- const backoffMS = retry?.backoffMS ?? 2e3;
78
- for (let attempt = 1; attempt <= attempts; attempt++) {
92
+ const createReady = (token, params, label, wait, retry) => withRetry(
93
+ async () => {
79
94
  const id = await platform.createContainer(token, params);
80
- if (id && await isReady(token, id, label, wait)) return id;
81
- if (attempt < attempts) {
82
- const delay = backoffMS * 2 ** (attempt - 1);
95
+ if (!id || !await isReady(token, id, label, wait)) return null;
96
+ return id;
97
+ },
98
+ {
99
+ ...retry,
100
+ onRetry: (attempt, attempts, delayMS) => {
83
101
  console.warn(
84
- `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delay}ms`
102
+ `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
85
103
  );
86
- await sleep(delay);
87
104
  }
88
105
  }
89
- return null;
90
- };
106
+ );
91
107
  return {
92
108
  /** Creates a container, waits for it to finish processing, then publishes it. */
93
109
  publish: async (token, params, wait, retry) => {
@@ -1,5 +1,6 @@
1
- import { WaitForContainerOptions, RetryOptions } from '../meta/container.mjs';
1
+ import { WaitForContainerOptions } from '../meta/container.mjs';
2
2
  import { MetaMediaType } from '../meta/types.mjs';
3
+ import { RetryOptions } from '../utils/poll.mjs';
3
4
 
4
5
  /**
5
6
  * Instagram Messaging API message shape.
@@ -1,5 +1,6 @@
1
- import { WaitForContainerOptions, RetryOptions } from '../meta/container.js';
1
+ import { WaitForContainerOptions } from '../meta/container.js';
2
2
  import { MetaMediaType } from '../meta/types.js';
3
+ import { RetryOptions } from '../utils/poll.js';
3
4
 
4
5
  /**
5
6
  * Instagram Messaging API message shape.
@@ -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) => {
@@ -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
@@ -62,4 +40,4 @@ declare function createPublisher(platform: {
62
40
  createChildren: (token: string, items: GraphParams[], wait?: WaitForContainerOptions, retry?: RetryOptions) => Promise<string[] | null>;
63
41
  };
64
42
 
65
- 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
@@ -62,4 +40,4 @@ declare function createPublisher(platform: {
62
40
  createChildren: (token: string, items: GraphParams[], wait?: WaitForContainerOptions, retry?: RetryOptions) => Promise<string[] | null>;
63
41
  };
64
42
 
65
- 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) => {
@@ -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.