@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.
- package/LICENSE +191 -0
- package/README.md +134 -0
- package/dist/adapter.d.ts +46 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +15 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/facebook.d.ts +25 -0
- package/dist/adapters/facebook.d.ts.map +1 -0
- package/dist/adapters/facebook.js +95 -0
- package/dist/adapters/facebook.js.map +1 -0
- package/dist/adapters/index.d.ts +23 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +19 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/instagram.d.ts +25 -0
- package/dist/adapters/instagram.d.ts.map +1 -0
- package/dist/adapters/instagram.js +113 -0
- package/dist/adapters/instagram.js.map +1 -0
- package/dist/adapters/linkedin.d.ts +28 -0
- package/dist/adapters/linkedin.d.ts.map +1 -0
- package/dist/adapters/linkedin.js +121 -0
- package/dist/adapters/linkedin.js.map +1 -0
- package/dist/adapters/x.d.ts +21 -0
- package/dist/adapters/x.d.ts.map +1 -0
- package/dist/adapters/x.js +80 -0
- package/dist/adapters/x.js.map +1 -0
- package/dist/http.d.ts +40 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +127 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/vk_social_0001_init.sql +121 -0
- package/dist/path.d.ts +9 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +17 -0
- package/dist/path.js.map +1 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +42 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +57 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +17 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +157 -0
- package/dist/validation.js.map +1 -0
- package/package.json +78 -0
- 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":"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"}
|
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
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,GAEtB,MAAM,YAAY,CAAC;AAkBpB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAA2B,EAC3B,GAAW,EACX,OAAqB,EAAE;IAEvB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;IAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAE9D,uDAAuD;IACvD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;YAC5B,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;oBACxD,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE;oBACxC,CAAC,CAAC,EAAE,CAAC;gBACP,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;aACxB;YACD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,IAAK,GAAyB,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACrD,MAAM,IAAI,kBAAkB,CAAC,cAAc,GAAG,oBAAoB,SAAS,IAAI,EAAE;gBAC/E,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,kBAAkB,CAC1B,4BAA4B,GAAG,KAAM,GAAa,CAAC,OAAO,EAAE,EAC5D,EAAE,QAAQ,EAAE,CACb,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,UAAmB,CAAC;IACxB,IAAI,CAAC;QACH,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,GAAG,QAAQ,CAAC;IACxB,CAAC;IAED,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,IAAI,EAAE,UAAU;YAChB,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,mEAAmE;IACnE,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpF,MAAM,OAAO,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;IAE1E,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,eAAe,CAAC,OAAO,EAAE;YACjC,QAAQ;YACR,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,GAAG,EAAE,UAAU;SAChB,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;QACxE,MAAM,IAAI,oBAAoB,CAAC,OAAO,EAAE;YACtC,QAAQ;YACR,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,GAAG,EAAE,UAAU;YACf,iBAAiB,EAAE,UAAU;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACpD,MAAM,IAAI,qBAAqB,CAAC,OAAO,EAAE;YACvC,QAAQ;YACR,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,GAAG,EAAE,UAAU;SAChB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,kBAAkB,CAAC,OAAO,EAAE;QACpC,QAAQ;QACR,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,GAAG,EAAE,UAAU;KAChB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,MAAqB;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,iCAAiC;IACjC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,uCAAuC;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAChE,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|