diff --git a/async-polling.mdx b/async-polling.mdx new file mode 100644 index 0000000..d9d2df2 --- /dev/null +++ b/async-polling.mdx @@ -0,0 +1,177 @@ +--- +title: 'Async Polling' +description: 'How graph generation jobs work and how to poll for results' +icon: 'arrows-rotate' +--- + +All graph generation endpoints are **asynchronous**. When you submit a request, the API creates a background job and returns immediately with a job status. You then poll for results by re-submitting the same request with the same `Idempotency-Key`. + +## How It Works + +```mermaid +sequenceDiagram + participant Client + participant API + + Client->>API: POST /v1/graphs/dependency (file + Idempotency-Key) + API-->>Client: 202 Accepted {status: "pending", retryAfter: 10} + Note over Client: Wait retryAfter seconds + Client->>API: POST /v1/graphs/dependency (same file + same key) + API-->>Client: 202 Accepted {status: "processing", retryAfter: 10} + Note over Client: Wait retryAfter seconds + Client->>API: POST /v1/graphs/dependency (same file + same key) + API-->>Client: 200 OK {status: "completed", result: {graph: {...}}} +``` + +## Job Statuses + +| Status | HTTP Code | Description | +|--------|-----------|-------------| +| `pending` | 202 | Job is queued, waiting to be processed | +| `processing` | 202 | Job is actively being analyzed | +| `completed` | 200 | Job finished successfully, `result` field contains the graph | +| `failed` | 200 | Job encountered an error, `error` field contains the message | + +## Response Envelope + +All graph endpoints return a consistent envelope: + +```json +{ + "status": "pending | processing | completed | failed", + "jobId": "unique-job-identifier", + "retryAfter": 10, + "result": { ... }, + "error": "error message if failed" +} +``` + +- **`status`** - Current job state +- **`jobId`** - Unique identifier for the job +- **`retryAfter`** - Recommended seconds to wait before the next poll (only present for pending/processing) +- **`result`** - The graph data (only present when completed) +- **`error`** - Error description (only present when failed) + +## Polling with cURL + +Store the `Idempotency-Key` in a variable and re-use it for polling: + +```bash +IDEMPOTENCY_KEY=$(uuidgen) + +# Submit the job +curl --request POST \ + --url https://api.supermodeltools.com/v1/graphs/dependency \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ + --header 'X-Api-Key: ' \ + --header 'Content-Type: multipart/form-data' \ + --form file='@repo.zip' + +# Poll until completed (re-submit the same request) +curl --request POST \ + --url https://api.supermodeltools.com/v1/graphs/dependency \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ + --header 'X-Api-Key: ' \ + --header 'Content-Type: multipart/form-data' \ + --form file='@repo.zip' +``` + + + The server uses the `Idempotency-Key` to identify your existing job. Re-submitting the file does not create a duplicate job. + + +## Using the SDK + +The `@supermodeltools/sdk` package handles polling automatically. Install it with: + +```bash +npm install @supermodeltools/sdk +``` + +### Basic Usage + +```typescript +import { Configuration, DefaultApi, SupermodelClient } from '@supermodeltools/sdk'; +import * as fs from 'fs'; + +const config = new Configuration({ + basePath: 'https://api.supermodeltools.com', + apiKey: 'smsk_live_...', +}); + +const api = new DefaultApi(config); +const client = new SupermodelClient(api); + +// Read your zip file +const zipBuffer = fs.readFileSync('repo.zip'); +const file = new Blob([zipBuffer], { type: 'application/zip' }); + +// This handles polling internally and returns the completed result +const result = await client.generateDependencyGraph(file); +console.log(result.graph.nodes); +``` + +### Configuring Polling Behavior + +```typescript +const controller = new AbortController(); + +const client = new SupermodelClient(api, { + timeoutMs: 600000, // Max wait time: 10 minutes (default: 5 minutes) + defaultRetryIntervalMs: 3000, // Poll interval if server doesn't specify (default: 5s) + maxPollingAttempts: 120, // Max number of polls (default: 60) + onPollingProgress: (progress) => { + console.log(`Attempt ${progress.attempt}/${progress.maxAttempts} - ${progress.status}`); + }, + signal: controller.signal, // AbortSignal for cancellation +}); + +// To cancel polling at any point: +// controller.abort(); +``` + +### Available Methods + +| Method | Endpoint | +|--------|----------| +| `client.generateDependencyGraph(file)` | `/v1/graphs/dependency` | +| `client.generateCallGraph(file)` | `/v1/graphs/call` | +| `client.generateDomainGraph(file)` | `/v1/graphs/domain` | +| `client.generateParseGraph(file)` | `/v1/graphs/parse` | +| `client.generateSupermodelGraph(file)` | `/v1/graphs/supermodel` | + +## Error Handling + +If a job fails, the response will have `status: "failed"` with an `error` message: + +```json +{ + "status": "failed", + "jobId": "550e8400-e29b-41d4-a716-446655440000", + "error": "Nested archives are not supported" +} +``` + +Common failure reasons: + +| Error | Resolution | +|-------|------------| +| Nested archives | Exclude `.zip`/`.tar` files from your archive using `.gitattributes` with `export-ignore` | +| File exceeds size limits | Exclude large binary files from the archive | +| Blob expired | Job waited too long in the queue; retry with a new idempotency key | + + + Jobs have a limited processing window. If a job stays in `pending` status too long, the uploaded file may expire and the job will be marked as `failed`. + + +## Idempotency Key Behavior + +The `Idempotency-Key` scopes a job to your API key. Key behaviors: + +- **Same key, same user**: Returns the existing job (no duplicate processing) +- **Same key, different user**: Creates independent jobs (no conflict) +- **New key, same file**: Creates a new job (useful for re-analysis after code changes) + + + Completed jobs are retained for 24 hours. After that, submitting the same idempotency key will create a new job. + diff --git a/authentication.mdx b/authentication.mdx index 1bff73e..b380940 100644 --- a/authentication.mdx +++ b/authentication.mdx @@ -20,10 +20,11 @@ X-Api-Key: smsk_live_... ## Idempotency Key -All API requests require an `Idempotency-Key` header. This is a unique value (such as a UUID) that you generate for each request. It serves two purposes: +All API requests require an `Idempotency-Key` header. This is a unique value (such as a UUID) that you generate for each request. It serves three purposes: -1. **Safe retries**: If a request fails due to a network issue, you can safely retry it with the same idempotency key without risking duplicate operations. -2. **Request tracing**: The key is echoed back in the `X-Request-Id` response header for debugging and support purposes. +1. **Job identity**: The key identifies your graph generation job. Re-submitting the same key returns the existing job status rather than creating a duplicate. +2. **Polling**: You poll for results by re-submitting the same request with the same idempotency key. See [Async Polling](/async-polling) for details. +3. **Request tracing**: The key is echoed back in the `X-Request-Id` response header for debugging and support purposes. ```bash Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 diff --git a/concepts.mdx b/concepts.mdx index b083d9b..dc7b74e 100644 --- a/concepts.mdx +++ b/concepts.mdx @@ -6,6 +6,8 @@ icon: 'layer-group' Supermodel provides different "lenses" to view your codebase through. Each API endpoint generates a specific type of graph suited for different analysis tasks. +All graph generation is **asynchronous** — the API accepts your request, processes it in the background, and you poll for results. See [Async Polling](/async-polling) for details. + ## Dependency Graph **Endpoint:** `/v1/graphs/dependency` diff --git a/docs.json b/docs.json index 0f3ef09..8fc3943 100644 --- a/docs.json +++ b/docs.json @@ -30,6 +30,7 @@ "index", "quickstart", "authentication", + "async-polling", "concepts" ] } diff --git a/quickstart.mdx b/quickstart.mdx index 0c9aa0c..0c05f41 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -21,53 +21,85 @@ The Supermodel API accepts code as a zipped archive. Navigate to your project fo zip -r repo.zip . -x ".*" -x "**/.*" -x "node_modules/*" ``` -## Step 2: Generate a graph +## Step 2: Submit a graph generation job -Use the `dependency` endpoint to generate a graph of file-level dependencies. Replace `` with your actual key. The `Idempotency-Key` should be a unique value (like a UUID) for each request. +Use the `dependency` endpoint to submit a graph generation job. Replace `` with your actual key. The `Idempotency-Key` should be a unique value (like a UUID) for each request. ```bash +IDEMPOTENCY_KEY=$(uuidgen) + curl --request POST \ --url https://api.supermodeltools.com/v1/graphs/dependency \ - --header "Idempotency-Key: $(uuidgen)" \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ --header 'X-Api-Key: ' \ --header 'Content-Type: multipart/form-data' \ --form file='@repo.zip' ``` +The API returns an **HTTP 202 Accepted** response with the job status: + +```json +{ + "status": "pending", + "jobId": "550e8400-e29b-41d4-a716-446655440000", + "retryAfter": 10 +} +``` + The `$(uuidgen)` command generates a unique ID automatically on macOS and Linux. On Windows, you can use `[guid]::NewGuid()` in PowerShell. -## Step 3: Interpret the response +## Step 3: Poll for results -You will receive a JSON response containing the graph nodes (files) and relationships (imports). +Graph generation is asynchronous. Poll by re-submitting the same request with the **same Idempotency-Key** until the job completes: + +```bash +# Re-submit the same request to check job status +curl --request POST \ + --url https://api.supermodeltools.com/v1/graphs/dependency \ + --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ + --header 'X-Api-Key: ' \ + --header 'Content-Type: multipart/form-data' \ + --form file='@repo.zip' +``` + +When the job completes, you receive an **HTTP 200** response with the graph inside the `result` field: ```json { - "graph": { - "nodes": [ - { - "id": "src/main.ts", - "labels": ["File"], - "properties": { "name": "main.ts" } - }, - { - "id": "src/utils.ts", - "labels": ["File"], - "properties": { "name": "utils.ts" } - } - ], - "relationships": [ - { - "type": "imports", - "startNode": "src/main.ts", - "endNode": "src/utils.ts" - } - ] + "status": "completed", + "jobId": "550e8400-e29b-41d4-a716-446655440000", + "result": { + "graph": { + "nodes": [ + { + "id": "src/main.ts", + "labels": ["File"], + "properties": { "name": "main.ts" } + }, + { + "id": "src/utils.ts", + "labels": ["File"], + "properties": { "name": "utils.ts" } + } + ], + "relationships": [ + { + "type": "imports", + "startNode": "src/main.ts", + "endNode": "src/utils.ts" + } + ] + } } } ``` + + **Using the SDK?** The `@supermodeltools/sdk` package handles polling automatically. See the [Async Polling](/async-polling) guide for details. + + ## Next Steps Explore other graph types to get different insights into your code: