diff --git a/.claude/rules/cross-sdk-parity.md b/.claude/rules/cross-sdk-parity.md new file mode 100644 index 00000000..03f7bc06 --- /dev/null +++ b/.claude/rules/cross-sdk-parity.md @@ -0,0 +1,48 @@ +# Cross-SDK Feature Parity + +All SDKs must implement the same operations. When adding a feature to one SDK, implement it in all others (or open tracking issues). + +## Required TurboSign Operations + +| Operation | JS | Py | Go | PHP | Java | +|---|---|---|---|---|---| +| configure | `configure()` | `configure()` | `Configure()` | `configure()` | `configure()` | +| createSignatureReviewLink | `createSignatureReviewLink()` | `create_signature_review_link()` | `CreateSignatureReviewLink()` | `createSignatureReviewLink()` | `createSignatureReviewLink()` | +| sendSignature | `sendSignature()` | `send_signature()` | `SendSignature()` | `sendSignature()` | `sendSignature()` | +| getStatus | `getStatus()` | `get_status()` | `GetStatus()` | `getStatus()` | `getStatus()` | +| download | `download()` | `download()` | `Download()` | `download()` | `download()` | +| void | `void()` | `void_document()` | `VoidDocument()` | `voidDocument()` | `voidDocument()` | +| resend | `resend()` | `resend_email()` | `ResendEmail()` | `resend()` | `resendEmail()` | +| getAuditTrail | `getAuditTrail()` | `get_audit_trail()` | `GetAuditTrail()` | `getAuditTrail()` | `getAuditTrail()` | + +## Required TurboPartner Operations + +- Organization CRUD: create, list, getDetails, update, delete +- Organization entitlements: updateEntitlements +- Organization users: list, add, update role, remove, resend invitation +- Organization API keys: list, create, update, revoke +- Partner API keys: list, create, update, revoke +- Partner users: list, add, update permissions, remove, resend invitation +- Audit logs: list with filtering + +## Naming Conventions by Language + +| Language | Methods | Classes | Files | Constants | +|---|---|---|---|---| +| JS/TS | camelCase | PascalCase | kebab-case | UPPER_SNAKE | +| Python | snake_case | PascalCase | snake_case | UPPER_SNAKE | +| Go | PascalCase (exported) | PascalCase | snake_case | PascalCase | +| PHP | camelCase | PascalCase | PascalCase | UPPER_SNAKE | +| Java | camelCase | PascalCase | PascalCase | UPPER_SNAKE | +| Ruby | snake_case | PascalCase | snake_case | UPPER_SNAKE | + +## New SDK Checklist + +1. Create `packages/-sdk/` directory +2. Implement TurboSign with all operations above +3. Implement TurboPartner with all operations above +4. Implement error hierarchy (TurboDocxError + 5 subtypes) +5. Write tests matching parity of existing SDKs +6. Add CI job to `.github/workflows/ci.yml` +7. Add publish workflow `.github/workflows/publish-.yml` +8. Create README with install, configure, and usage examples diff --git a/.claude/rules/js-sdk.md b/.claude/rules/js-sdk.md new file mode 100644 index 00000000..d4ca7229 --- /dev/null +++ b/.claude/rules/js-sdk.md @@ -0,0 +1,46 @@ +# JS/TS SDK Rules + +## Zero Runtime Dependencies + +The JS SDK has **no runtime dependencies** — only devDependencies (TypeScript, Jest, ts-jest, @types). +Use native `fetch` (Node 18+ global) for HTTP. Use native `fs` and `path` for file operations. +Never add runtime dependencies without explicit approval. + +## Static Class Pattern + +Both `TurboSign` and `TurboPartner` use static classes: +```typescript +TurboSign.configure({ apiKey, orgId, senderEmail }); +const result = await TurboSign.sendSignature({ ... }); +``` +No `new TurboSign()` — all methods are static. The `HttpClient` instance is held as a private static field. + +## TypeScript Configuration + +- **Strict mode**: `strict: true` in tsconfig +- **Target**: ES2020 +- **Module**: commonjs +- **Declaration files**: Generated in `dist/` +- All public APIs must have TypeScript types — no `any` in public interfaces + +## senderEmail Validation + +`senderEmail` is **required** for TurboSign (not for TurboPartner). The HttpClient throws `ValidationError` if missing unless `skipSenderValidation: true` (used internally by TurboPartner). + +## File Type Detection + +Use **magic bytes** to detect file types, not file extensions: +- PDF: bytes `0x25 0x50 0x44 0x46` (`%PDF`) +- DOCX: bytes `0x50 0x4B` (`PK`) + contains `word/` +- PPTX: bytes `0x50 0x4B` (`PK`) + contains `ppt/` + +## Response Unwrapping + +The `smartUnwrap` method strips the `{ data: ... }` wrapper when the response object has only a `data` key. This is automatic — module methods don't need to unwrap manually. + +## Testing + +- Framework: Jest + ts-jest +- Run: `npm test` (from `packages/js-sdk/`) +- Mock `global.fetch` — never make real HTTP calls in tests +- Test files live in `packages/js-sdk/tests/` diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 00000000..4482ed9b --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,36 @@ +# Testing Rules + +## TDD Workflow + +1. Write tests first that describe expected behavior +2. Run tests to confirm they fail +3. Implement the feature to make tests pass +4. Refactor while keeping tests green + +## Test Parity + +All SDKs must have equivalent test coverage. When adding a test case in one SDK, add the same scenario in all others. Tests should cover: +- Successful operations (happy path) +- Error handling (auth errors, validation, not found, rate limit) +- Configuration (explicit config, env var fallback, missing required fields) +- File input variants (buffer, path, URL, deliverableId) +- Sender config behavior (senderEmail required, senderName optional) + +## Per-SDK Test Commands + +| SDK | Command | Framework | +|---|---|---| +| js-sdk | `npm test` | Jest + ts-jest | +| py-sdk | `pytest -v` | pytest | +| go-sdk | `go test -v ./...` | go test | +| php-sdk | `composer test` | PHPUnit | +| java-sdk | `mvn test -B` | JUnit (Maven Surefire) | +| ruby-sdk | `bundle exec rspec` | RSpec | + +## Mock HTTP Responses, Not SDK Internals + +- Mock the HTTP layer (fetch/requests/http.Client), not SDK module methods +- Return realistic response shapes matching the actual API +- Test error mapping: mock HTTP 401 → verify AuthenticationError is thrown +- Never make real HTTP calls in unit tests +- Keep mocks minimal — only mock what's needed for the specific test case diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..e5e24f0e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,54 @@ +# TurboDocx SDK Monorepo + +Multi-language SDK monorepo for TurboDocx API (TurboSign digital signatures + TurboPartner portal management). + +## Directory Structure + +``` +packages/ +├── js-sdk/ # TypeScript/JavaScript (@turbodocx/sdk) +├── py-sdk/ # Python (turbodocx-sdk) +├── go-sdk/ # Go (github.com/TurboDocx/SDK/packages/go-sdk) +├── php-sdk/ # PHP (turbodocx/sdk) +├── java-sdk/ # Java (com.turbodocx:sdk) +└── ruby-sdk/ # Ruby (turbodocx-sdk) +.github/workflows/ # Per-SDK CI + publish workflows +``` + +## Build & Test Commands + +| SDK | Install | Build | Test | +|-----|---------|-------|------| +| js-sdk | `npm ci` | `npm run build` | `npm test` | +| py-sdk | `pip install -e ".[dev]"` | — | `pytest -v` | +| go-sdk | `go mod tidy` | — | `go test -v ./...` | +| php-sdk | `composer install` | — | `composer test` | +| java-sdk | — | `mvn compile` | `mvn test -B` | +| ruby-sdk | `bundle install` | — | `bundle exec rspec` | + +Root-level shortcuts (JS only): `npm run build:js`, `npm run test:js` + +## Commit Format + +``` +feat|fix|docs|test|refactor: description +[js-sdk] Add batch signing # PR title format +``` + +## Cross-SDK Conventions + +- All SDKs must maintain **feature parity** — see `.claude/rules/cross-sdk-parity.md` +- Two modules per SDK: **TurboSign** (signatures) and **TurboPartner** (partner portal) +- Follow each language's idiomatic naming (camelCase JS, snake_case Py, PascalCase Go) +- Shared error hierarchy: `TurboDocxError > Auth | Validation | NotFound | RateLimit | Network` +- Config pattern: explicit config → env var fallback → error + +## Architecture + +See @docs/ARCHITECTURE.md for HTTP client design, file input abstraction, error handling, and CI/CD details. + +## Rules + +- `.claude/rules/cross-sdk-parity.md` — feature parity requirements +- `.claude/rules/js-sdk.md` — JS/TS-specific conventions +- `.claude/rules/testing.md` — TDD workflow and test patterns diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..2caf46fd --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,99 @@ +# SDK Architecture + +## Module Design + +Each SDK exposes two top-level modules: + +- **TurboSign** — digital signature operations (create review links, send signatures, void, resend, download, get status, audit trail) +- **TurboPartner** — partner portal management (organizations, users, API keys, entitlements, audit logs) + +Modules use a **static class pattern**: configure once with `TurboSign.configure(config)`, then call static methods. No instantiation required. + +## HTTP Client + +Each SDK wraps a single `HttpClient` class responsible for: + +1. **Authentication**: Bearer token via `apiKey` or `accessToken`, sent in `Authorization` header. Org ID sent as `x-rapiddocx-org-id` header. +2. **Base URL**: Configurable, defaults to `https://api.turbodocx.com`. Env var fallback: `TURBODOCX_BASE_URL`. +3. **Response unwrapping**: Backend wraps responses in `{ "data": ... }`. The client auto-unwraps when the response has only a `data` key (smart unwrap). +4. **Error mapping**: HTTP status codes map to typed errors (400→Validation, 401→Auth, 404→NotFound, 429→RateLimit, other→TurboDocxError). +5. **File upload**: Multipart form upload with magic-byte file type detection. + +### Partner Client + +TurboPartner uses a separate config (`PartnerClientConfig`) with `partnerApiKey` (TDXP- prefix) and `partnerId`. It skips sender email validation since it doesn't send signature emails. + +## Configuration Pattern + +All SDKs follow: **explicit config > env var fallback > error**. + +| Config Field | Env Var | Required | +|---|---|---| +| `apiKey` | `TURBODOCX_API_KEY` | Yes (or accessToken) | +| `orgId` | `TURBODOCX_ORG_ID` | Yes for TurboSign | +| `senderEmail` | `TURBODOCX_SENDER_EMAIL` | Yes for TurboSign | +| `senderName` | `TURBODOCX_SENDER_NAME` | No (defaults to "API Service User") | +| `baseUrl` | `TURBODOCX_BASE_URL` | No (defaults to api.turbodocx.com) | + +## Error Hierarchy + +``` +TurboDocxError (base) +├── AuthenticationError (401) +├── ValidationError (400) +├── NotFoundError (404) +├── RateLimitError (429) +└── NetworkError (no status — fetch/connection failure) +``` + +All errors carry `statusCode` and `code` properties. Each SDK implements this hierarchy using language-appropriate patterns (classes in JS/Py/Java/PHP, error types in Go, exceptions in Ruby). + +## File Input Abstraction + +TurboSign methods accept documents via multiple input types: + +| Input | Description | +|---|---| +| `file` (Buffer/bytes) | Raw file content — type detected via magic bytes | +| `file` (path string) | Local file path — read and detect type | +| `file` (File object) | Browser File API object | +| `fileLink` (URL) | Remote file URL — server fetches it | +| `deliverableId` | TurboDocx-generated document ID | +| `templateId` | Pre-configured TurboSign template | + +When a `file` is provided, the request uses multipart form upload. Otherwise, JSON body is used. + +### Magic Byte Detection + +File types are detected from content, not extensions: +- `%PDF` (0x25504446) → PDF +- `PK` (0x504B) + `word/` → DOCX +- `PK` (0x504B) + `ppt/` → PPTX + +## Signature Field Positioning + +Fields use **coordinate-based positioning**: +- `page` — page number (1-indexed) +- `x`, `y` — position from top-left in points +- `width`, `height` — field dimensions in points +- `recipientEmail` — binds field to a specific signer +- `type` — field type (`signature`, `initials`, `date`, `text`, etc.) + +## Type/Model Organization + +Each SDK organizes types by module: +- `types/sign` — TurboSign request/response types (recipients, fields, document status) +- `types/partner` — TurboPartner request/response types (organizations, users, API keys, audit logs) + +## CI/CD + +### Test Workflows (`.github/workflows/ci.yml`) +Runs on push to `main`/`develop` and all PRs. Per-SDK jobs with language-specific setup: +- JS: Node 22, `npm ci && npm run build && npm test` +- Python: 3.9, `pip install -e ".[dev]" && pytest -v` +- Go: 1.21, `go mod tidy && go test -v ./...` +- PHP: 8.1, `composer install && composer test && composer phpstan` +- Java: JDK 11 (Temurin), `mvn test -B` + +### Publish Workflows +Separate workflow per SDK (`publish-{js,py,go,php,java}.yml`). Triggered on release or manual dispatch. Each publishes to the language's package registry.