twforge
v0.1.0
Published
Zero-dependency TypeScript client for the X (Twitter) API v2.
Maintainers
Readme
twforge
A zero-dependency TypeScript client for the X (Twitter) API v2.
A thin hand-written transport core plus a fully typed surface generated from the official OpenAPI spec, built entirely on Node-native primitives (fetch, FormData, Blob, node:crypto, node:fs). No runtime dependencies.
- 100% of the X v2 API — every operation and schema is generated from
api.x.com/2/openapi.json(currently 165 operations, 446 schemas). - OAuth 1.0a, Bearer, and OAuth2 user tokens — OAuth 1.0a HMAC-SHA1 signing (verified against Twitter's documented example) is required for posting and media upload.
- Chunked media upload —
uploadMedia()runs INITIALIZE → APPEND → FINALIZE and waits out async video/GIF processing. - Typed, with an escape hatch — call
client.createPosts(...)etc. with full types, orclient.callOperation(name, params)for anything new.
Install
npm install twforgeQuick start
import { XClient, InputFile } from "twforge";
const client = new XClient({
credentials: {
appKey: process.env.X_CONSUMER_KEY!,
appSecret: process.env.X_CONSUMER_SECRET!,
accessToken: process.env.X_ACCESS_TOKEN!,
accessSecret: process.env.X_ACCESS_TOKEN_SECRET!,
},
});
// Text tweet
await client.postTweet("Hello from twforge!");
// Tweet with media (image or video)
const mediaId = await client.uploadMedia(
InputFile.fromPath("./photo.jpg"),
{ mediaType: "image/jpeg", mediaCategory: "tweet_image" },
);
await client.postTweet("With a photo 📸", { mediaIds: [mediaId] });
// Any typed operation directly
const tweet = await client.getPostsById({
id: "20",
"tweet.fields": ["created_at", "author_id"],
});Auth options
// App-only Bearer (read-only endpoints)
new XClient({ credentials: { bearerToken: process.env.X_BEARER! } });
// OAuth 2.0 user access token
new XClient({ credentials: { oauth2Token: userAccessToken } });API shape
XClient extends XApi, which exposes one typed method per operationId
(createPosts, getPostsById, appendMediaUpload, ...). Each method takes a
single params object merging path params, query params, and the request body;
the client splits them per the generated OPERATIONS table when building the
request.
Errors:
XApiError— non-2xx response, with.status,.problem,.errors, and.retryAfter(for 429s).XNetworkError— transport-level failure (network/timeout/non-JSON).
Development
npm run gen:fetch # vendor the latest OpenAPI spec into spec/openapi.min.json
npm run gen # regenerate src/generated/{types,methods}.ts
npm run typecheck
npm test
npm run build # dual ESM + CJS output in dist/The transport core (src/core/) and high-level client (src/client.ts) are
hand-written; everything in src/generated/ is produced by scripts/generate.ts
and must not be edited by hand.
License
MIT
