From 2e29d564ab164c58a122ddb158e5abc2589463dd Mon Sep 17 00:00:00 2001 From: DocFast Bot Date: Fri, 20 Feb 2026 20:25:43 +0000 Subject: [PATCH] feat: add official Node.js and Python SDKs - Node.js SDK (sdk/nodejs/): TypeScript, zero deps, native fetch, Node 18+ - Python SDK (sdk/python/): sync + async clients via httpx, Python 3.8+ - Both wrap all conversion endpoints (html, markdown, url, templates) - Proper error handling with DocFastError - Full README documentation for each --- sdk/nodejs/README.md | 95 +++++++++++++++++++++ sdk/nodejs/package.json | 24 ++++++ sdk/nodejs/src/index.ts | 129 ++++++++++++++++++++++++++++ sdk/nodejs/tsconfig.json | 16 ++++ sdk/python/README.md | 103 +++++++++++++++++++++++ sdk/python/docfast/__init__.py | 6 ++ sdk/python/docfast/client.py | 148 +++++++++++++++++++++++++++++++++ sdk/python/docfast/py.typed | 0 sdk/python/pyproject.toml | 26 ++++++ 9 files changed, 547 insertions(+) create mode 100644 sdk/nodejs/README.md create mode 100644 sdk/nodejs/package.json create mode 100644 sdk/nodejs/src/index.ts create mode 100644 sdk/nodejs/tsconfig.json create mode 100644 sdk/python/README.md create mode 100644 sdk/python/docfast/__init__.py create mode 100644 sdk/python/docfast/client.py create mode 100644 sdk/python/docfast/py.typed create mode 100644 sdk/python/pyproject.toml diff --git a/sdk/nodejs/README.md b/sdk/nodejs/README.md new file mode 100644 index 0000000..61c7da5 --- /dev/null +++ b/sdk/nodejs/README.md @@ -0,0 +1,95 @@ +# DocFast Node.js SDK + +Official Node.js client for the [DocFast](https://docfast.dev) HTML-to-PDF API. + +## Install + +```bash +npm install docfast +``` + +Requires Node.js 18+ (uses native `fetch`). Zero runtime dependencies. + +## Quick Start + +```typescript +import DocFast from 'docfast'; + +const client = new DocFast('df_pro_your_api_key'); + +// HTML to PDF +const pdf = await client.html('

Hello World

'); +fs.writeFileSync('output.pdf', pdf); + +// Markdown to PDF +const pdf2 = await client.markdown('# Hello\n\nThis is **bold**.'); +fs.writeFileSync('doc.pdf', pdf2); + +// URL to PDF +const pdf3 = await client.url('https://example.com'); +fs.writeFileSync('page.pdf', pdf3); +``` + +## API + +### `new DocFast(apiKey, options?)` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | Your DocFast API key | +| `options.baseUrl` | `string` | API base URL (default: `https://docfast.dev`) | + +### `client.html(html, options?)` + +Convert an HTML string to PDF. Returns `Promise`. + +### `client.markdown(markdown, options?)` + +Convert a Markdown string to PDF. Returns `Promise`. + +### `client.url(url, options?)` + +Convert a webpage URL to PDF. Returns `Promise`. + +### `client.templates()` + +List available templates. Returns `Promise`. + +### `client.renderTemplate(id, data, options?)` + +Render a template with data. Returns `Promise`. + +### PDF Options + +All conversion methods accept an optional `options` object: + +```typescript +{ + format: 'A4' | 'Letter' | 'Legal' | 'A3' | 'A5' | 'Tabloid', + landscape: boolean, + margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' }, + header: { content: '
Header HTML
', height: '30mm' }, + footer: { content: '
Footer HTML
', height: '20mm' }, + scale: 1.0, + printBackground: true, +} +``` + +## Error Handling + +```typescript +import DocFast, { DocFastError } from 'docfast'; + +try { + const pdf = await client.html('

Test

