@venturekit-pro/social 0.0.0-dev.20260602192622

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 (53) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +134 -0
  3. package/dist/adapter.d.ts +46 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +15 -0
  6. package/dist/adapter.js.map +1 -0
  7. package/dist/adapters/facebook.d.ts +25 -0
  8. package/dist/adapters/facebook.d.ts.map +1 -0
  9. package/dist/adapters/facebook.js +95 -0
  10. package/dist/adapters/facebook.js.map +1 -0
  11. package/dist/adapters/index.d.ts +23 -0
  12. package/dist/adapters/index.d.ts.map +1 -0
  13. package/dist/adapters/index.js +19 -0
  14. package/dist/adapters/index.js.map +1 -0
  15. package/dist/adapters/instagram.d.ts +25 -0
  16. package/dist/adapters/instagram.d.ts.map +1 -0
  17. package/dist/adapters/instagram.js +113 -0
  18. package/dist/adapters/instagram.js.map +1 -0
  19. package/dist/adapters/linkedin.d.ts +28 -0
  20. package/dist/adapters/linkedin.d.ts.map +1 -0
  21. package/dist/adapters/linkedin.js +121 -0
  22. package/dist/adapters/linkedin.js.map +1 -0
  23. package/dist/adapters/x.d.ts +21 -0
  24. package/dist/adapters/x.d.ts.map +1 -0
  25. package/dist/adapters/x.js +80 -0
  26. package/dist/adapters/x.js.map +1 -0
  27. package/dist/http.d.ts +40 -0
  28. package/dist/http.d.ts.map +1 -0
  29. package/dist/http.js +127 -0
  30. package/dist/http.js.map +1 -0
  31. package/dist/index.d.ts +28 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +27 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/migrations/vk_social_0001_init.sql +121 -0
  36. package/dist/path.d.ts +9 -0
  37. package/dist/path.d.ts.map +1 -0
  38. package/dist/path.js +17 -0
  39. package/dist/path.js.map +1 -0
  40. package/dist/registry.d.ts +32 -0
  41. package/dist/registry.d.ts.map +1 -0
  42. package/dist/registry.js +42 -0
  43. package/dist/registry.js.map +1 -0
  44. package/dist/types.d.ts +229 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +57 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/validation.d.ts +17 -0
  49. package/dist/validation.d.ts.map +1 -0
  50. package/dist/validation.js +157 -0
  51. package/dist/validation.js.map +1 -0
  52. package/package.json +78 -0
  53. package/src/migrations/vk_social_0001_init.sql +121 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Instagram (Business / Creator) adapter — Meta Graph Content
