@docyrus/docyrus 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +25 -2
  2. package/main.js +1028 -540
  3. package/main.js.map +4 -4
  4. package/package.json +2 -1
  5. package/resources/pi-agent/prompts/agent-system.md +25 -0
  6. package/resources/pi-agent/prompts/coder-append-system.md +19 -0
  7. package/resources/pi-agent/skills/docyrus-ai/SKILL.md +28 -0
  8. package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +161 -0
  9. package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +349 -0
  10. package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +238 -0
  11. package/resources/pi-agent/skills/docyrus-api-dev/references/data-source-query-guide.md +2059 -0
  12. package/resources/pi-agent/skills/docyrus-api-dev/references/formula-design-guide-llm.md +320 -0
  13. package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +592 -0
  14. package/resources/pi-agent/skills/docyrus-api-doctor/SKILL.md +70 -0
  15. package/resources/pi-agent/skills/docyrus-api-doctor/references/checklist-details.md +588 -0
  16. package/resources/pi-agent/skills/docyrus-app-dev/SKILL.md +159 -0
  17. package/resources/pi-agent/skills/docyrus-app-dev/references/api-client-and-auth.md +275 -0
  18. package/resources/pi-agent/skills/docyrus-app-dev/references/collections-and-patterns.md +352 -0
  19. package/resources/pi-agent/skills/docyrus-app-dev/references/data-source-query-guide.md +2059 -0
  20. package/resources/pi-agent/skills/docyrus-app-dev/references/formula-design-guide-llm.md +320 -0
  21. package/resources/pi-agent/skills/docyrus-app-dev/references/query-guide.md +525 -0
  22. package/resources/pi-agent/skills/docyrus-app-ui-design/SKILL.md +466 -0
  23. package/resources/pi-agent/skills/docyrus-app-ui-design/references/component-selection-guide.md +602 -0
  24. package/resources/pi-agent/skills/docyrus-app-ui-design/references/icon-usage-guide.md +463 -0
  25. package/resources/pi-agent/skills/docyrus-app-ui-design/references/preferred-components-catalog.md +242 -0
  26. package/resources/pi-agent/skills/docyrus-apps/SKILL.md +54 -0
  27. package/resources/pi-agent/skills/docyrus-architect/SKILL.md +174 -0
  28. package/resources/pi-agent/skills/docyrus-architect/references/custom-query-guide.md +410 -0
  29. package/resources/pi-agent/skills/docyrus-architect/references/data-source-query-guide.md +2059 -0
  30. package/resources/pi-agent/skills/docyrus-architect/references/formula-design-guide-llm.md +320 -0
  31. package/resources/pi-agent/skills/docyrus-architect/references/formula-reference.md +145 -0
  32. package/resources/pi-agent/skills/docyrus-auth/SKILL.md +100 -0
  33. package/resources/pi-agent/skills/docyrus-cli-app/SKILL.md +279 -0
  34. package/resources/pi-agent/skills/docyrus-cli-app/references/cli-manifest.md +532 -0
  35. package/resources/pi-agent/skills/docyrus-cli-app/references/list-query-examples.md +248 -0
  36. package/resources/pi-agent/skills/docyrus-curl/SKILL.md +32 -0
  37. package/resources/pi-agent/skills/docyrus-discover/SKILL.md +63 -0
  38. package/resources/pi-agent/skills/docyrus-ds/SKILL.md +95 -0
  39. package/resources/pi-agent/skills/docyrus-env/SKILL.md +21 -0
  40. package/resources/pi-agent/skills/docyrus-studio/SKILL.md +369 -0
  41. package/resources/pi-agent/skills/docyrus-tui/SKILL.md +15 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docyrus/docyrus",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "private": false,
5
5
  "description": "Docyrus API CLI",
6
6
  "main": "./main.js",
@@ -8,6 +8,7 @@
8
8
  "docyrus": "main.js"
9
9
  },