'); +} catch (err) { + if (err instanceof DocFastError) { + console.error(err.message); // "Invalid API key" + console.error(err.status); // 403 + } +} +``` + +## License + +MIT diff --git a/sdk/nodejs/package.json b/sdk/nodejs/package.json new file mode 100644 index 0000000..4c5eb9c --- /dev/null +++ b/sdk/nodejs/package.json @@ -0,0 +1,24 @@ +{ + "name": "docfast", + "version": "0.1.0", + "description": "Official Node.js client for the DocFast HTML-to-PDF API", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist"], + "engines": { "node": ">=18.0.0" }, + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build" + }, + "keywords": ["pdf", "html-to-pdf", "markdown-to-pdf", "docfast", "api", "document"], + "author": "DocFast ", + "license": "MIT", + "homepage": "https://docfast.dev", + "repository": { + "type": "git", + "url": "https://git.cloonar.com/openclawd/docfast" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/sdk/nodejs/src/index.ts b/sdk/nodejs/src/index.ts new file mode 100644 index 0000000..5b2fd8d --- /dev/null +++ b/sdk/nodejs/src/index.ts @@ -0,0 +1,129 @@ +/** + * DocFast — Official Node.js SDK + * https://docfast.dev + */ + +export interface PdfMargin { + top?: string; + bottom?: string; + left?: string; + right?: string; +} + +export interface HeaderFooter { + content?: string; + height?: string; +} + +export interface PdfOptions { + format?: 'A4' | 'Letter' | 'Legal' | 'A3' | 'A5' | 'Tabloid'; + landscape?: boolean; + margin?: PdfMargin; + header?: HeaderFooter; + footer?: HeaderFooter; + scale?: number; + printBackground?: boolean; +} + +export interface Template { + id: string; + name: string; + description?: string; +} + +export interface DocFastOptions { + baseUrl?: string; +} + +export class DocFastError extends Error { + readonly status: number; + readonly code?: string; + + constructor(message: string, status: number, code?: string) { + super(message); + this.name = 'DocFastError'; + this.status = status; + this.code = code; + } +} + +export class DocFast { + private readonly apiKey: string; + private readonly baseUrl: string; + + constructor(apiKey: string, options?: DocFastOptions) { + if (!apiKey) throw new Error('API key is required'); + this.apiKey = apiKey; + this.baseUrl = options?.baseUrl?.replace(/\/+$/, '') ?? 'https://docfast.dev'; + } + + private async request(path: string, body: Record): Promise { + const res = await fetch(`${this.baseUrl}${path}`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + let message = `HTTP ${res.status}`; + let code: string | undefined; + try { + const err = await res.json() as { error?: string; code?: string }; + if (err.error) message = err.error; + code = err.code; + } catch {} + throw new DocFastError(message, res.status, code); + } + + const arrayBuffer = await res.arrayBuffer(); + return Buffer.from(arrayBuffer); + } + + private async requestJson(method: string, path: string): Promise { + const res = await fetch(`${this.baseUrl}${path}`, { + method, + headers: { 'Authorization': `Bearer ${this.apiKey}` }, + }); + + if (!res.ok) { + let message = `HTTP ${res.status}`; + try { + const err = await res.json() as { error?: string }; + if (err.error) message = err.error; + } catch {} + throw new DocFastError(message, res.status); + } + + return res.json() as Promise; + } + + /** Convert HTML to PDF */ + async html(html: string, options?: PdfOptions): Promise { + return this.request('/v1/convert/html', { html, options }); + } + + /** Convert Markdown to PDF */ + async markdown(markdown: string, options?: PdfOptions): Promise { + return this.request('/v1/convert/markdown', { markdown, options }); + } + + /** Convert a URL to PDF */ + async url(url: string, options?: PdfOptions): Promise { + return this.request('/v1/convert/url', { url, options }); + } + + /** List available templates */ + async templates(): Promise { + return this.requestJson('GET', '/v1/templates'); + } + + /** Render a template to PDF */ + async renderTemplate(id: string, data: Record, options?: PdfOptions): Promise { + return this.request(`/v1/templates/${encodeURIComponent(id)}/render`, { data, options }); + } +} + +export default DocFast; diff --git a/sdk/nodejs/tsconfig.json b/sdk/nodejs/tsconfig.json new file mode 100644 index 0000000..84f8181 --- /dev/null +++ b/sdk/nodejs/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/sdk/python/README.md b/sdk/python/README.md new file mode 100644 index 0000000..2bc9635 --- /dev/null +++ b/sdk/python/README.md @@ -0,0 +1,103 @@ +# DocFast Python SDK + +Official Python client for the [DocFast](https://docfast.dev) HTML-to-PDF API. + +## Install + +```bash +pip install docfast +``` + +Requires Python 3.8+. + +## Quick Start + +```python +from docfast import DocFast + +client = DocFast("df_pro_your_api_key") + +# HTML to PDF +pdf = client.html("