3
+ * Publishing API.
4
+ *
5
+ * Two-step protocol — the only one in this package:
6
+ * 1. `POST /v18.0/<ig-user-id>/media` → returns `creation_id`
7
+ * 2. `POST /v18.0/<ig-user-id>/media_publish` → publishes the container
8
+ *
9
+ * Auth: Page-linked IG user access token via
10
+ * `Authorization: Bearer …`.
11
+ *
12
+ * `credentials.authorRef` carries the IG user id (e.g.
13
+ * `'ig_user_12345'`). Caption is required; media is required —
14
+ * Instagram doesn't allow text-only posts.
15
+ */
16
+ import { vendorFetch } from '../http.js';
17
+ import { validatePost } from '../validation.js';
18
+ const META_GRAPH_BASE = 'https://graph.facebook.com';
19
+ const META_GRAPH_VERSION = 'v18.0';
20
+ export const INSTAGRAM_CONSTRAINTS = {
21
+ maxBodyChars: 2200,
22
+ // Instagram doesn't strictly require a caption but a 0-char caption
23
+ // is universally a smell. Adapter requires 1+ char for a meaningful
24
+ // post; apps can override.
25
+ minBodyChars: 1,
26
+ minHashtags: 5,
27
+ maxHashtags: 12,
28
+ allowedMimeTypes: ['image/jpeg'],
29
+ maxMediaCount: 1, // Carousels require a different multi-step flow; v1 = single
30
+ allowedAspectRatios: ['1:1', '4:5'],
31
+ maxMediaSizeBytes: 8 * 1024 * 1024,
32
+ supportsFirstComment: true,
33
+ // Text-only posts are rejected by the Content Publishing API; the
34
+ // validator catches this client-side so the caller doesn't have to
35
+ // round-trip to Meta to discover it.
36
+ requiresMedia: true,
37
+ };
38
+ export function createInstagramAdapter(config = {}) {
39
+ const baseUrl = (config.baseUrl ?? META_GRAPH_BASE).replace(/\/+$/, '');
40
+ const version = config.graphVersion ?? META_GRAPH_VERSION;
41
+ const constraints = {
42
+ ...INSTAGRAM_CONSTRAINTS,
43
+ ...config.constraints,
44
+ };
45
+ return {
46
+ key: 'instagram',
47
+ displayName: 'Instagram',
48
+ constraints,
49
+ validate(post) {
50
+ return validatePost(post, constraints);
51
+ },
52
+ async publish(post, credentials) {
53
+ const igUserId = stripIgPrefix(credentials.authorRef);
54
+ const media = post.media?.[0];
55
+ if (!media) {
56
+ // Validation should have caught this, but defensive: IG
57
+ // refuses text-only posts at the API level.
58
+ throw new Error('[social/instagram] Instagram requires at least one media item.');
59
+ }
60
+ // ─── Step 1: create the container ────────────────────────────
61
+ const createBody = new URLSearchParams({
62
+ image_url: media.url,
63
+ caption: post.body,
64
+ });
65
+ const createRes = await vendorFetch('instagram', `${baseUrl}/${version}/${encodeURIComponent(igUserId)}/media`, {
66
+ method: 'POST',
67
+ headers: {
68
+ Authorization: `Bearer ${credentials.accessToken}`,
69
+ 'Content-Type': 'application/x-www-form-urlencoded',
70
+ },
71
+ body: createBody,
72
+ });
73
+ const created = createRes.body;
74
+ const creationId = created?.id;
75
+ if (!creationId) {
76
+ throw new Error('[social/instagram] media-create returned no id; vendor body: ' +
77
+ createRes.bodyText.slice(0, 200));
78
+ }
79
+ // ─── Step 2: publish the container ──────────────────────────
80
+ const publishBody = new URLSearchParams({
81
+ creation_id: creationId,
82
+ });
83
+ const publishRes = await vendorFetch('instagram', `${baseUrl}/${version}/${encodeURIComponent(igUserId)}/media_publish`, {
84
+ method: 'POST',
85
+ headers: {
86
+ Authorization: `Bearer ${credentials.accessToken}`,
87
+ 'Content-Type': 'application/x-www-form-urlencoded',
88
+ },
89
+ body: publishBody,
90
+ });
91
+ const published = publishRes.body;
92
+ const externalRef = published?.id;
93
+ if (!externalRef) {
94
+ throw new Error('[social/instagram] media_publish returned no id; vendor body: ' +
95
+ publishRes.bodyText.slice(0, 200));
96
+ }
97
+ return {
98
+ externalRef,
99
+ // IG doesn't expose a deterministic media-permalink at
100
+ // publish time; the caller can re-fetch via
101
+ // `GET /<ig-media-id>?fields=permalink` if needed.
102
+ publishedAt: new Date().toISOString(),
103
+ idempotencyKey: post.idempotencyKey,
104
+ correlationId: post.correlationId,
105
+ raw: { container: created, publish: published },
106
+ };
107
+ },
108
+ };
109
+ }
110
+ function stripIgPrefix(authorRef) {
111
+ return authorRef.startsWith('ig_user_') ? authorRef.slice(8) : authorRef;
112
+ }
113
+ //# sourceMappingURL=instagram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instagram.js","sourceRoot":"","sources":["../../src/adapters/instagram.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * LinkedIn adapter — UGC Posts API (v2).
3
+ *
4
+ * Endpoint: `POST https://api.linkedin.com/v2/ugcPosts`
5
+ * Auth: OAuth 2.0 bearer token in `Authorization: Bearer …`
6
+ * Author: `urn:li:organization:<id>` or `urn:li:person:<id>`
7
+ * — caller supplies via `credentials.authorRef`.
8
+ *
9
+ * The adapter only implements text + image posts (the 95% case for
10
+ * editorial content). Article / video / multi-image carousels are
11
+ * out of scope for the v1 ship and require the staged-upload
12
+ * endpoint; deliberately deferred.
13
+ */
14
+ import type { SocialAdapter } from '../adapter.js';
15
+ import type { SocialPlatformConstraints } from '../types.js';
16
+ export declare const LINKEDIN_CONSTRAINTS: SocialPlatformConstraints;
17
+ export interface LinkedInAdapterConfig {
18
+ /** Override the API host — for testing against `wiremock` etc. */
19
+ baseUrl?: string;
20
+ /** Per-platform constraint overrides (e.g. stricter byline rules). */
21
+ constraints?: Partial<SocialPlatformConstraints>;
22
+ }
23
+ /**
24
+ * Construct a LinkedIn adapter. Constraints can be overridden for
25
+ * tenants on premium plans / sandbox endpoints.
26
+ */
27
+ export declare function createLinkedInAdapter(config?: LinkedInAdapterConfig): SocialAdapter;
28
+ //# sourceMappingURL=linkedin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkedin.d.ts","sourceRoot":"","sources":["../../src/adapters/linkedin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAIV,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAMrB,eAAO,MAAM,oBAAoB,EAAE,yBAUlC,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,GAAE,qBAA0B,GACjC,aAAa,CAyDf"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * LinkedIn adapter — UGC Posts API (v2).
3
+ *
4
+ * Endpoint: `POST https://api.linkedin.com/v2/ugcPosts`
5
+ * Auth: OAuth 2.0 bearer token in `Authorization: Bearer …`
6
+ * Author: `urn:li:organization:<id>` or `urn:li:person:<id>`
7
+ * — caller supplies via `credentials.authorRef`.
8
+ *
9
+ * The adapter only implements text + image posts (the 95% case for
10
+ * editorial content). Article / video / multi-image carousels are
11
+ * out of scope for the v1 ship and require the staged-upload
12
+ * endpoint; deliberately deferred.
13
+ */
14
+ import { vendorFetch } from '../http.js';
15
+ import { validatePost } from '../validation.js';
16
+ const LINKEDIN_API_BASE = 'https://api.linkedin.com';
17
+ export const LINKEDIN_CONSTRAINTS = {
18
+ maxBodyChars: 3000,
19
+ minBodyChars: 1,
20
+ minHashtags: 2,
21
+ maxHashtags: 5,
22
+ allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
23
+ maxMediaCount: 9,
24
+ allowedAspectRatios: ['1:1', '4:3', '16:9', '4:5'],
25
+ maxMediaSizeBytes: 20 * 1024 * 1024, // 20MB per LinkedIn docs
26
+ supportsFirstComment: true,
27
+ };
28
+ /**
29
+ * Construct a LinkedIn adapter. Constraints can be overridden for
30
+ * tenants on premium plans / sandbox endpoints.
31
+ */
32
+ export function createLinkedInAdapter(config = {}) {
33
+ const baseUrl = (config.baseUrl ?? LINKEDIN_API_BASE).replace(/\/+$/, '');
34
+ const constraints = {
35
+ ...LINKEDIN_CONSTRAINTS,
36
+ ...config.constraints,
37
+ };
38
+ return {
39
+ key: 'linkedin',
40
+ displayName: 'LinkedIn',
41
+ constraints,
42
+ validate(post) {
43
+ return validatePost(post, constraints);
44
+ },
45
+ async publish(post, credentials) {
46
+ const body = buildUgcPost(post, credentials);
47
+ const headers = {
48
+ Authorization: `Bearer ${credentials.accessToken}`,
49
+ 'Content-Type': 'application/json',
50
+ 'X-Restli-Protocol-Version': '2.0.0',
51
+ };
52
+ if (post.idempotencyKey) {
53
+ // LinkedIn doesn't have a native Idempotency-Key header for
54
+ // UGC Posts; the value travels via the `originToken` field
55
+ // on the post body which LinkedIn dedupes against.
56
+ // (Implemented in buildUgcPost.)
57
+ }
58
+ const res = await vendorFetch('linkedin', `${baseUrl}/v2/ugcPosts`, {
59
+ method: 'POST',
60
+ headers,
61
+ body: JSON.stringify(body),
62
+ });
63
+ const json = res.body;
64
+ const externalRef = json?.id ?? res.headers.get('x-linkedin-id') ?? '';
65
+ if (!externalRef) {
66
+ // LinkedIn always returns 201 with an id; if we didn't get
67
+ // one, the post probably WASN'T accepted — fail loud.
68
+ throw new Error('[social/linkedin] Publish returned no id; vendor body: ' + res.bodyText.slice(0, 200));
69
+ }
70
+ return {
71
+ externalRef,
72
+ // The public URL form is `https://www.linkedin.com/feed/update/<urn>`.
73
+ publishedUrl: `https://www.linkedin.com/feed/update/${encodeURIComponent(externalRef)}`,
74
+ publishedAt: new Date().toISOString(),
75
+ idempotencyKey: post.idempotencyKey,
76
+ correlationId: post.correlationId,
77
+ raw: json,
78
+ };
79
+ },
80
+ };
81
+ }
82
+ function buildUgcPost(post, credentials) {
83
+ const media = post.media ?? [];
84
+ const hasMedia = media.length > 0;
85
+ const hasArticleLink = !hasMedia && !!post.link;
86
+ const body = {
87
+ author: credentials.authorRef,
88
+ lifecycleState: 'PUBLISHED',
89
+ specificContent: {
90
+ 'com.linkedin.ugc.ShareContent': {
91
+ shareCommentary: { text: post.body },
92
+ shareMediaCategory: hasMedia ? 'IMAGE' : hasArticleLink ? 'ARTICLE' : 'NONE',
93
+ },
94
+ },
95
+ visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
96
+ };
97
+ if (post.idempotencyKey) {
98
+ body.originToken = post.idempotencyKey;
99
+ }
100
+ if (hasMedia) {
101
+ body.specificContent['com.linkedin.ugc.ShareContent'].media = media.map((m) => ({
102
+ status: 'READY',
103
+ ...(m.altText ? { description: { text: m.altText } } : {}),
104
+ // LinkedIn expects an image URN (`urn:li:digitalmediaAsset:…`),
105
+ // not a public URL. For v1 we assume the caller has already
106
+ // staged the upload + put the URN in `media.url`. Caller-side
107
+ // staging is documented in the README.
108
+ media: m.url,
109
+ }));
110
+ }
111
+ else if (hasArticleLink) {
112
+ body.specificContent['com.linkedin.ugc.ShareContent'].media = [
113
+ {
114
+ status: 'READY',
115
+ originalUrl: post.link,
116
+ },
117
+ ];
118
+ }
119
+ return body;
120
+ }
121
+ //# sourceMappingURL=linkedin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkedin.js","sourceRoot":"","sources":["../../src/adapters/linkedin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * X (formerly Twitter) adapter — v2 Tweets API.
3
+ *
4
+ * Endpoint: `POST https://api.twitter.com/2/tweets`
5
+ * Auth: OAuth 2.0 user-context bearer token
6
+ *
7
+ * Media uploads go through the v1.1 `media/upload.json` endpoint
8
+ * which is a separate pre-upload step; v1 of this adapter assumes
9
+ * the caller has already uploaded media and stashes the resulting
10
+ * `media_id` in `media.url` (we treat `url` as a generic "platform
11
+ * media reference" for every adapter).
12
+ */
13
+ import type { SocialAdapter } from '../adapter.js';
14
+ import type { SocialPlatformConstraints } from '../types.js';
15
+ export declare const X_CONSTRAINTS: SocialPlatformConstraints;
16
+ export interface XAdapterConfig {
17
+ baseUrl?: string;
18
+ constraints?: Partial<SocialPlatformConstraints>;
19
+ }
20
+ export declare function createXAdapter(config?: XAdapterConfig): SocialAdapter;
21
+ //# sourceMappingURL=x.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x.d.ts","sourceRoot":"","sources":["../../src/adapters/x.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAIV,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAMrB,eAAO,MAAM,aAAa,EAAE,yBAU3B,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAClD;AAED,wBAAgB,cAAc,CAAC,MAAM,GAAE,cAAmB,GAAG,aAAa,CAiDzE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * X (formerly Twitter) adapter — v2 Tweets API.
3
+ *
4
+ * Endpoint: `POST https://api.twitter.com/2/tweets`
5
+ * Auth: OAuth 2.0 user-context bearer token
6
+ *
7
+ * Media uploads go through the v1.1 `media/upload.json` endpoint
8
+ * which is a separate pre-upload step; v1 of this adapter assumes
9
+ * the caller has already uploaded media and stashes the resulting
10
+ * `media_id` in `media.url` (we treat `url` as a generic "platform
11
+ * media reference" for every adapter).
12
+ */
13
+ import { vendorFetch } from '../http.js';
14
+ import { validatePost } from '../validation.js';
15
+ const X_API_BASE = 'https://api.twitter.com';
16
+ export const X_CONSTRAINTS = {
17
+ maxBodyChars: 280,
18
+ minBodyChars: 1,
19
+ minHashtags: 1,
20
+ maxHashtags: 3,
21
+ allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
22
+ maxMediaCount: 4,
23
+ allowedAspectRatios: ['16:9', '4:3', '1:1'],
24
+ maxMediaSizeBytes: 5 * 1024 * 1024,
25
+ supportsFirstComment: true,
26
+ };
27
+ export function createXAdapter(config = {}) {
28
+ const baseUrl = (config.baseUrl ?? X_API_BASE).replace(/\/+$/, '');
29
+ const constraints = {
30
+ ...X_CONSTRAINTS,
31
+ ...config.constraints,
32
+ };
33
+ return {
34
+ key: 'x',
35
+ displayName: 'X',
36
+ constraints,
37
+ validate(post) {
38
+ return validatePost(post, constraints);
39
+ },
40
+ async publish(post, credentials) {
41
+ const body = buildTweetBody(post);
42
+ const res = await vendorFetch('x', `${baseUrl}/2/tweets`, {
43
+ method: 'POST',
44
+ headers: {
45
+ Authorization: `Bearer ${credentials.accessToken}`,
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ body: JSON.stringify(body),
49
+ });
50
+ const json = res.body;
51
+ const externalRef = json?.data?.id;
52
+ if (!externalRef) {
53
+ throw new Error('[social/x] Publish returned no tweet id; vendor body: ' + res.bodyText.slice(0, 200));
54
+ }
55
+ // X's public URL form requires the username, which we don't
56
+ // necessarily have at publish time. Use the canonical `i/web`
57
+ // form which works for any tweet and lets the browser redirect.
58
+ return {
59
+ externalRef,
60
+ publishedUrl: `https://x.com/i/web/status/${encodeURIComponent(externalRef)}`,
61
+ publishedAt: new Date().toISOString(),
62
+ idempotencyKey: post.idempotencyKey,
63
+ correlationId: post.correlationId,
64
+ raw: json,
65
+ };
66
+ },
67
+ };
68
+ }
69
+ function buildTweetBody(post) {
70
+ const body = { text: post.body };
71
+ const media = post.media ?? [];
72
+ if (media.length > 0) {
73
+ // For X, `media.url` carries the v1.1 `media_id_string` value
74
+ // produced by the upload step (the adapter doesn't perform that
75
+ // upload — apps stage media before calling publish).
76
+ body.media = { media_ids: media.map((m) => m.url) };
77
+ }
78
+ return body;
79
+ }
80
+ //# sourceMappingURL=x.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x.js","sourceRoot":"","sources":["../../src/adapters/x.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,UAAU,GAAG,yBAAyB,CAAC;AAE7C,MAAM,CAAC,MAAM,aAAa,GAA8B;IACtD,YAAY,EAAE,GAAG;IACjB,YAAY,EAAE,CAAC;IACf,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,CAAC;IACd,gBAAgB,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,CAAC;IACxE,aAAa,EAAE,CAAC;IAChB,mBAAmB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;IAC3C,iBAAiB,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAClC,oBAAoB,EAAE,IAAI;CAC3B,CAAC;AAOF,MAAM,UAAU,cAAc,CAAC,SAAyB,EAAE;IACxD,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,UAAU,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACnE,MAAM,WAAW,GAA8B;QAC7C,GAAG,aAAa;QAChB,GAAG,MAAM,CAAC,WAAW;KACtB,CAAC;IAEF,OAAO;QACL,GAAG,EAAE,GAAG;QACR,WAAW,EAAE,GAAG;QAChB,WAAW;QAEX,QAAQ,CAAC,IAAgB;YACvB,OAAO,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACzC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW;YAC7B,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YAElC,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,WAAW,EAAE;gBACxD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE;oBAClD,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,GAAG,CAAC,IAAwD,CAAC;YAC1E,MAAM,WAAW,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;YACnC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CACb,wDAAwD,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CACtF,CAAC;YACJ,CAAC;YAED,4DAA4D;YAC5D,8DAA8D;YAC9D,gEAAgE;YAChE,OAAO;gBACL,WAAW;gBACX,YAAY,EAAE,8BAA8B,kBAAkB,CAAC,WAAW,CAAC,EAAE;gBAC7E,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACrC,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,GAAG,EAAE,IAAI;aACV,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAQD,SAAS,cAAc,CAAC,IAAgB;IACtC,MAAM,IAAI,GAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,8DAA8D;QAC9D,gEAAgE;QAChE,qDAAqD;QACrD,IAAI,CAAC,KAAK,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Tiny fetch wrapper shared by every adapter.
3
+ *
4
+ * Centralizes:
5
+ * - AbortController-backed timeout (default 30s, override per call)
6
+ * - Vendor-status → typed-error mapping (401/403 → SocialAuthError,
7
+ * 429 → SocialRateLimitError, 4xx → SocialValidationError,
8
+ * 5xx → SocialPublishError)
9
+ * - Retry-After parsing for rate-limit responses
10
+ *
11
+ * Adapter-level retry / fallback orchestration is the caller's
12
+ * concern (the CMS wraps publish() in its `withJob` retry logic, and
13
+ * apps can reuse `@venturekit-pro/ai`'s `retryWithBackoff` if they
14
+ * want exponential jitter — these are not LLM-specific).
15
+ */
16
+ import { type SocialPlatformKey } from './types.js';
17
+ export interface FetchOptions {
18
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
19
+ headers?: Record<string, string>;
20
+ body?: string | URLSearchParams;
21
+ timeoutMs?: number;
22
+ /** Override the abort signal — e.g. when the caller propagates its own. */
23
+ signal?: AbortSignal;
24
+ }
25
+ export interface FetchResult {
26
+ status: number;
27
+ headers: Headers;
28
+ body: unknown;
29
+ bodyText: string;
30
+ }
31
+ /**
32
+ * POST/PUT/GET with vendor-status mapping. Returns the parsed body
33
+ * on success; throws a typed error on failure.
34
+ *
35
+ * `body` is forwarded verbatim — caller is responsible for setting
36
+ * the right Content-Type header (`application/json` is the package
37
+ * default when one isn't supplied).
38
+ */
39
+ export declare function vendorFetch(platform: SocialPlatformKey, url: string, opts?: FetchOptions): Promise<FetchResult>;
40
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,iBAAiB,EAC3B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,CAAC,CA+FtB"}
package/dist/http.js ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Tiny fetch wrapper shared by every adapter.
3
+ *
4
+ * Centralizes:
5
+ * - AbortController-backed timeout (default 30s, override per call)
6
+ * - Vendor-status → typed-error mapping (401/403 → SocialAuthError,
7
+ * 429 → SocialRateLimitError, 4xx → SocialValidationError,
8
+ * 5xx → SocialPublishError)
9
+ * - Retry-After parsing for rate-limit responses
10
+ *
11
+ * Adapter-level retry / fallback orchestration is the caller's
12
+ * concern (the CMS wraps publish() in its `withJob` retry logic, and
13
+ * apps can reuse `@venturekit-pro/ai`'s `retryWithBackoff` if they
14
+ * want exponential jitter — these are not LLM-specific).
15
+ */
16
+ import { SocialAuthError, SocialPublishError, SocialRateLimitError, SocialValidationError, } from './types.js';
17
+ /**
18
+ * POST/PUT/GET with vendor-status mapping. Returns the parsed body
19
+ * on success; throws a typed error on failure.
20
+ *
21
+ * `body` is forwarded verbatim — caller is responsible for setting
22
+ * the right Content-Type header (`application/json` is the package
23
+ * default when one isn't supplied).
24
+ */
25
+ export async function vendorFetch(platform, url, opts = {}) {
26
+ const controller = new AbortController();
27
+ const timeoutMs = opts.timeoutMs ?? 30_000;
28
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
29
+ // If the caller passed their own signal, forward both.
30
+ if (opts.signal) {
31
+ if (opts.signal.aborted) {
32
+ clearTimeout(timer);
33
+ throw new SocialPublishError('Aborted before request', { platform });
34
+ }
35
+ opts.signal.addEventListener('abort', () => controller.abort(), { once: true });
36
+ }
37
+ let response;
38
+ try {
39
+ response = await fetch(url, {
40
+ method: opts.method ?? 'GET',
41
+ headers: {
42
+ Accept: 'application/json',
43
+ ...(opts.body && !('Content-Type' in (opts.headers ?? {}))
44
+ ? { 'Content-Type': 'application/json' }
45
+ : {}),
46
+ ...(opts.headers ?? {}),
47
+ },
48
+ body: opts.body,
49
+ signal: controller.signal,
50
+ });
51
+ }
52
+ catch (err) {
53
+ clearTimeout(timer);
54
+ if (err.name === 'AbortError') {
55
+ throw new SocialPublishError(`Request to ${url} timed out after ${timeoutMs}ms`, {
56
+ platform,
57
+ });
58
+ }
59
+ throw new SocialPublishError(`Network error talking to ${url}: ${err.message}`, { platform });
60
+ }
61
+ finally {
62
+ clearTimeout(timer);
63
+ }
64
+ const bodyText = await response.text();
65
+ let parsedBody;
66
+ try {
67
+ parsedBody = bodyText ? JSON.parse(bodyText) : null;
68
+ }
69
+ catch {
70
+ parsedBody = bodyText;
71
+ }
72
+ if (response.ok) {
73
+ return {
74
+ status: response.status,
75
+ headers: response.headers,
76
+ body: parsedBody,
77
+ bodyText,
78
+ };
79
+ }
80
+ // ─── Map vendor status → typed error ────────────────────────────
81
+ const truncated = bodyText.length > 1024 ? bodyText.slice(0, 1024) + '…' : bodyText;
82
+ const message = `${response.status} ${response.statusText}: ${truncated}`;
83
+ if (response.status === 401 || response.status === 403) {
84
+ throw new SocialAuthError(message, {
85
+ platform,
86
+ status: response.status,
87
+ raw: parsedBody,
88
+ });
89
+ }
90
+ if (response.status === 429) {
91
+ const retryAfter = parseRetryAfter(response.headers.get('Retry-After'));
92
+ throw new SocialRateLimitError(message, {
93
+ platform,
94
+ status: response.status,
95
+ raw: parsedBody,
96
+ retryAfterSeconds: retryAfter,
97
+ });
98
+ }
99
+ if (response.status >= 400 && response.status < 500) {
100
+ throw new SocialValidationError(message, {
101
+ platform,
102
+ status: response.status,
103
+ raw: parsedBody,
104
+ });
105
+ }
106
+ throw new SocialPublishError(message, {
107
+ platform,
108
+ status: response.status,
109
+ raw: parsedBody,
110
+ });
111
+ }
112
+ function parseRetryAfter(header) {
113
+ if (!header)
114
+ return undefined;
115
+ // Numeric form: seconds to wait.
116
+ const n = Number(header);
117
+ if (Number.isFinite(n) && n >= 0)
118
+ return n;
119
+ // HTTP-date form: difference from now.
120
+ const t = Date.parse(header);
121
+ if (Number.isFinite(t)) {
122
+ const seconds = Math.max(0, Math.ceil((t - Date.now()) / 1000));
123
+ return seconds;
124
+ }
125
+ return undefined;
126
+ }
127
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @venturekit-pro/social
3
+ *
4
+ * Generic social-platform publishing adapters + tenant catalog.
5
+ *
6
+ * - `SocialAdapter` — adapter contract (validate + publish + optional verify/parseWebhook).
7
+ * - `createAdapterRegistry` — lookup table for the consumer to populate.
8
+ * - `validatePost` — generic constraint validator reused by every adapter.
9
+ * - `createLinkedInAdapter` /
10
+ * `createXAdapter` /
11
+ * `createFacebookAdapter` /
12
+ * `createInstagramAdapter` — v1 adapters.
13
+ * - `getSocialMigrationsDir` — path to `vk_social_*.sql` for `vk migrate`.
14
+ *
15
+ * Documentation: https://venturekit.dev
16
+ */
17
+ export type { SocialPlatformKey, SocialPost, SocialMedia, SocialPlatformConstraints, SocialValidationCode, SocialValidationIssue, SocialValidationResult, OAuthCredentials, PublishResult, VerifyResult, WebhookEvent, } from './types.js';
18
+ export { SocialPublishError, SocialAuthError, SocialRateLimitError, SocialValidationError, } from './types.js';
19
+ export type { SocialAdapter } from './adapter.js';
20
+ export type { AdapterRegistry } from './registry.js';
21
+ export { createAdapterRegistry } from './registry.js';
22
+ export { validatePost, simplifyRatio } from './validation.js';
23
+ export { vendorFetch } from './http.js';
24
+ export type { FetchOptions, FetchResult } from './http.js';
25
+ export { createLinkedInAdapter, LINKEDIN_CONSTRAINTS, createXAdapter, X_CONSTRAINTS, createFacebookAdapter, FACEBOOK_CONSTRAINTS, createInstagramAdapter, INSTAGRAM_CONSTRAINTS, } from './adapters/index.js';
26
+ export type { LinkedInAdapterConfig, XAdapterConfig, FacebookAdapterConfig, InstagramAdapterConfig, } from './adapters/index.js';
27
+ export { getSocialMigrationsDir } from './path.js';
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,YAAY,EACV,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG3D,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @venturekit-pro/social
3
+ *
4
+ * Generic social-platform publishing adapters + tenant catalog.
5
+ *
6
+ * - `SocialAdapter` — adapter contract (validate + publish + optional verify/parseWebhook).
7
+ * - `createAdapterRegistry` — lookup table for the consumer to populate.
8
+ * - `validatePost` — generic constraint validator reused by every adapter.
9
+ * - `createLinkedInAdapter` /
10
+ * `createXAdapter` /
11
+ * `createFacebookAdapter` /
12
+ * `createInstagramAdapter` — v1 adapters.
13
+ * - `getSocialMigrationsDir` — path to `vk_social_*.sql` for `vk migrate`.
14
+ *
15
+ * Documentation: https://venturekit.dev
16
+ */
17
+ export { SocialPublishError, SocialAuthError, SocialRateLimitError, SocialValidationError, } from './types.js';
18
+ export { createAdapterRegistry } from './registry.js';
19
+ // ─── Validation helpers ──────────────────────────────────────────────
20
+ export { validatePost, simplifyRatio } from './validation.js';
21
+ // ─── HTTP helper (exposed for adapter authors building their own) ───
22
+ export { vendorFetch } from './http.js';
23
+ // ─── V1 adapters ─────────────────────────────────────────────────────
24
+ export { createLinkedInAdapter, LINKEDIN_CONSTRAINTS, createXAdapter, X_CONSTRAINTS, createFacebookAdapter, FACEBOOK_CONSTRAINTS, createInstagramAdapter, INSTAGRAM_CONSTRAINTS, } from './adapters/index.js';
25
+ // ─── Migrations wiring ───────────────────────────────────────────────
26
+ export { getSocialMigrationsDir } from './path.js';
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAiBH,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,wEAAwE;AACxE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,uEAAuE;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,wEAAwE;AACxE,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAQ7B,wEAAwE;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}