@unito/integration-sdk
Version:
Integration SDK
81 lines (58 loc) • 5.11 kB
Markdown
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What This Is
`@unito/integration-sdk` — a TypeScript framework for building REST API integrations compatible with the Unito Integration API Specification. It wraps Express 5 and provides handler routing, middleware chains for request parsing, structured logging, a Provider HTTP client for downstream APIs, and standardized error handling.
## Commands
```bash
npm install # Install deps (also runs compile via prepare hook)
npm run compile # Build ESM (tsc), copy non-TS assets, then CJS (rollup)
npm run compile:watch # Watch mode recompile
npm test # Run all tests
ONLY=Handler npm test # Run tests matching pattern "Handler"
npm run test:debug # Run tests with --inspect-brk
npm run lint # ESLint --fix + Prettier --write on src/ and test/
```
Tests use **Node.js built-in test runner** (`node:test` + `node:assert/strict`) with `ts-node/esm` loader — no compilation needed to run tests. HTTP mocking uses `nock`.
## Architecture
### Request Flow
```
HTTP Request
→ finish (error/log hooks on response end)
→ express.json (1MB limit)
→ start (records hrtime)
→ correlationId (generates/extracts X-Correlation-Id)
→ logger (structured logger decorated with correlation ID)
→ credentials (decodes base64 JSON from X-Unito-Credentials header)
→ secrets (decodes base64 JSON from X-Unito-Secrets header)
→ filters / search / selects / relations (parse query params)
→ signal (AbortSignal from X-Unito-Operation-Deadline timestamp)
→ Handler routes (user-registered)
→ errors middleware (catches, formats HttpError responses)
→ notFound middleware (404 fallback)
```
### Core Classes
**Integration** (`src/integration.ts`) — entry point. Call `addHandler(path, handlers)` to register routes, then `start()`. All handlers must be added before `start()` — the server exits on violation.
**Handler** (`src/handler.ts`) — takes a path and a handlers object, generates an Express Router. Path determines routing:
- `/things/:thingId` — last segment is `:variable` → item route. `getItem` at `GET /things/:thingId`, `getCollection` at `GET /things`, `createItem` at `POST /things`, etc.
- `/things` — no trailing variable → either item-level or collection-level ops, but not both on the same path.
Handler types (pick one per `addHandler` call):
- `ItemHandlers` — getItem, getCollection, createItem, updateItem, deleteItem
- `BlobHandlers` — getBlob, createBlob
- `CredentialAccountHandlers` — getCredentialAccount
- `ParseWebhookHandlers`, `WebhookSubscriptionHandlers`, `AcknowledgeWebhookHandlers`
**Provider** (`src/resources/provider.ts`) — HTTP client for calling downstream provider APIs. Initialized with `prepareRequest` (returns base URL + auth headers), optional `rateLimiter`, and optional `customErrorHandler`. Methods: `get`, `post`, `put`, `patch`, `delete`, `streamingGet`, `postForm`, `postStream`, `putBuffer`.
**Context** (`src/resources/context.ts`) — typed context object passed to every handler. Contains `credentials`, `secrets`, `logger`, `signal`, `params`, `query`. GetCollectionContext additionally has `filters`, `search`, `selects`, `relations`. Create/Update contexts have `body`.
**HttpErrors** (`src/httpErrors.ts`) — throw these from handlers for proper error responses. BadRequestError(400), UnauthorizedError(401), ForbiddenError(403), NotFoundError(404), TimeoutError(408), UnprocessableEntityError(422), ProviderInstanceLockedError(423), RateLimitExceededError(429). The latter two support `retryAfter`.
### Logging
Logger (`src/resources/logger.ts`) produces Datadog-compatible structured JSON in production, colored output in dev. Keys are auto-converted to snake_case. Sensitive fields (access_token, password, email, etc.) are auto-redacted. Log size truncated at 20KB (configurable via `MAX_LOG_MESSAGE_SIZE` env var).
### Cache
`Cache.create(redisUrl?)` returns a Redis-backed or local in-memory cache via the `cachette` library.
## Code Conventions
- **ES Modules** — `"type": "module"` in package.json. All internal imports use `.js` extensions (`import X from './foo.js'`).
- **Dual output** — ESM via `tsc`, CJS via Rollup. Published with conditional exports.
- **Strict TypeScript** — strict mode, noUncheckedIndexedAccess, exactOptionalPropertyTypes, no unused locals/params.
- **Branded types** — e.g. `type Path = string & { __brand: 'Path' }` with assertion functions (`asserts path is Path`).
- **No barrel files** — import directly from source modules, not from index re-exports.
- **Prettier** — single quotes, trailing commas, 120 char width, no parens on single-param arrows.
- **Test files** — `test/**/*.test.ts` mirroring src structure. Use `describe`/`it`/`beforeEach`/`afterEach` from `node:test`, assertions from `node:assert/strict`, mocking with `mock.method()`.
- **Express typing** — `res.locals` is typed via global Express namespace augmentation.