10
10
  "dependencies": {
11
+ "@mariozechner/pi-coding-agent": "0.58.4",
11
12
  "@opentui/core": "^0.1.85",
12
13
  "@opentui/react": "^0.1.85",
13
14
  "incur": "^0.1.6",
@@ -0,0 +1,25 @@
1
+ You are the Docyrus assistant running inside the local `docyrus` CLI.
2
+
3
+ Your primary domain is Docyrus. Unless the user clearly asks about something else, assume the task is about the Docyrus platform, the Docyrus CLI, tenant data, app/data-source schema management, API discovery, or development workflows around Docyrus-backed systems.
4
+
5
+ Core behavior:
6
+
7
+ - Prefer the local `docyrus` CLI for Docyrus operations instead of guessing undocumented HTTP endpoints.
8
+ - When you need structured command output, prefer `--json`.
9
+ - Start by inspecting real tenant state before making claims about apps, data sources, users, environments, auth state, or API shape.
10
+ - Use the installed Docyrus skills as your command and workflow reference.
11
+ - Be careful with tenant-scoped mutations. Confirm real identifiers and current context before changing state.
12
+ - Treat auth files, tokens, and `.docyrus` contents as sensitive.
13
+ - When the user asks for code changes, hand off to the coding-capable profile only if the current toolset is insufficient.
14
+
15
+ Docyrus concepts you should understand and use accurately:
16
+
17
+ - environments such as live, beta, alpha, and dev
18
+ - user and tenant auth context
19
+ - apps
20
+ - data sources and records
21
+ - studio schema management for data sources, fields, and enums
22
+ - discover commands and tenant OpenAPI inspection
23
+ - raw API access via the CLI when needed
24
+
25
+ You are not a generic shell assistant first. You are a Docyrus-first operator that happens to have local tools.
@@ -0,0 +1,19 @@
1
+ Docyrus is the primary domain for this session.
2
+
3
+ Treat most user requests as Docyrus-related unless the prompt clearly says otherwise. When the task involves the Docyrus platform, prefer using the local `docyrus` CLI to inspect or mutate real state instead of guessing API details. Use `--json` whenever command output needs to be parsed or compared.
4
+
5
+ When working on code:
6
+
7
+ - Use the repository files and coding tools for implementation work.
8
+ - Use the Docyrus CLI for platform state, tenant context, API discovery, schema inspection, and operational verification.
9
+ - Respect the current Docyrus scope and auth context provided by the runtime skill.
10
+ - Treat `.docyrus` and all auth material as sensitive.
11
+
12
+ Useful Docyrus areas to reason about precisely:
13
+
14
+ - auth accounts and tenant switching
15
+ - apps
16
+ - data sources and records
17
+ - studio schema operations
18
+ - discover/OpenAPI inspection
19
+ - curl/raw API checks through the CLI
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: docyrus-ai
3
+ description: Chat with a Docyrus AI agent. Run `docyrus ai --help` for usage details.
4
+ command: docyrus ai
5
+ ---
6
+
7
+ # docyrus ai
8
+
9
+ Chat with a Docyrus AI agent
10
+
11
+ ## Arguments
12
+
13
+ | Name | Type | Required | Description |
14
+ |------|------|----------|-------------|
15
+ | `prompt` | `string` | yes | Prompt string; quote it when it contains spaces |
16
+
17
+ ## Environment Variables
18
+
19
+ | Name | Type | Required | Default | Description |
20
+ |------|------|----------|---------|-------------|
21
+ | `DOCYRUS_API_CLIENT_ID` | `string` | no | | Default Docyrus OAuth2 client id |
22
+
23
+ ## Options
24
+
25
+ | Flag | Type | Default | Description |
26
+ |------|------|---------|-------------|
27
+ | `--agentId` | `string` | | Agent ID; defaults to the Docyrus CLI agent |
28
+ | `--deploymentId` | `string` | | Optional agent deployment ID |
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: docyrus-api-dev
3
+ description: Develop applications using the Docyrus API with @docyrus/api-client and @docyrus/signin libraries. Use when building apps that authenticate with Docyrus OAuth2 (PKCE, iframe, client credentials, device code), make REST API calls to Docyrus data source endpoints, or construct query payloads with filters, aggregations, formulas, pivots, and child queries. Triggers on tasks involving Docyrus API integration, @docyrus/api-client usage, @docyrus/signin authentication, data source query building, or Docyrus REST endpoint consumption.
4
+ ---
5
+
6
+ # Docyrus API Developer
7
+
8
+ Integrate with the Docyrus API using `@docyrus/api-client` (REST client) and `@docyrus/signin` (React auth provider). Authenticate via OAuth2 PKCE, query data sources with powerful filtering/aggregation, and consume REST endpoints.
9
+
10
+ ## Authentication Quick Start
11
+
12
+ ### React Apps — Use @docyrus/signin
13
+
14
+ ```tsx
15
+ import { DocyrusAuthProvider, useDocyrusAuth, useDocyrusClient, SignInButton } from '@docyrus/signin'
16
+
17
+ // 1. Wrap root
18
+ <DocyrusAuthProvider
19
+ apiUrl={import.meta.env.VITE_API_BASE_URL}
20
+ clientId={import.meta.env.VITE_OAUTH2_CLIENT_ID}
21
+ redirectUri={import.meta.env.VITE_OAUTH2_REDIRECT_URI}
22
+ scopes={['offline_access', 'Read.All', 'DS.ReadWrite.All', 'Users.Read']}
23
+ callbackPath="/auth/callback"
24
+ >
25
+ <App />
26
+ </DocyrusAuthProvider>
27
+
28
+ // 2. Use hooks
29
+ function App() {
30
+ const { status, signOut } = useDocyrusAuth()
31
+ const client = useDocyrusClient() // RestApiClient | null
32
+
33
+ if (status === 'loading') return <Spinner />
34
+ if (status === 'unauthenticated') return <SignInButton />
35
+
36
+ // client is ready — make API calls
37
+ const user = await client!.get('/v1/users/me')
38
+ }
39
+ ```
40
+
41
+ ### Non-React / Server — Use OAuth2Client Directly
42
+
43
+ ```typescript
44
+ import { RestApiClient, OAuth2Client, OAuth2TokenManagerAdapter, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
45
+
46
+ const tokenStorage = new BrowserOAuth2TokenStorage(localStorage)
47
+ const oauth2 = new OAuth2Client({
48
+ baseURL: 'https://api.docyrus.com',
49
+ clientId: 'your-client-id',
50
+ redirectUri: 'http://localhost:3000/callback',
51
+ usePKCE: true,
52
+ tokenStorage,
53
+ })
54
+
55
+ // Auth Code flow
56
+ const { url } = await oauth2.getAuthorizationUrl({ scope: 'openid offline_access Users.Read' })
57
+ window.location.href = url
58
+ // After redirect:
59
+ const tokens = await oauth2.handleCallback(window.location.href)
60
+
61
+ // Create API client with auto-refresh
62
+ const client = new RestApiClient({
63
+ baseURL: 'https://api.docyrus.com',
64
+ tokenManager: new OAuth2TokenManagerAdapter(tokenStorage, async () => {
65
+ return (await oauth2.refreshAccessToken()).accessToken
66
+ }),
67
+ })
68
+ ```
69
+
70
+ ## API Endpoints
71
+
72
+ ### Data Source Items (Dynamic per tenant)
73
+ ```
74
+ GET /v1/apps/{appSlug}/data-sources/{slug}/items — List with query payload
75
+ GET /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Get one
76
+ POST /v1/apps/{appSlug}/data-sources/{slug}/items — Create
77
+ PATCH /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Update
78
+ DELETE /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Delete one
79
+ DELETE /v1/apps/{appSlug}/data-sources/{slug}/items — Delete many (body: { recordIds })
80
+ ```
81
+
82
+ Endpoints exist only if the data source is defined in the tenant. Check the tenant's OpenAPI spec at `GET /v1/api/openapi.json`.
83
+
84
+ ### System Endpoints (Always Available)
85
+ ```
86
+ GET /v1/users — List users
87
+ POST /v1/users — Create user
88
+ GET /v1/users/me — Current user profile
89
+ PATCH /v1/users/me — Update current user
90
+ ```
91
+
92
+ ### Making API Calls
93
+
94
+ ```typescript
95
+ // List items with query payload
96
+ const items = await client.get('/v1/apps/base/data-sources/project/items', {
97
+ columns: 'name, status, record_owner(firstname,lastname)',
98
+ filters: { rules: [{ field: 'status', operator: '!=', value: 'archived' }] },
99
+ orderBy: 'created_on DESC',
100
+ limit: 50,
101
+ })
102
+
103
+ // Get single item
104
+ const item = await client.get('/v1/apps/base/data-sources/project/items/uuid-here', {
105
+ columns: 'name, description, status',
106
+ })
107
+
108
+ // Create
109
+ const newItem = await client.post('/v1/apps/base/data-sources/project/items', {
110
+ name: 'New Project',
111
+ status: 'status-enum-id',
112
+ })
113
+
114
+ // Update
115
+ await client.patch('/v1/apps/base/data-sources/project/items/uuid-here', {
116
+ name: 'Updated Name',
117
+ })
118
+
119
+ // Delete
120
+ await client.delete('/v1/apps/base/data-sources/project/items/uuid-here')
121
+ ```
122
+
123
+ ## Query Payload Summary
124
+
125
+ The GET items endpoint accepts a powerful query payload:
126
+
127
+ | Feature | Purpose |
128
+ |---------|---------|
129
+ | `columns` | Select fields, expand relations `field(subfields)`, alias `alias:field`, spread `...field()` |
130
+ | `filters` | Nested AND/OR groups with 50+ operators (comparison, date shortcuts, user-related) |
131
+ | `filterKeyword` | Full-text search across all searchable fields |
132
+ | `orderBy` | Sort by fields with direction, including related fields |
133
+ | `limit`/`offset` | Pagination (default limit: 100) |
134
+ | `fullCount` | Return total matching count alongside results |
135
+ | `calculations` | Aggregations: count, sum, avg, min, max with grouping |
136
+ | `formulas` | Computed virtual columns (simple functions, block AST, correlated subqueries) |
137
+ | `childQueries` | Fetch related child records as nested JSON arrays |
138
+ | `pivot` | Cross-tab matrix queries with date range series |
139
+ | `expand` | Return full objects for relation/user/enum fields instead of IDs |
140
+
141
+ **For full query and formula references, read**:
142
+ - `references/data-source-query-guide.md`
143
+ - `references/formula-design-guide-llm.md`
144
+
145
+ ## Critical Rules
146
+
147
+ 1. **Always send `columns`** in list/get calls. Without it, only `id` is returned.
148
+ 2. **Data source endpoints are dynamic** — they exist only for data sources defined in the tenant.
149
+ 3. **Use `id` field** for `count` calculations. Use the actual field slug for `sum`, `avg`, `min`, `max`.
150
+ 4. **Child query keys must appear in `columns`** — if childQuery key is `orders`, include `orders` in columns.
151
+ 5. **Formula keys must appear in `columns`** — if formula key is `total`, include `total` in columns.
152
+ 6. **Filter by related field** using `rel_{{relation_field}}/{{field}}` syntax.
153
+
154
+ ## References
155
+
156
+ Read these files when you need detailed information:
157
+
158
+ - **`references/api-client.md`** — Full RestApiClient API, OAuth2Client (all flows: PKCE, client credentials, device code), token managers, interceptors, error classes, SSE/streaming, file upload/download, HTML to PDF, retry logic
159
+ - **`references/authentication.md`** — @docyrus/signin React provider, useDocyrusAuth/useDocyrusClient hooks, SignInButton, standalone vs iframe auth modes, env vars, API client access pattern
160
+ - **`references/data-source-query-guide.md`** — Up-to-date query payload guide: columns, filters, orderBy, pagination, calculations, formulas, child queries, pivots, and operator reference
161
+ - **`references/formula-design-guide-llm.md`** — Up-to-date formula design guide for building and validating `formulas` payloads
@@ -0,0 +1,349 @@
1
+ # @docyrus/api-client Reference
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [RestApiClient](#restapiclient)
6
+ 2. [HTTP Methods](#http-methods)
7
+ 3. [Configuration](#configuration)
8
+ 4. [Token Management](#token-management)
9
+ 5. [OAuth2Client](#oauth2client)
10
+ 6. [Interceptors](#interceptors)
11
+ 7. [Error Handling](#error-handling)
12
+ 8. [Streaming](#streaming)
13
+ 9. [File Operations](#file-operations)
14
+ 10. [Utilities](#utilities)
15
+
16
+ ---
17
+
18
+ ## RestApiClient
19
+
20
+ ```typescript
21
+ import { RestApiClient, MemoryTokenManager } from '@docyrus/api-client'
22
+
23
+ const client = new RestApiClient({
24
+ baseURL: 'https://api.docyrus.com',
25
+ tokenManager: new MemoryTokenManager(),
26
+ timeout: 5000,
27
+ headers: { 'X-API-Version': '1.0' },
28
+ })
29
+ ```
30
+
31
+ ---
32
+
33
+ ## HTTP Methods
34
+
35
+ ```typescript
36
+ // GET with query params
37
+ const users = await client.get<User[]>('/v1/users', { params: { page: 1, limit: 10 } })
38
+
39
+ // POST with body
40
+ const newUser = await client.post<User>('/v1/users', { name: 'John', email: 'john@example.com' })
41
+
42
+ // PATCH (partial update)
43
+ const updated = await client.patch<User>('/v1/users/123', { name: 'Jane' })
44
+
45
+ // PUT (full replace)
46
+ await client.put('/v1/users/123', { name: 'Jane', email: 'jane@example.com' })
47
+
48
+ // DELETE
49
+ await client.delete('/v1/users/123')
50
+
51
+ // DELETE with body
52
+ await client.delete('/v1/items', { recordIds: ['id1', 'id2'] })
53
+ ```
54
+
55
+ ### Typed Responses
56
+
57
+ ```typescript
58
+ interface ApiResponse<T> { data: T; meta: { page: number; total: number } }
59
+ const response = await client.get<ApiResponse<User[]>>('/v1/users')
60
+ const users: User[] = response.data.data
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Configuration
66
+
67
+ ```typescript
68
+ interface ApiClientConfig {
69
+ baseURL?: string // Base URL for all requests
70
+ tokenManager?: TokenManager // Token manager instance
71
+ headers?: Record<string, string> // Default headers
72
+ timeout?: number // Request timeout in ms
73
+ fetch?: typeof fetch // Custom fetch implementation
74
+ FormData?: typeof FormData // Custom FormData
75
+ AbortController?: typeof AbortController
76
+ storage?: Storage // Browser storage for persistence
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Token Management
83
+
84
+ ### MemoryTokenManager (default)
85
+ ```typescript
86
+ import { MemoryTokenManager } from '@docyrus/api-client'
87
+ const tokenManager = new MemoryTokenManager()
88
+ ```
89
+
90
+ ### StorageTokenManager (persistent)
91
+ ```typescript
92
+ import { StorageTokenManager } from '@docyrus/api-client'
93
+ const tokenManager = new StorageTokenManager(localStorage, 'auth_token')
94
+ ```
95
+
96
+ ### AsyncTokenManager (custom)
97
+ ```typescript
98
+ import { AsyncTokenManager } from '@docyrus/api-client'
99
+ const tokenManager = new AsyncTokenManager({
100
+ async getToken() { return await secureStorage.get('token') },
101
+ async setToken(token) { await secureStorage.set('token', token) },
102
+ async clearToken() { await secureStorage.remove('token') },
103
+ })
104
+ ```
105
+
106
+ ### Set Token Directly
107
+ ```typescript
108
+ await client.setAccessToken('your-auth-token')
109
+ ```
110
+
111
+ ---
112
+
113
+ ## OAuth2Client
114
+
115
+ Full OAuth2 support with PKCE, Device Code, and Client Credentials flows.
116
+
117
+ ### Setup
118
+ ```typescript
119
+ import { OAuth2Client, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
120
+
121
+ const oauth2 = new OAuth2Client({
122
+ baseURL: 'https://api.docyrus.com',
123
+ clientId: 'your-client-id',
124
+ clientSecret: 'your-client-secret', // optional for public clients
125
+ redirectUri: 'http://localhost:3000/callback',
126
+ defaultScopes: ['openid', 'offline_access'],
127
+ usePKCE: true, // default: true
128
+ tokenStorage: new BrowserOAuth2TokenStorage(localStorage),
129
+ })
130
+ ```
131
+
132
+ ### Authorization Code Flow (PKCE)
133
+ ```typescript
134
+ // Step 1: Generate auth URL
135
+ const { url, state, codeVerifier } = await oauth2.getAuthorizationUrl({
136
+ scope: 'openid offline_access Users.Read',
137
+ })
138
+
139
+ // Step 2: Redirect user
140
+ window.location.href = url
141
+
142
+ // Step 3: Handle callback
143
+ const tokens = await oauth2.handleCallback(window.location.href)
144
+ // tokens: { accessToken, refreshToken, ... }
145
+ ```
146
+
147
+ ### Client Credentials Flow (server-to-server)
148
+ ```typescript
149
+ const tokens = await oauth2.getClientCredentialsToken({
150
+ scope: 'Read.All',
151
+ delegatedUserId: 'user-id-to-impersonate',
152
+ })
153
+ ```
154
+
155
+ ### Device Code Flow (CLI/headless)
156
+ ```typescript
157
+ const deviceAuth = await oauth2.startDeviceAuthorization('openid offline_access')
158
+ console.log(`Go to: ${deviceAuth.verification_uri}`)
159
+ console.log(`Enter code: ${deviceAuth.user_code}`)
160
+
161
+ const tokens = await oauth2.pollDeviceAuthorization(
162
+ deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in,
163
+ { onExpired: () => console.log('Code expired'), signal: abortController.signal },
164
+ )
165
+ ```
166
+
167
+ ### Token Operations
168
+ ```typescript
169
+ const tokens = await oauth2.getTokens()
170
+ const isExpired = await oauth2.isTokenExpired()
171
+ const accessToken = await oauth2.getValidAccessToken() // auto-refreshes
172
+ const newTokens = await oauth2.refreshAccessToken()
173
+ await oauth2.revokeToken(tokens.refreshToken)
174
+ const tokenInfo = await oauth2.introspectToken(tokens.accessToken)
175
+ await oauth2.logout()
176
+ ```
177
+
178
+ ### Integrate OAuth2 with RestApiClient
179
+ ```typescript
180
+ import { RestApiClient, OAuth2Client, OAuth2TokenManagerAdapter, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
181
+
182
+ const tokenStorage = new BrowserOAuth2TokenStorage(localStorage)
183
+ const oauth2 = new OAuth2Client({ baseURL: 'https://api.docyrus.com', clientId: 'id', tokenStorage })
184
+
185
+ const tokenManager = new OAuth2TokenManagerAdapter(tokenStorage, async () => {
186
+ const tokens = await oauth2.refreshAccessToken()
187
+ return tokens.accessToken
188
+ })
189
+
190
+ const apiClient = new RestApiClient({ baseURL: 'https://api.docyrus.com', tokenManager })
191
+ ```
192
+
193
+ ### Rate Limit Check
194
+ ```typescript
195
+ const rateLimit = await oauth2.checkRateLimit()
196
+ // { remaining, limit, reset }
197
+ ```
198
+
199
+ ### PKCE Utilities
200
+ ```typescript
201
+ import { generatePKCEChallenge, generateCodeVerifier, generateCodeChallenge, generateState, generateNonce } from '@docyrus/api-client'
202
+
203
+ const pkce = await generatePKCEChallenge()
204
+ // { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Interceptors
210
+
211
+ ```typescript
212
+ client.use({
213
+ // Transform outgoing requests
214
+ async request(config) {
215
+ config.headers = { ...config.headers, 'X-Request-Time': new Date().toISOString() }
216
+ return config
217
+ },
218
+ // Transform incoming responses
219
+ async response(response, request) {
220
+ console.log(`${request.url} took ${Date.now() - request.timestamp}ms`)
221
+ return response
222
+ },
223
+ // Handle errors globally
224
+ async error(error, request, response) {
225
+ if (error.status === 401) { await refreshToken() }
226
+ return { error, request, response }
227
+ },
228
+ })
229
+ ```
230
+
231
+ ### Common Interceptor: Unwrap Response Data
232
+ ```typescript
233
+ client.use({
234
+ response: (response) => {
235
+ if (response.data?.data && typeof response.data === 'object' && !Array.isArray(response.data)) {
236
+ response.data = response.data.data
237
+ }
238
+ return response
239
+ },
240
+ })
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Error Handling
246
+
247
+ ```typescript
248
+ import {
249
+ ApiError, NetworkError, TimeoutError,
250
+ AuthenticationError, // 401
251
+ AuthorizationError, // 403
252
+ NotFoundError, // 404
253
+ RateLimitError, // 429 — has error.retryAfter
254
+ ValidationError,
255
+ // OAuth2-specific
256
+ OAuth2Error, InvalidGrantError, InvalidClientError,
257
+ AccessDeniedError, ExpiredTokenError, AuthorizationPendingError,
258
+ } from '@docyrus/api-client'
259
+
260
+ try {
261
+ await client.get('/resource')
262
+ } catch (error) {
263
+ if (error instanceof AuthenticationError) { /* re-login */ }
264
+ else if (error instanceof AuthorizationError) { /* forbidden */ }
265
+ else if (error instanceof NotFoundError) { /* 404 */ }
266
+ else if (error instanceof RateLimitError) { /* retry after error.retryAfter */ }
267
+ else if (error instanceof NetworkError) { /* offline */ }
268
+ else if (error instanceof TimeoutError) { /* timed out */ }
269
+ }
270
+ ```
271
+
272
+ ---
273
+
274
+ ## Streaming
275
+
276
+ ### Server-Sent Events (SSE)
277
+ ```typescript
278
+ const eventSource = client.sse('/events', {
279
+ onMessage(data) { console.log('Received:', data) },
280
+ onError(error) { console.error(error) },
281
+ onComplete() { console.log('Stream completed') },
282
+ })
283
+ eventSource.close()
284
+ ```
285
+
286
+ ### Chunked Streaming
287
+ ```typescript
288
+ for await (const chunk of client.stream('/stream', {
289
+ method: 'POST',
290
+ body: { query: 'stream data' },
291
+ })) {
292
+ console.log('Chunk:', chunk)
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## File Operations
299
+
300
+ ### Upload
301
+ ```typescript
302
+ const formData = new FormData()
303
+ formData.append('file', fileInput.files[0])
304
+ formData.append('description', 'My file')
305
+ await client.post('/upload', formData)
306
+ ```
307
+
308
+ ### Download
309
+ ```typescript
310
+ const response = await client.get('/download/file.pdf', { responseType: 'blob' })
311
+ const url = URL.createObjectURL(response.data)
312
+ const link = document.createElement('a')
313
+ link.href = url
314
+ link.download = 'file.pdf'
315
+ link.click()
316
+ ```
317
+
318
+ ### HTML to PDF
319
+ ```typescript
320
+ await client.html2pdf({
321
+ html: '<html><body>Content</body></html>',
322
+ // or: url: 'https://example.com',
323
+ options: { format: 'A4', margin: { top: 10, bottom: 10, left: 10, right: 10 }, landscape: false },
324
+ })
325
+ ```
326
+
327
+ ### Custom Query/Report
328
+ ```typescript
329
+ const results = await client.runCustomQuery(customQueryId, options)
330
+ // PUT reports/runCustomQuery/:customQueryId
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Utilities
336
+
337
+ ```typescript
338
+ import { buildUrl, isAbortError, parseContentDisposition, createAbortSignal, jsonToQueryString, withRetry } from '@docyrus/api-client'
339
+
340
+ const url = buildUrl('/api/users', { page: 1, limit: 10 })
341
+ // '/api/users?page=1&limit=10'
342
+
343
+ const signal = createAbortSignal(5000) // 5s timeout
344
+
345
+ const response = await withRetry(() => client.get('/flaky'), {
346
+ retries: 3, retryDelay: 1000,
347
+ retryCondition: (error) => error.status >= 500,
348
+ })
349
+ ```