Hello World

") +with open("output.pdf", "wb") as f: + f.write(pdf) + +# Markdown to PDF +pdf = client.markdown("# Hello\n\nThis is **bold**.") + +# URL to PDF +pdf = client.url("https://example.com") +``` + +## Async Usage + +```python +from docfast import AsyncDocFast + +async with AsyncDocFast("df_pro_your_api_key") as client: + pdf = await client.html("

Hello

") +``` + +## API + +### `DocFast(api_key, *, base_url=None)` + +Create a synchronous client. Use as a context manager or call `client.close()`. + +### `AsyncDocFast(api_key, *, base_url=None)` + +Create an async client. Use as an async context manager. + +### Conversion Methods + +All methods return PDF bytes and accept optional keyword arguments: + +| Method | Input | Description | +|--------|-------|-------------| +| `client.html(html, **opts)` | HTML string | Convert HTML to PDF | +| `client.markdown(markdown, **opts)` | Markdown string | Convert Markdown to PDF | +| `client.url(url, **opts)` | URL string | Convert webpage to PDF | +| `client.templates()` | — | List available templates | +| `client.render_template(id, data, **opts)` | Template ID + data dict | Render template to PDF | + +### PDF Options + +Pass as keyword arguments to any conversion method: + +```python +pdf = client.html( + "

Report

", + format="A4", + landscape=True, + margin={"top": "20mm", "bottom": "20mm"}, + print_background=True, +) +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `format` | str | `"A4"` | Page size: A4, Letter, Legal, A3, A5, Tabloid | +| `landscape` | bool | `False` | Landscape orientation | +| `margin` | dict | — | `{top, bottom, left, right}` in CSS units | +| `header` | dict | — | `{content, height}` for page header | +| `footer` | dict | — | `{content, height}` for page footer | +| `scale` | float | `1.0` | Render scale | +| `print_background` | bool | `False` | Include background colors/images | + +## Error Handling + +```python +from docfast import DocFast, DocFastError + +client = DocFast("df_pro_your_api_key") + +try: + pdf = client.html("

Test

") +except DocFastError as e: + print(e) # "Invalid API key" + print(e.status) # 403 +``` + +## License + +MIT diff --git a/sdk/python/docfast/__init__.py b/sdk/python/docfast/__init__.py new file mode 100644 index 0000000..db58c10 --- /dev/null +++ b/sdk/python/docfast/__init__.py @@ -0,0 +1,6 @@ +"""DocFast — Official Python SDK for the HTML-to-PDF API.""" + +from .client import DocFast, AsyncDocFast, DocFastError + +__all__ = ["DocFast", "AsyncDocFast", "DocFastError"] +__version__ = "0.1.0" diff --git a/sdk/python/docfast/client.py b/sdk/python/docfast/client.py new file mode 100644 index 0000000..6a12cf8 --- /dev/null +++ b/sdk/python/docfast/client.py @@ -0,0 +1,148 @@ +"""DocFast API clients (sync and async).""" + +from __future__ import annotations + +from typing import Any, Dict, List, Optional +from urllib.parse import quote + +import httpx + + +class DocFastError(Exception): + """Error returned by the DocFast API.""" + + def __init__(self, message: str, status: int, code: Optional[str] = None): + super().__init__(message) + self.status = status + self.code = code + + +_KEY_MAP = {"print_background": "printBackground"} + + +def _build_body(key: str, value: str, options: Optional[Dict[str, Any]]) -> Dict[str, Any]: + body: Dict[str, Any] = {key: value} + if options: + body["options"] = {_KEY_MAP.get(k, k): v for k, v in options.items()} + return body + + +def _handle_error(response: httpx.Response) -> None: + if response.is_success: + return + message = f"HTTP {response.status_code}" + code = None + try: + data = response.json() + if "error" in data: + message = data["error"] + code = data.get("code") + except Exception: + pass + raise DocFastError(message, response.status_code, code) + + +class DocFast: + """Synchronous DocFast client.""" + + def __init__(self, api_key: str, *, base_url: Optional[str] = None): + if not api_key: + raise ValueError("API key is required") + self._base_url = (base_url or "https://docfast.dev").rstrip("/") + self._client = httpx.Client( + base_url=self._base_url, + headers={"Authorization": f"Bearer {api_key}"}, + timeout=120.0, + ) + + def __enter__(self) -> "DocFast": + return self + + def __exit__(self, *args: Any) -> None: + self.close() + + def close(self) -> None: + self._client.close() + + def _convert(self, path: str, body: Dict[str, Any]) -> bytes: + r = self._client.post(path, json=body) + _handle_error(r) + return r.content + + def html(self, html: str, **options: Any) -> bytes: + """Convert HTML to PDF.""" + return self._convert("/v1/convert/html", _build_body("html", html, options or None)) + + def markdown(self, markdown: str, **options: Any) -> bytes: + """Convert Markdown to PDF.""" + return self._convert("/v1/convert/markdown", _build_body("markdown", markdown, options or None)) + + def url(self, url: str, **options: Any) -> bytes: + """Convert a URL to PDF.""" + return self._convert("/v1/convert/url", _build_body("url", url, options or None)) + + def templates(self) -> List[Dict[str, Any]]: + """List available templates.""" + r = self._client.get("/v1/templates") + _handle_error(r) + return r.json() + + def render_template(self, template_id: str, data: Dict[str, Any], **options: Any) -> bytes: + """Render a template to PDF.""" + body: Dict[str, Any] = {"data": data} + if options: + body["options"] = options + return self._convert(f"/v1/templates/{quote(template_id, safe='')}/render", body) + + +class AsyncDocFast: + """Asynchronous DocFast client.""" + + def __init__(self, api_key: str, *, base_url: Optional[str] = None): + if not api_key: + raise ValueError("API key is required") + self._base_url = (base_url or "https://docfast.dev").rstrip("/") + self._client = httpx.AsyncClient( + base_url=self._base_url, + headers={"Authorization": f"Bearer {api_key}"}, + timeout=120.0, + ) + + async def __aenter__(self) -> "AsyncDocFast": + return self + + async def __aexit__(self, *args: Any) -> None: + await self.close() + + async def close(self) -> None: + await self._client.aclose() + + async def _convert(self, path: str, body: Dict[str, Any]) -> bytes: + r = await self._client.post(path, json=body) + _handle_error(r) + return r.content + + async def html(self, html: str, **options: Any) -> bytes: + """Convert HTML to PDF.""" + return await self._convert("/v1/convert/html", _build_body("html", html, options or None)) + + async def markdown(self, markdown: str, **options: Any) -> bytes: + """Convert Markdown to PDF.""" + return await self._convert("/v1/convert/markdown", _build_body("markdown", markdown, options or None)) + + async def url(self, url: str, **options: Any) -> bytes: + """Convert a URL to PDF.""" + return await self._convert("/v1/convert/url", _build_body("url", url, options or None)) + + async def templates(self) -> List[Dict[str, Any]]: + """List available templates.""" + r = await self._client.get("/v1/templates") + _handle_error(r) + return r.json() + + async def render_template(self, template_id: str, data: Dict[str, Any], **options: Any) -> bytes: + """Render a template to PDF.""" + body: Dict[str, Any] = {"data": data} + if options: + body["options"] = options + return await self._convert(f"/v1/templates/{quote(template_id, safe='')}/render", body) diff --git a/sdk/python/docfast/py.typed b/sdk/python/docfast/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml new file mode 100644 index 0000000..9e7f4ae --- /dev/null +++ b/sdk/python/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "docfast" +version = "0.1.0" +description = "Official Python client for the DocFast HTML-to-PDF API" +readme = "README.md" +license = "MIT" +requires-python = ">=3.8" +authors = [{ name = "DocFast", email = "support@docfast.dev" }] +keywords = ["pdf", "html-to-pdf", "markdown-to-pdf", "docfast", "api"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries", +] +dependencies = ["httpx>=0.24.0"] + +[project.urls] +Homepage = "https://docfast.dev" +Documentation = "https://docfast.dev/docs" +Repository = "https://git.cloonar.com/openclawd/docfast"