@rustrak/client 0.25.0 → 0.27.0

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 (71) hide show
  1. package/README.md +65 -48
  2. package/dist/client.cjs +5 -4
  3. package/dist/client.cjs.map +1 -1
  4. package/dist/client.d.cts +2 -1
  5. package/dist/client.d.cts.map +1 -1
  6. package/dist/client.d.mts +2 -1
  7. package/dist/client.d.mts.map +1 -1
  8. package/dist/client.mjs +6 -5
  9. package/dist/client.mjs.map +1 -1
  10. package/dist/config.d.cts.map +1 -1
  11. package/dist/config.d.mts.map +1 -1
  12. package/dist/errors.cjs +19 -6
  13. package/dist/errors.cjs.map +1 -1
  14. package/dist/errors.d.cts +1 -1
  15. package/dist/errors.d.cts.map +1 -1
  16. package/dist/errors.d.mts +1 -1
  17. package/dist/errors.d.mts.map +1 -1
  18. package/dist/errors.mjs +19 -7
  19. package/dist/errors.mjs.map +1 -1
  20. package/dist/generated/binary.gen.cjs +6 -0
  21. package/dist/generated/binary.gen.cjs.map +1 -0
  22. package/dist/generated/binary.gen.d.cts +2 -0
  23. package/dist/generated/binary.gen.d.cts.map +1 -0
  24. package/dist/generated/binary.gen.d.mts +2 -0
  25. package/dist/generated/binary.gen.d.mts.map +1 -0
  26. package/dist/generated/binary.gen.mjs +6 -0
  27. package/dist/generated/binary.gen.mjs.map +1 -0
  28. package/dist/generated/client/client.gen.d.cts +1 -0
  29. package/dist/generated/client/index.d.cts +10 -0
  30. package/dist/generated/client/types.gen.d.cts +1 -1
  31. package/dist/generated/client/types.gen.d.mts +1 -1
  32. package/dist/generated/client/utils.gen.cjs +1 -1
  33. package/dist/generated/client/utils.gen.d.cts +2 -0
  34. package/dist/generated/client/utils.gen.d.cts.map +1 -1
  35. package/dist/generated/client/utils.gen.mjs +1 -1
  36. package/dist/generated/client.gen.cjs +2 -1
  37. package/dist/generated/client.gen.cjs.map +1 -1
  38. package/dist/generated/client.gen.mjs +1 -1
  39. package/dist/generated/core/params.gen.d.cts +1 -0
  40. package/dist/generated/core/queryKeySerializer.gen.d.cts +1 -0
  41. package/dist/generated/sdk.gen.cjs +797 -599
  42. package/dist/generated/sdk.gen.cjs.map +1 -1
  43. package/dist/generated/sdk.gen.d.cts +49 -28
  44. package/dist/generated/sdk.gen.d.cts.map +1 -1
  45. package/dist/generated/sdk.gen.d.mts +49 -29
  46. package/dist/generated/sdk.gen.d.mts.map +1 -1
  47. package/dist/generated/sdk.gen.mjs +797 -601
  48. package/dist/generated/sdk.gen.mjs.map +1 -1
  49. package/dist/generated/types.gen.d.cts +349 -20
  50. package/dist/generated/types.gen.d.cts.map +1 -1
  51. package/dist/generated/types.gen.d.mts +349 -20
  52. package/dist/generated/types.gen.d.mts.map +1 -1
  53. package/dist/generated/zod.gen.cjs +253 -18
  54. package/dist/generated/zod.gen.cjs.map +1 -1
  55. package/dist/generated/zod.gen.mjs +216 -17
  56. package/dist/generated/zod.gen.mjs.map +1 -1
  57. package/dist/index.cjs +2 -2
  58. package/dist/index.d.cts +3 -3
  59. package/dist/index.d.mts +3 -3
  60. package/dist/index.mjs +2 -2
  61. package/dist/result.cjs +0 -2
  62. package/dist/result.cjs.map +1 -1
  63. package/dist/result.d.cts.map +1 -1
  64. package/dist/result.d.mts.map +1 -1
  65. package/dist/result.mjs +0 -2
  66. package/dist/result.mjs.map +1 -1
  67. package/dist/utils/http.cjs +18 -31
  68. package/dist/utils/http.cjs.map +1 -1
  69. package/dist/utils/http.mjs +20 -32
  70. package/dist/utils/http.mjs.map +1 -1
  71. package/package.json +7 -9
package/README.md CHANGED
@@ -87,7 +87,7 @@ const resolved = await client.issues.update({
87
87
  - **Validated both ways**: requests and responses are checked against Zod schemas generated from the same spec; a mismatch is a typed error, not a crash
88
88
  - **No exceptions**: every method returns `Result<T, RustrakError>`, so failure is in the type, and the value survives the React server/client boundary
89
89
  - **Tree-shakeable**: ESM is published one module per file with `sideEffects: false`
90
- - **Retries**: exponential backoff on 408 and 5xx for idempotent methods (`GET`, `PUT`, `DELETE`)
90
+ - **Retries**: exponential backoff on an unreachable server and on 408, 500, 502, 503 and 504, for idempotent methods (`GET`, `PUT`, `DELETE`). Not on a timeout or a 429: `isRetryable` says yes to both, but when to retry is the caller's call
91
91
  - **Redacted 5xx**: a server error never carries a server-supplied message, so internal detail cannot reach a UI
92
92
 
93
93
  ## Configuration
@@ -157,8 +157,8 @@ console.log(result.data);
157
157
  | `gone` | 410 | ✅ | ❌ | |
158
158
  | `payload_too_large` | 413 | ✅ | ❌ | Envelope ingestion only |
159
159
  | `rate_limited` | 429 | ✅ | ✅ | Optional `retryAfter` in seconds |
160
- | `client_error` | other 4xx | ✅ | ❌ | Catch-all, so no status is unrepresentable |
161
- | `server_error` | 5xx | ✅ | ✅ | **Message is always a fixed generic string** |
160
+ | `client_error` | other 4xx | ✅ | 408 only | Catch-all, so no status is unrepresentable |
161
+ | `server_error` | 5xx | ✅ | 500, 502, 503, 504 | **Message is always a fixed generic string** |
162
162
  | `network` | n/a | no | ✅ | DNS, refused connection, TLS, timeout |
163
163
  | `invalid_response` | n/a | no | ❌ | 2xx whose body failed its schema |
164
164
  | `invalid_request` | n/a | no | ❌ | Your input failed its schema; nothing was sent |
@@ -248,54 +248,53 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
248
248
  | `agents.duration({ path, query? })` | `GET /api/projects/{project_id}/agents/duration` | Time-bucketed avg/p95 duration for `agent`/`ai_client` spans. |
249
249
  | `agents.environments({ path })` | `GET /api/projects/{project_id}/agents/environments` | The options the environment filter offers, read from the data rather than hardcoded — an installation may name its environments anything. |
250
250
  | `agents.modelsCalls({ path, query? })` | `GET /api/projects/{project_id}/agents/models/calls` | Top models by LLM call count (`gen_ai.operation.type:ai_client`). |
251
- | `agents.modelsTable({ path, query? })` | `GET /api/projects/{project_id}/agents/models` | |
251
+ | `agents.modelsTable({ path, query? })` | `GET /api/projects/{project_id}/agents/models` | Lists the AI models a project's agents called, with requests, tokens and latency. |
252
252
  | `agents.modelsTokens({ path, query? })` | `GET /api/projects/{project_id}/agents/models/tokens` | Top models by total tokens used (`gen_ai.operation.type:ai_client`). |
253
253
  | `agents.runs({ path, query? })` | `GET /api/projects/{project_id}/agents/runs` | Time-bucketed count of agent-run spans (`gen_ai.operation.type:agent`). |
254
254
  | `agents.summary({ path, query? })` | `GET /api/projects/{project_id}/agents/summary` | Agent runs, LLM calls, tool calls, errors, tokens and latency over the selected window — the numbers the six charts cannot state outright. |
255
255
  | `agents.tools({ path, query? })` | `GET /api/projects/{project_id}/agents/tools` | Top tools by call count (`gen_ai.operation.type:tool`). |
256
- | `agents.toolsTable({ path, query? })` | `GET /api/projects/{project_id}/agents/tools/stats` | |
256
+ | `agents.toolsTable({ path, query? })` | `GET /api/projects/{project_id}/agents/tools/stats` | Lists the tools a project's agents called, with calls, errors and latency. |
257
257
  | `agents.traces({ path, query? })` | `GET /api/projects/{project_id}/agents/traces` | Paginated per-trace_id aggregate (duration, tokens, tool usage) across all AI spans sharing that trace, regardless of origin. |
258
258
 
259
259
  ### Alert Channels
260
260
 
261
261
  | Method | Endpoint | Description |
262
262
  |---|---|---|
263
- | `alertChannels.create({ body })` | `POST /api/integrations` | |
264
- | `alertChannels.delete({ path })` | `DELETE /api/integrations/{id}` | |
265
- | `alertChannels.get({ path })` | `GET /api/integrations/{id}` | |
263
+ | `alertChannels.create({ body })` | `POST /api/integrations` | Creates a notification integration (webhook, Slack, email…). |
264
+ | `alertChannels.delete({ path })` | `DELETE /api/integrations/{id}` | Deletes a notification integration. |
265
+ | `alertChannels.get({ path })` | `GET /api/integrations/{id}` | Gets one notification integration. |
266
266
  | `alertChannels.list()` | `GET /api/integrations` | Lists the channels alerts can go through. |
267
267
  | `alertChannels.previewTemplate({ body })` | `POST /api/integrations/preview-template` | Renders a Custom Webhook template against a sample payload and hands back the body that would be sent. |
268
- | `alertChannels.test({ path, body })` | `POST /api/integrations/{id}/test` | |
269
- | `alertChannels.update({ path, body })` | `PATCH /api/integrations/{id}` | |
268
+ | `alertChannels.test({ path, body })` | `POST /api/integrations/{id}/test` | Sends a test notification through an integration. |
269
+ | `alertChannels.update({ path, body })` | `PATCH /api/integrations/{id}` | Updates a notification integration. |
270
270
 
271
271
  ### Alert Rules
272
272
 
273
273
  | Method | Endpoint | Description |
274
274
  |---|---|---|
275
- | `alertRules.create({ path, body })` | `POST /api/projects/{project_id}/alert-rules` | |
276
- | `alertRules.delete({ path })` | `DELETE /api/projects/{project_id}/alert-rules/{rule_id}` | |
277
- | `alertRules.get({ path })` | `GET /api/projects/{project_id}/alert-rules/{rule_id}` | |
275
+ | `alertRules.create({ path, body })` | `POST /api/projects/{project_id}/alert-rules` | Creates an alert rule in a project. |
276
+ | `alertRules.delete({ path })` | `DELETE /api/projects/{project_id}/alert-rules/{rule_id}` | Deletes an alert rule. |
277
+ | `alertRules.get({ path })` | `GET /api/projects/{project_id}/alert-rules/{rule_id}` | Gets one alert rule. |
278
278
  | `alertRules.historyByDay({ path, query? })` | `GET /api/projects/{project_id}/alert-history/daily` | Count a project's alert deliveries per day, by outcome. |
279
- | `alertRules.list({ path })` | `GET /api/projects/{project_id}/alert-rules` | |
280
- | `alertRules.listHistory({ path, query? })` | `GET /api/projects/{project_id}/alert-history` | |
281
- | `alertRules.update({ path, body })` | `PATCH /api/projects/{project_id}/alert-rules/{rule_id}` | |
279
+ | `alertRules.list({ path })` | `GET /api/projects/{project_id}/alert-rules` | Lists a project's alert rules. |
280
+ | `alertRules.listHistory({ path, query? })` | `GET /api/projects/{project_id}/alert-history` | Lists the alerts a project sent, newest first. |
281
+ | `alertRules.update({ path, body })` | `PATCH /api/projects/{project_id}/alert-rules/{rule_id}` | Updates an alert rule. |
282
282
 
283
283
  ### Auth
284
284
 
285
285
  | Method | Endpoint | Description |
286
286
  |---|---|---|
287
287
  | `auth.acceptInvitation({ body })` | `POST /auth/accept-invitation` | Accept a pending invitation: creates the user (with the invite's email + role) and logs in. |
288
- | `auth.changePassword({ body })` | `POST /auth/me/password` | Change the current user's password |
288
+ | `auth.changePassword({ body })` | `POST /auth/me/password` | Change the current user's password. |
289
289
  | `auth.confirmSsoLink({ body })` | `POST /auth/sso/link` | Link the pending SSO identity to its account once the account's password is given, and sign in. |
290
290
  | `auth.getCurrentUser()` | `GET /auth/me` | Get current authenticated user |
291
291
  | `auth.getInvitation({ path })` | `GET /auth/invitation/{token}` | Returns the invitation's details if it is still acceptable (for the accept page). |
292
292
  | `auth.login({ body })` | `POST /auth/login` | Authenticate user and create session |
293
293
  | `auth.logout()` | `POST /auth/logout` | Clear session |
294
- | `auth.register({ body })` | `POST /auth/register` | Registration is invite-only — always rejected. |
295
294
  | `auth.ssoCallback({ query? })` | `GET /auth/sso/callback` | Validate the provider response, resolve or provision the local account, and establish the normal Rustrak session. |
296
295
  | `auth.ssoConfig()` | `GET /auth/sso/config` | Public, non-sensitive information used to render the login page. |
297
296
  | `auth.ssoLink()` | `GET /auth/sso/link` | The account an SSO login matched, shown while its owner confirms. |
298
- | `auth.ssoStart()` | `POST /auth/sso/start` | Create a one-time OIDC authorization request and retain its protections in the encrypted session cookie. |
297
+ | `auth.ssoStart()` | `POST /auth/sso/start` | Create a one-time OIDC authorization request and retain its protections in the session. |
299
298
  | `auth.updateCurrentUser({ body })` | `PATCH /auth/me` | Update the current user's presentation preferences |
300
299
 
301
300
  ### Events
@@ -314,20 +313,20 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
314
313
  |---|---|---|
315
314
  | `github.callback({ query })` | `GET /api/github/callback` | GitHub sends the browser here after creating the app; the code trades for its credentials. |
316
315
  | `github.createIssue({ path, body })` | `POST /api/projects/{project_id}/issues/{issue_id}/github/issues` | Opens a GitHub issue and links it. |
317
- | `github.deleteProjectRepository({ path })` | `DELETE /api/projects/{project_id}/github` | |
318
- | `github.disconnect()` | `DELETE /api/github` | |
316
+ | `github.deleteProjectRepository({ path })` | `DELETE /api/projects/{project_id}/github` | Unlinks a project from its GitHub repository. |
317
+ | `github.disconnect()` | `DELETE /api/github` | Disconnects the GitHub App from this instance. |
319
318
  | `github.getMyLogin()` | `GET /api/github/me` | The GitHub username assignees sync through. |
320
- | `github.getProjectRepository({ path })` | `GET /api/projects/{project_id}/github` | |
319
+ | `github.getProjectRepository({ path })` | `GET /api/projects/{project_id}/github` | Gets the GitHub repository a project is linked to. |
321
320
  | `github.installed({ query })` | `GET /api/github/installed` | GitHub sends the browser here after the app is installed on an account. |
322
321
  | `github.issue({ path })` | `GET /api/projects/{project_id}/issues/{issue_id}/github` | The issue's links, the commits that may have caused it, and who owns its culprit file. |
323
322
  | `github.issueDraft({ path })` | `GET /api/projects/{project_id}/issues/{issue_id}/github/draft` | What creating a GitHub issue sends by default, with the templates applied, for the person to edit first. |
324
323
  | `github.linkIssue({ path, body })` | `POST /api/projects/{project_id}/issues/{issue_id}/github/links` | Links an existing GitHub issue or pull request. |
325
324
  | `github.manifest({ body })` | `POST /api/github/manifest` | What the dashboard posts to GitHub so GitHub creates an app already configured for this server. |
326
325
  | `github.putIssueTemplate({ body })` | `PUT /api/github/issue-template` | The labels, title prefix and body footer every GitHub issue starts with, before a project's own. |
327
- | `github.putMyLogin({ body })` | `PUT /api/github/me` | |
328
- | `github.putProjectRepository({ path, body })` | `PUT /api/projects/{project_id}/github` | |
326
+ | `github.putMyLogin({ body })` | `PUT /api/github/me` | Sets or clears the caller's GitHub username, which assignees sync through. |
327
+ | `github.putProjectRepository({ path, body })` | `PUT /api/projects/{project_id}/github` | Links a project to a GitHub repository. |
329
328
  | `github.repositories({ query })` | `GET /api/github/repositories` | The repositories a project can point at, for whoever may change that project's settings. |
330
- | `github.status()` | `GET /api/github` | |
329
+ | `github.status()` | `GET /api/github` | Shows whether a GitHub App is connected, and how. |
331
330
  | `github.syncCodeowners({ path })` | `POST /api/projects/{project_id}/github/codeowners` | Copies the repository's CODEOWNERS so ownership can be answered without asking GitHub each time. |
332
331
  | `github.unlinkIssue({ path })` | `DELETE /api/projects/{project_id}/issues/{issue_id}/github/links/{link_id}` | |
333
332
  | `github.webhook({ body })` | `POST /api/github/webhook` | Public, and trusted only through the signature GitHub computes with the app's webhook secret. |
@@ -356,8 +355,9 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
356
355
  |---|---|---|
357
356
  | `environments.list({ path })` | `GET /api/projects/{project_id}/environments` | Lists the environments a project's issues have been seen in, by name. |
358
357
  | `feedback.list({ path, query? })` | `GET /api/projects/{project_id}/feedback` | Lists user feedback with what each user submitted. |
359
- | `issues.bulkDelete({ path, body })` | `DELETE /api/projects/{project_id}/issues` | Bulk delete by ids or filter |
360
- | `issues.bulkUpdate({ path, body })` | `PUT /api/projects/{project_id}/issues` | Bulk mutate |
358
+ | `feedback.listAll({ query? })` | `GET /api/feedback` | Lists user feedback across every project the reader can read issues on. |
359
+ | `issues.bulkDelete({ path, body })` | `DELETE /api/projects/{project_id}/issues` | Deletes many issues at once, selected by id or by filter. |
360
+ | `issues.bulkUpdate({ path, body })` | `PUT /api/projects/{project_id}/issues` | Sets the status or priority of many issues at once, by id. |
361
361
  | `issues.createComment({ path, body })` | `POST /api/projects/{project_id}/issues/{issue_id}/comments` | Add a note. |
362
362
  | `issues.createUserReport({ path, body })` | `POST /api/projects/{project_id}/issues/{issue_id}/user-reports` | Attach user feedback to an issue. |
363
363
  | `issues.delete({ path })` | `DELETE /api/projects/{project_id}/issues/{issue_id}` | Hard-deletes an issue and all associated events |
@@ -368,6 +368,7 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
368
368
  | `issues.getStats({ path, query? })` | `GET /api/projects/{project_id}/issues/{issue_id}/stats` | Zero-filled event-count timeseries. |
369
369
  | `issues.getTagValues({ path })` | `GET /api/projects/{project_id}/issues/{issue_id}/tags/{key}` | Lists the distinct values (with counts) for a tag key across the issue. |
370
370
  | `issues.list({ path, query? })` | `GET /api/projects/{project_id}/issues` | Lists issues for a project with offset-based pagination |
371
+ | `issues.listAll({ query? })` | `GET /api/issues` | Lists issues across every project the reader can read issues on, in the same shape and pages as a project's list. |
371
372
  | `issues.listUserReports({ path, query? })` | `GET /api/projects/{project_id}/issues/{issue_id}/user-reports` | List user feedback for an issue. |
372
373
  | `issues.markSeen({ path })` | `POST /api/projects/{project_id}/issues/{issue_id}/seen` | Mark the issue as seen by the current user. |
373
374
  | `issues.setBookmark({ path, body })` | `PUT /api/projects/{project_id}/issues/{issue_id}/bookmark` | Set/clear the per-user bookmark. |
@@ -395,13 +396,13 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
395
396
 
396
397
  | Method | Endpoint | Description |
397
398
  |---|---|---|
398
- | `monitors.create({ path, body })` | `POST /api/projects/{project_id}/monitors` | |
399
- | `monitors.delete({ path })` | `DELETE /api/projects/{project_id}/monitors/{slug}` | |
400
- | `monitors.get({ path })` | `GET /api/projects/{project_id}/monitors/{slug}` | |
401
- | `monitors.list({ path })` | `GET /api/projects/{project_id}/monitors` | |
402
- | `monitors.listCheckins({ path, query? })` | `GET /api/projects/{project_id}/monitors/{slug}/checkins` | |
399
+ | `monitors.create({ path, body })` | `POST /api/projects/{project_id}/monitors` | Creates a cron monitor in a project. |
400
+ | `monitors.delete({ path })` | `DELETE /api/projects/{project_id}/monitors/{slug}` | Deletes a cron monitor and its check-ins. |
401
+ | `monitors.get({ path })` | `GET /api/projects/{project_id}/monitors/{slug}` | Gets one cron monitor. |
402
+ | `monitors.list({ path })` | `GET /api/projects/{project_id}/monitors` | Lists a project's cron monitors. |
403
+ | `monitors.listCheckins({ path, query? })` | `GET /api/projects/{project_id}/monitors/{slug}/checkins` | Lists a cron monitor's check-ins, newest first. |
403
404
  | `monitors.stats({ path, query? })` | `GET /api/projects/{project_id}/monitor-stats` | Counts the check-ins of a project's monitors by status over a window, bucket by bucket. |
404
- | `monitors.update({ path, body })` | `PUT /api/projects/{project_id}/monitors/{slug}` | |
405
+ | `monitors.update({ path, body })` | `PUT /api/projects/{project_id}/monitors/{slug}` | Updates a cron monitor. |
405
406
 
406
407
  ### OAuth
407
408
 
@@ -420,7 +421,7 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
420
421
  | `projects.delete({ path })` | `DELETE /api/projects/{id}` | Delete a project |
421
422
  | `projects.get({ path })` | `GET /api/projects/{id}` | Get a project by ID |
422
423
  | `projects.getRateLimits()` | `GET /api/rate-limits` | What a project without limits of its own follows |
423
- | `projects.list({ query? })` | `GET /api/projects` | List projects with pagination |
424
+ | `projects.list({ query? })` | `GET /api/projects` | List projects with pagination. |
424
425
  | `projects.update({ path, body })` | `PATCH /api/projects/{id}` | Update a project |
425
426
 
426
427
  ### Releases
@@ -431,7 +432,22 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
431
432
  | `releases.finalize({ path, body })` | `PUT /api/0/projects/{org_slug}/{project_slug}/releases/{version}/` | Generic partial update (`ref`, `url`, `dateReleased`). |
432
433
  | `releases.get({ path, query? })` | `GET /api/projects/{project_id}/releases/{release}` | Gets one release with its events, new issues, environments and session health. |
433
434
  | `releases.list({ path, query? })` | `GET /api/projects/{project_id}/releases` | Lists a project's releases, newest first, with the events, new issues, environments and session health of each. |
434
- | `releases.newIssues({ path, query? })` | `GET /api/projects/{project_id}/releases/{release}/new-issues` | |
435
+ | `releases.newIssues({ path, query? })` | `GET /api/projects/{project_id}/releases/{release}/new-issues` | Lists the issues first seen in a release. |
436
+
437
+ ### Saved Views
438
+
439
+ | Method | Endpoint | Description |
440
+ |---|---|---|
441
+ | `savedViews.create({ path, body })` | `POST /api/projects/{project_id}/views` | Saves a project page's address as a view, personal unless `visibility` is `project`. |
442
+ | `savedViews.delete({ path })` | `DELETE /api/projects/{project_id}/views/{view_id}` | Deletes a project view. |
443
+ | `savedViews.list({ path, query })` | `GET /api/projects/{project_id}/views` | Lists the views of one project page the caller sees: their own and the shared ones. |
444
+ | `savedViews.setDefault({ path, body })` | `PUT /api/projects/{project_id}/views/default` | Sets or clears the view a project page opens on, for the caller or the project. |
445
+ | `savedViews.update({ path, body })` | `PATCH /api/projects/{project_id}/views/{view_id}` | Renames a project view, or changes its address or who sees it. |
446
+ | `workspaceViews.create({ body })` | `POST /api/views` | Saves a workspace page's address as a view, personal unless `visibility` is `project`. |
447
+ | `workspaceViews.delete({ path })` | `DELETE /api/views/{view_id}` | Deletes a workspace view. |
448
+ | `workspaceViews.list({ query })` | `GET /api/views` | Lists the views of one workspace page (one that spans every project). |
449
+ | `workspaceViews.setDefault({ body })` | `PUT /api/views/default` | Sets or clears the view a workspace page opens on, for the caller or everyone. |
450
+ | `workspaceViews.update({ path, body })` | `PATCH /api/views/{view_id}` | Renames a workspace view, or changes its address or who sees it. |
435
451
 
436
452
  ### Schedules
437
453
 
@@ -445,7 +461,8 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
445
461
  |---|---|---|
446
462
  | `sentry.getIssue({ path })` | `GET /api/0/issues/{issue_id}/` | One issue with its releases and its 24h and 30d series (Sentry's `GroupDetailsEndpoint`). |
447
463
  | `sentry.getLatestEvent({ path })` | `GET /api/0/issues/{issue_id}/events/latest/` | The issue's most recent event (Sentry's `GroupEventDetailsEndpoint`, `issues/endpoints/group_event_details.py`). |
448
- | `sentry.listIssues({ path, query? })` | `GET /api/0/projects/{org_slug}/{project_slug}/issues/` | A project's issues, filtered by `query`, sorted by `sort` and paginated by `cursor` (Sentry's `ProjectGroupIndexEndpoint`, `issues/endpoints/project_group_index.py`). |
464
+ | `sentry.listIssues({ path, query? })` | `GET /api/0/projects/{org_slug}/{project_slug}/issues/` | Issues, filtered by `query`, sorted by `sort` and paginated by `cursor` (Sentry's `ProjectGroupIndexEndpoint`, `issues/endpoints/project_group_index.py`). |
465
+ | `sentry.listOrganizationIssues({ path, query? })` | `GET /api/0/organizations/{org_slug}/issues/` | Issues across projects, narrowed by `project`, filtered by `query`, sorted by `sort` and paginated by `cursor` (Sentry's `OrganizationGroupIndexEndpoint`, `issues/endpoints/organization_group_index.py`). |
449
466
  | `sentry.listOrganizations()` | `GET /api/0/organizations/` | The organizations the token belongs to (Sentry's `OrganizationIndexEndpoint`): always the one synthetic org. |
450
467
  | `sentry.listProjects({ query? })` | `GET /api/0/projects/` | The projects the token can see, newest first (Sentry's `ProjectIndexEndpoint`, `core/endpoints/project_index.py`). |
451
468
  | `sentry.listRepos({ path })` | `GET /api/0/organizations/{org_slug}/repos/` | The code repositories connected to the organization (Sentry's `OrganizationRepositoriesEndpoint`). |
@@ -457,9 +474,9 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
457
474
 
458
475
  | Method | Endpoint | Description |
459
476
  |---|---|---|
460
- | `sessions.getStats({ path, query? })` | `GET /api/projects/{project_id}/sessions/stats` | |
461
- | `sessions.getSummary({ path, query? })` | `GET /api/projects/{project_id}/sessions/summary` | |
462
- | `sessions.getTimeseries({ path, query? })` | `GET /api/projects/{project_id}/sessions/timeseries` | |
477
+ | `sessions.getStats({ path, query? })` | `GET /api/projects/{project_id}/sessions/stats` | Session counts by release, for release health. |
478
+ | `sessions.getSummary({ path, query? })` | `GET /api/projects/{project_id}/sessions/summary` | Totals and crash-free rates for a project's sessions. |
479
+ | `sessions.getTimeseries({ path, query? })` | `GET /api/projects/{project_id}/sessions/timeseries` | Session counts over time, bucketed for a chart. |
463
480
 
464
481
  ### Settings
465
482
 
@@ -474,10 +491,10 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
474
491
  |---|---|---|
475
492
  | `debugFiles.assemble({ path, body })` | `POST /api/0/projects/{org_slug}/{project_slug}/files/difs/assemble/` | Assemble uploaded chunks into debug information files (ProGuard mappings, JVM source bundles). |
476
493
  | `debugFiles.upload({ path, body })` | `POST /api/0/projects/{org_slug}/{project_slug}/files/dsyms/` | Upload a zip of debug information files (the path `sentry-cli upload-proguard` takes). |
477
- | `sourceMaps.artifactBundleAssemble({ path, body })` | `POST /api/0/organizations/{org_slug}/artifactbundle/assemble/` | |
478
- | `sourceMaps.chunkUpload({ path, body })` | `POST /api/0/organizations/{org_slug}/chunk-upload/` | |
479
- | `sourceMaps.chunkUploadCapability({ path })` | `GET /api/0/organizations/{org_slug}/chunk-upload/` | |
480
- | `sourceMaps.list({ path })` | `GET /api/0/projects/{org_slug}/{project_slug}/files/source-maps/` | |
494
+ | `sourceMaps.artifactBundleAssemble({ path, body })` | `POST /api/0/organizations/{org_slug}/artifactbundle/assemble/` | Assembles uploaded chunks into an artifact bundle (sentry-cli `sourcemaps upload`). |
495
+ | `sourceMaps.chunkUpload({ path, body })` | `POST /api/0/organizations/{org_slug}/chunk-upload/` | Receives chunks of a source map or debug file upload from sentry-cli. |
496
+ | `sourceMaps.chunkUploadCapability({ path })` | `GET /api/0/organizations/{org_slug}/chunk-upload/` | Describes how sentry-cli should upload chunks: size, concurrency and accepted kinds. |
497
+ | `sourceMaps.list({ path })` | `GET /api/0/projects/{org_slug}/{project_slug}/files/source-maps/` | Lists a project's uploaded source map artifacts (sentry-cli). |
481
498
  | `sourceMaps.orgDetails({ path })` | `GET /api/0/organizations/{org_slug}/` | Always returns 200 with synthetic org data — sentry-cli validates this before uploading. |
482
499
 
483
500
  ### Spans
@@ -491,8 +508,8 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
491
508
 
492
509
  | Method | Endpoint | Description |
493
510
  |---|---|---|
494
- | `stats.getEventTimeseries({ path, query? })` | `GET /api/projects/{project_id}/events/stats` | |
495
- | `stats.getSummary({ path, query? })` | `GET /api/projects/{project_id}/stats/summary` | |
511
+ | `stats.getEventTimeseries({ path, query? })` | `GET /api/projects/{project_id}/events/stats` | Counts a project's events over time, bucketed for a chart. |
512
+ | `stats.getSummary({ path, query? })` | `GET /api/projects/{project_id}/stats/summary` | Headline figures for a project's overview: events, new and open issues. |
496
513
 
497
514
  ### Storage
498
515
 
@@ -505,13 +522,13 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
505
522
  | `storage.getCleanupStatus()` | `GET /api/storage/cleanup/status` | Progress of the running cleanup, or the outcome of the last one. |
506
523
  | `storage.getCompaction()` | `GET /api/storage/compaction` | Whether the database file shrinks when data is deleted, and the disk a one-off compaction needs. |
507
524
  | `storage.getProjects()` | `GET /api/storage/projects` | Per-project counts and estimated weight. |
508
- | `storage.getRetention()` | `GET /api/storage/retention` | |
525
+ | `storage.getRetention()` | `GET /api/storage/retention` | Gets the retention settings and the cleanup schedule. |
509
526
  | `storage.getSummary()` | `GET /api/storage/summary` | Row counts, DB size, and source-map weight. |
510
527
  | `storage.previewCleanup({ body })` | `POST /api/storage/cleanup/preview` | Dry-run count, mutates nothing. |
511
528
  | `storage.previewRetention({ body })` | `POST /api/storage/retention/preview` | What saving these settings would delete on the next cleanup, without saving or deleting anything. |
512
529
  | `storage.previewSourceMapGc()` | `POST /api/storage/source-maps/gc/preview` | Dry-run orphan count, deletes nothing. |
513
530
  | `storage.runRetention()` | `POST /api/storage/retention/run` | Enforces the saved retention now, without waiting for the schedule. |
514
- | `storage.updateRetention({ body })` | `PUT /api/storage/retention` | |
531
+ | `storage.updateRetention({ body })` | `PUT /api/storage/retention` | Updates the retention settings; the next cleanup is scheduled at once. |
515
532
 
516
533
  ### Team
517
534
 
@@ -525,7 +542,7 @@ Every method, grouped as it hangs off the client (`client.issues.list(...)`).
525
542
 
526
543
  | Method | Endpoint | Description |
527
544
  |---|---|---|
528
- | `telemetry.preview()` | `GET /api/telemetry/preview` | |
545
+ | `telemetry.preview()` | `GET /api/telemetry/preview` | Shows the exact anonymous telemetry report this instance would send. |
529
546
 
530
547
  ### Tokens
531
548
 
package/dist/client.cjs CHANGED
@@ -1,11 +1,12 @@
1
- const require_utils_gen = require("./generated/client/utils.gen.cjs");
1
+ const require_binary_gen = require("./generated/binary.gen.cjs");
2
2
  const require_client_gen = require("./generated/client/client.gen.cjs");
3
+ const require_utils_gen = require("./generated/client/utils.gen.cjs");
3
4
  const require_sdk_gen = require("./generated/sdk.gen.cjs");
4
5
  const require_result = require("./result.cjs");
5
6
  const require_http = require("./utils/http.cjs");
6
7
  let zod = require("zod");
7
8
  // src/client.ts
8
- const BINARY = /* @__PURE__ */ new Set(["events.downloadAttachment"]);
9
+ const BINARY_OPERATIONS = new Set(require_binary_gen.BINARY);
9
10
  /**
10
11
  * Main Rustrak API client. Every method returns a `Result`; nothing throws for
11
12
  * an expected failure.
@@ -48,7 +49,7 @@ function createApi(config) {
48
49
  methods[method] = async (options = {}) => toResult(await resource[method]({
49
50
  ...options,
50
51
  headers: options.headers instanceof Headers || Array.isArray(options.headers) ? Object.fromEntries(new Headers(options.headers)) : options.headers,
51
- ...BINARY.has(`${name}.${method}`) && { parseAs: "blob" },
52
+ ...BINARY_OPERATIONS.has(`${name}.${method}`) && { parseAs: "blob" },
52
53
  throwOnError: false
53
54
  }));
54
55
  }
@@ -66,7 +67,7 @@ function createApi(config) {
66
67
  */
67
68
  function toResult({ data, error, response }) {
68
69
  if (error === void 0) return require_result.Ok(data);
69
- if (require_http.isTransportFailure(error)) return require_result.Err(error.rustrakError);
70
+ if (error instanceof require_http.RustrakTransportFailure) return require_result.Err(error.rustrakError);
70
71
  if (response && !response.ok) return require_result.Err(require_http.transformHttpError(response, error));
71
72
  if (!response && error instanceof zod.ZodError) return require_result.Err({
72
73
  kind: "invalid_request",
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["Rustrak","createClient","createConfig","createFetch","Ok","isTransportFailure","Err","transformHttpError","ZodError"],"sources":["../src/client.ts"],"sourcesContent":["import { ZodError } from 'zod';\nimport type { ClientConfig } from './config.js';\nimport type { RustrakError } from './errors.js';\nimport { createClient, createConfig } from './generated/client/index.js';\nimport { Rustrak } from './generated/sdk.gen.js';\nimport { Err, Ok, type Result } from './result.js';\nimport {\n createFetch,\n isTransportFailure,\n transformHttpError,\n} from './utils/http.js';\n\n/**\n * Operations whose 2xx is a file, not JSON: read as a `Blob`. The spec leaves\n * the body untyped (zod has no binary type), so the type comes from here.\n */\ntype Binary = 'events.downloadAttachment';\nconst BINARY: ReadonlySet<string> = new Set<Binary>([\n 'events.downloadAttachment',\n]);\n\n/** What a caller may pass: the request's own parts, never client plumbing. */\ntype RequestKeys = 'path' | 'query' | 'body' | 'headers' | 'signal';\n\ntype Call<F, Bytes extends boolean> = F extends (options: infer O) => infer R\n ? (\n ...args: {} extends Pick<O, Extract<keyof O, RequestKeys>>\n ? [options?: Pick<O, Extract<keyof O, RequestKeys>>]\n : [options: Pick<O, Extract<keyof O, RequestKeys>>]\n ) => Promise<\n Result<Bytes extends true ? Blob : Data<Awaited<R>>, RustrakError>\n >\n : never;\n\n/** The success half of the generated `{ data, error }` union. */\ntype Data<R> =\n Extract<R, { error: undefined }> extends { data: infer D } ? D : never;\n\ntype Resource<T, Name extends string> = {\n readonly [M in keyof T]: Call<\n T[M],\n `${Name}.${M & string}` extends Binary ? true : false\n >;\n};\n\n/**\n * Every operation in `openapi.json`, grouped by tag:\n * `client.issues.list({ path: { project_id } })`.\n *\n * Generated in `src/generated/`; this file only changes what a call returns.\n */\nexport type RustrakClient = {\n readonly [R in keyof Rustrak]: Resource<Rustrak[R], R & string>;\n};\n\n/**\n * Main Rustrak API client. Every method returns a `Result`; nothing throws for\n * an expected failure.\n *\n * @example\n * ```typescript\n * const client = new RustrakClient({\n * baseUrl: 'http://localhost:8080',\n * token: 'your-api-token',\n * });\n *\n * const issues = await client.issues.list({ path: { project_id: 1 } });\n * if (issues.success) console.log(issues.data.items);\n * ```\n */\nexport const RustrakClient = class {\n constructor(config: ClientConfig) {\n // biome-ignore lint/correctness/noConstructorReturn: the instance is the generated SDK with every method wrapped\n return Object.setPrototypeOf(createApi(config), new.target.prototype);\n }\n} as unknown as new (\n config: ClientConfig,\n) => RustrakClient;\n\nfunction createApi(config: ClientConfig): RustrakClient {\n const sdk = new Rustrak({\n client: createClient(\n createConfig({\n baseUrl: config.baseUrl,\n credentials: 'include',\n fetch: createFetch(config),\n // Every 2xx in the spec is JSON or empty, except the files in\n // `BINARY`. `auto` would hand back an HTML page or a stream as a\n // success without validating it.\n parseAs: 'json',\n headers: {\n // Without it a content-negotiating hop can answer with HTML.\n Accept: 'application/json',\n ...config.headers,\n ...(config.token && { Authorization: `Bearer ${config.token}` }),\n },\n }),\n ),\n });\n\n const api: Record<string, Record<string, unknown>> = {};\n for (const name of Object.getOwnPropertyNames(Rustrak.prototype)) {\n if (name === 'constructor') continue;\n const resource = sdk[name as keyof Rustrak] as unknown as Record<\n string,\n (options: object) => Promise<Outcome>\n >;\n const methods: Record<string, unknown> = {};\n for (const method of Object.getOwnPropertyNames(\n Object.getPrototypeOf(resource),\n )) {\n if (method === 'constructor') continue;\n methods[method] = async (options: { headers?: unknown } = {}) =>\n toResult(\n await resource[method]!({\n ...options,\n // A JSON write spreads these into an object, which keeps nothing\n // of a `Headers` or an array of pairs.\n headers:\n options.headers instanceof Headers ||\n Array.isArray(options.headers)\n ? Object.fromEntries(new Headers(options.headers))\n : options.headers,\n ...(BINARY.has(`${name}.${method}`) && { parseAs: 'blob' }),\n throwOnError: false,\n }),\n );\n }\n api[name] = methods;\n }\n return api as unknown as RustrakClient;\n}\n\ntype Outcome = {\n data?: unknown;\n error?: unknown;\n response?: Response;\n};\n\n/**\n * The boundary: the generated client's `{ data, error, response }` becomes a\n * `Result`.\n *\n * Only failures this file recognises become an `Err`. Anything else, a\n * `TypeError` from a genuine bug, is rethrown so it crashes where it can be\n * debugged instead of being laundered into `{ success: false }`.\n */\nfunction toResult({\n data,\n error,\n response,\n}: Outcome): Result<unknown, RustrakError> {\n if (error === undefined) return Ok(data);\n if (isTransportFailure(error)) return Err(error.rustrakError);\n if (response && !response.ok) {\n // `error` is the parsed body, or its text when it is not JSON.\n return Err(transformHttpError(response, error));\n }\n if (!response && error instanceof ZodError) {\n return Err({\n kind: 'invalid_request',\n message: 'The request input did not match the expected schema.',\n });\n }\n if (response && error instanceof SyntaxError) {\n return Err({\n kind: 'invalid_response',\n message: 'The server returned a body that is not valid JSON.',\n });\n }\n if (response && error instanceof ZodError) {\n // No Zod issues: they embed the offending response data.\n return Err({\n kind: 'invalid_response',\n message: 'The server response did not match the expected schema.',\n });\n }\n throw error;\n}\n"],"mappings":";;;;;;;AAiBA,MAAM,yBAA8B,IAAI,IAAY,CAClD,2BACF,CAAC;;;;;;;;;;;;;;;;AAmDD,MAAa,gBAAgB,MAAM;CACjC,YAAY,QAAsB;EAEhC,OAAO,OAAO,eAAe,UAAU,MAAM,GAAG,WAAW,SAAS;CACtE;AACF;AAIA,SAAS,UAAU,QAAqC;CACtD,MAAM,MAAM,IAAIA,gBAAAA,QAAQ,EACtB,QAAQC,mBAAAA,aACNC,kBAAAA,aAAa;EACX,SAAS,OAAO;EAChB,aAAa;EACb,OAAOC,aAAAA,YAAY,MAAM;EAIzB,SAAS;EACT,SAAS;GAEP,QAAQ;GACR,GAAG,OAAO;GACV,GAAI,OAAO,SAAS,EAAE,eAAe,UAAU,OAAO,QAAQ;EAChE;CACF,CAAC,CACH,EACF,CAAC;CAED,MAAM,MAA+C,CAAC;CACtD,KAAK,MAAM,QAAQ,OAAO,oBAAoBH,gBAAAA,QAAQ,SAAS,GAAG;EAChE,IAAI,SAAS,eAAe;EAC5B,MAAM,WAAW,IAAI;EAIrB,MAAM,UAAmC,CAAC;EAC1C,KAAK,MAAM,UAAU,OAAO,oBAC1B,OAAO,eAAe,QAAQ,CAChC,GAAG;GACD,IAAI,WAAW,eAAe;GAC9B,QAAQ,UAAU,OAAO,UAAiC,CAAC,MACzD,SACE,MAAM,SAAS,OAAO,CAAE;IACtB,GAAG;IAGH,SACE,QAAQ,mBAAmB,WAC3B,MAAM,QAAQ,QAAQ,OAAO,IACzB,OAAO,YAAY,IAAI,QAAQ,QAAQ,OAAO,CAAC,IAC/C,QAAQ;IACd,GAAI,OAAO,IAAI,GAAG,KAAK,GAAG,QAAQ,KAAK,EAAE,SAAS,OAAO;IACzD,cAAc;GAChB,CAAC,CACH;EACJ;EACA,IAAI,QAAQ;CACd;CACA,OAAO;AACT;;;;;;;;;AAgBA,SAAS,SAAS,EAChB,MACA,OACA,YACyC;CACzC,IAAI,UAAU,KAAA,GAAW,OAAOI,eAAAA,GAAG,IAAI;CACvC,IAAIC,aAAAA,mBAAmB,KAAK,GAAG,OAAOC,eAAAA,IAAI,MAAM,YAAY;CAC5D,IAAI,YAAY,CAAC,SAAS,IAExB,OAAOA,eAAAA,IAAIC,aAAAA,mBAAmB,UAAU,KAAK,CAAC;CAEhD,IAAI,CAAC,YAAY,iBAAiBC,IAAAA,UAChC,OAAOF,eAAAA,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiB,aAC/B,OAAOA,eAAAA,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiBE,IAAAA,UAE/B,OAAOF,eAAAA,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,MAAM;AACR"}
1
+ {"version":3,"file":"client.cjs","names":["BINARY","Rustrak","createClient","createConfig","createFetch","Ok","RustrakTransportFailure","Err","transformHttpError","ZodError"],"sources":["../src/client.ts"],"sourcesContent":["import { ZodError } from 'zod';\nimport type { ClientConfig } from './config.js';\nimport type { RustrakError } from './errors.js';\nimport { BINARY } from './generated/binary.gen.js';\nimport { createClient, createConfig } from './generated/client/index.js';\nimport { Rustrak } from './generated/sdk.gen.js';\nimport { Err, Ok, type Result } from './result.js';\nimport {\n createFetch,\n RustrakTransportFailure,\n transformHttpError,\n} from './utils/http.js';\n\n/**\n * Operations whose 2xx is a file, read as a `Blob`. The spec leaves the body\n * untyped (zod has no binary type), so the type comes from this list.\n */\ntype Binary = (typeof BINARY)[number];\nconst BINARY_OPERATIONS: ReadonlySet<string> = new Set(BINARY);\n\n/** What a caller may pass: the request's own parts, never client plumbing. */\ntype RequestKeys = 'path' | 'query' | 'body' | 'headers' | 'signal';\n\ntype Call<F, Bytes extends boolean> = F extends (options: infer O) => infer R\n ? (\n ...args: {} extends Pick<O, Extract<keyof O, RequestKeys>>\n ? [options?: Pick<O, Extract<keyof O, RequestKeys>>]\n : [options: Pick<O, Extract<keyof O, RequestKeys>>]\n ) => Promise<\n Result<Bytes extends true ? Blob : Data<Awaited<R>>, RustrakError>\n >\n : never;\n\n/** The success half of the generated `{ data, error }` union. */\ntype Data<R> =\n Extract<R, { error: undefined }> extends { data: infer D } ? D : never;\n\ntype Resource<T, Name extends string> = {\n readonly [M in keyof T]: Call<\n T[M],\n `${Name}.${M & string}` extends Binary ? true : false\n >;\n};\n\n/**\n * Every operation in `openapi.json`, grouped by tag:\n * `client.issues.list({ path: { project_id } })`.\n *\n * Generated in `src/generated/`; this file only changes what a call returns.\n */\nexport type RustrakClient = {\n readonly [R in keyof Rustrak]: Resource<Rustrak[R], R & string>;\n};\n\n/**\n * Main Rustrak API client. Every method returns a `Result`; nothing throws for\n * an expected failure.\n *\n * @example\n * ```typescript\n * const client = new RustrakClient({\n * baseUrl: 'http://localhost:8080',\n * token: 'your-api-token',\n * });\n *\n * const issues = await client.issues.list({ path: { project_id: 1 } });\n * if (issues.success) console.log(issues.data.items);\n * ```\n */\nexport const RustrakClient = class {\n constructor(config: ClientConfig) {\n // biome-ignore lint/correctness/noConstructorReturn: the instance is the generated SDK with every method wrapped\n return Object.setPrototypeOf(createApi(config), new.target.prototype);\n }\n} as unknown as new (\n config: ClientConfig,\n) => RustrakClient;\n\nfunction createApi(config: ClientConfig): RustrakClient {\n const sdk = new Rustrak({\n client: createClient(\n createConfig({\n baseUrl: config.baseUrl,\n credentials: 'include',\n fetch: createFetch(config),\n // Every 2xx in the spec is JSON or empty, except the files in\n // `BINARY`. `auto` would hand back an HTML page or a stream as a\n // success without validating it.\n parseAs: 'json',\n headers: {\n // Without it a content-negotiating hop can answer with HTML.\n Accept: 'application/json',\n ...config.headers,\n ...(config.token && { Authorization: `Bearer ${config.token}` }),\n },\n }),\n ),\n });\n\n const api: Record<string, Record<string, unknown>> = {};\n for (const name of Object.getOwnPropertyNames(Rustrak.prototype)) {\n if (name === 'constructor') continue;\n const resource = sdk[name as keyof Rustrak] as unknown as Record<\n string,\n (options: object) => Promise<Outcome>\n >;\n const methods: Record<string, unknown> = {};\n for (const method of Object.getOwnPropertyNames(\n Object.getPrototypeOf(resource),\n )) {\n if (method === 'constructor') continue;\n methods[method] = async (options: { headers?: unknown } = {}) =>\n toResult(\n await resource[method]!({\n ...options,\n // A JSON write spreads these into an object, which keeps nothing\n // of a `Headers` or an array of pairs.\n headers:\n options.headers instanceof Headers ||\n Array.isArray(options.headers)\n ? Object.fromEntries(new Headers(options.headers))\n : options.headers,\n ...(BINARY_OPERATIONS.has(`${name}.${method}`) && {\n parseAs: 'blob',\n }),\n throwOnError: false,\n }),\n );\n }\n api[name] = methods;\n }\n return api as unknown as RustrakClient;\n}\n\ntype Outcome = {\n data?: unknown;\n error?: unknown;\n response?: Response;\n};\n\n/**\n * The boundary: the generated client's `{ data, error, response }` becomes a\n * `Result`.\n *\n * Only failures this file recognises become an `Err`. Anything else, a\n * `TypeError` from a genuine bug, is rethrown so it crashes where it can be\n * debugged instead of being laundered into `{ success: false }`.\n */\nfunction toResult({\n data,\n error,\n response,\n}: Outcome): Result<unknown, RustrakError> {\n if (error === undefined) return Ok(data);\n if (error instanceof RustrakTransportFailure) return Err(error.rustrakError);\n if (response && !response.ok) {\n // `error` is the parsed body, or its text when it is not JSON.\n return Err(transformHttpError(response, error));\n }\n if (!response && error instanceof ZodError) {\n return Err({\n kind: 'invalid_request',\n message: 'The request input did not match the expected schema.',\n });\n }\n if (response && error instanceof SyntaxError) {\n return Err({\n kind: 'invalid_response',\n message: 'The server returned a body that is not valid JSON.',\n });\n }\n if (response && error instanceof ZodError) {\n // No Zod issues: they embed the offending response data.\n return Err({\n kind: 'invalid_response',\n message: 'The server response did not match the expected schema.',\n });\n }\n throw error;\n}\n"],"mappings":";;;;;;;;AAkBA,MAAM,oBAAyC,IAAI,IAAIA,mBAAAA,MAAM;;;;;;;;;;;;;;;;AAmD7D,MAAa,gBAAgB,MAAM;CACjC,YAAY,QAAsB;EAEhC,OAAO,OAAO,eAAe,UAAU,MAAM,GAAG,WAAW,SAAS;CACtE;AACF;AAIA,SAAS,UAAU,QAAqC;CACtD,MAAM,MAAM,IAAIC,gBAAAA,QAAQ,EACtB,QAAQC,mBAAAA,aACNC,kBAAAA,aAAa;EACX,SAAS,OAAO;EAChB,aAAa;EACb,OAAOC,aAAAA,YAAY,MAAM;EAIzB,SAAS;EACT,SAAS;GAEP,QAAQ;GACR,GAAG,OAAO;GACV,GAAI,OAAO,SAAS,EAAE,eAAe,UAAU,OAAO,QAAQ;EAChE;CACF,CAAC,CACH,EACF,CAAC;CAED,MAAM,MAA+C,CAAC;CACtD,KAAK,MAAM,QAAQ,OAAO,oBAAoBH,gBAAAA,QAAQ,SAAS,GAAG;EAChE,IAAI,SAAS,eAAe;EAC5B,MAAM,WAAW,IAAI;EAIrB,MAAM,UAAmC,CAAC;EAC1C,KAAK,MAAM,UAAU,OAAO,oBAC1B,OAAO,eAAe,QAAQ,CAChC,GAAG;GACD,IAAI,WAAW,eAAe;GAC9B,QAAQ,UAAU,OAAO,UAAiC,CAAC,MACzD,SACE,MAAM,SAAS,OAAO,CAAE;IACtB,GAAG;IAGH,SACE,QAAQ,mBAAmB,WAC3B,MAAM,QAAQ,QAAQ,OAAO,IACzB,OAAO,YAAY,IAAI,QAAQ,QAAQ,OAAO,CAAC,IAC/C,QAAQ;IACd,GAAI,kBAAkB,IAAI,GAAG,KAAK,GAAG,QAAQ,KAAK,EAChD,SAAS,OACX;IACA,cAAc;GAChB,CAAC,CACH;EACJ;EACA,IAAI,QAAQ;CACd;CACA,OAAO;AACT;;;;;;;;;AAgBA,SAAS,SAAS,EAChB,MACA,OACA,YACyC;CACzC,IAAI,UAAU,KAAA,GAAW,OAAOI,eAAAA,GAAG,IAAI;CACvC,IAAI,iBAAiBC,aAAAA,yBAAyB,OAAOC,eAAAA,IAAI,MAAM,YAAY;CAC3E,IAAI,YAAY,CAAC,SAAS,IAExB,OAAOA,eAAAA,IAAIC,aAAAA,mBAAmB,UAAU,KAAK,CAAC;CAEhD,IAAI,CAAC,YAAY,iBAAiBC,IAAAA,UAChC,OAAOF,eAAAA,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiB,aAC/B,OAAOA,eAAAA,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiBE,IAAAA,UAE/B,OAAOF,eAAAA,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,MAAM;AACR"}
package/dist/client.d.cts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { ClientConfig } from "./config.cjs";
2
2
  import { RustrakError } from "./errors.cjs";
3
+ import { BINARY } from "./generated/binary.gen.cjs";
3
4
  import { Rustrak } from "./generated/sdk.gen.cjs";
4
5
  import { Result } from "./result.cjs";
5
- type Binary = 'events.downloadAttachment';
6
+ type Binary = (typeof BINARY)[number];
6
7
  type RequestKeys = 'path' | 'query' | 'body' | 'headers' | 'signal';
7
8
  type Call<F, Bytes extends boolean> = F extends ((options: infer O) => infer R) ? (...args: {} extends Pick<O, Extract<keyof O, RequestKeys>> ? [options?: Pick<O, Extract<keyof O, RequestKeys>>] : [options: Pick<O, Extract<keyof O, RequestKeys>>]) => Promise<Result<Bytes extends true ? Blob : Data<Awaited<R>>, RustrakError>> : never;
8
9
  type Data<R> = Extract<R, {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;;;KAgBK;KAMA;KAEA,KAAK,GAAG,yBAAyB,YAAW,eAAe,YAAY,SAEnE,iBAAiB,KAAK,GAAG,cAAc,GAAG,iBACxC,UAAU,KAAK,GAAG,cAAc,GAAG,kBACnC,SAAS,KAAK,GAAG,cAAc,GAAG,mBACpC,QACH,OAAO,qBAAqB,OAAO,KAAK,QAAQ,KAAK;KAKtD,KAAK,KACR,QAAQ;EAAK;;EAA8B,YAAY;IAAM;KAE1D,SAAS,GAAG,mCACL,WAAW,IAAI,KACvB,EAAE,OACC,QAAQ,qBAAqB;YAUxB,4BACA,WAAW,UAAU,SAAS,QAAQ,IAAI;qBAkBzC,oBAMX,QAAQ,iBACL"}
1
+ {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;KAiBK,iBAAiB;KAIjB;KAEA,KAAK,GAAG,yBAAyB,YAAW,eAAe,YAAY,SAEnE,iBAAiB,KAAK,GAAG,cAAc,GAAG,iBACxC,UAAU,KAAK,GAAG,cAAc,GAAG,kBACnC,SAAS,KAAK,GAAG,cAAc,GAAG,mBACpC,QACH,OAAO,qBAAqB,OAAO,KAAK,QAAQ,KAAK;KAKtD,KAAK,KACR,QAAQ;EAAK;;EAA8B,YAAY;IAAM;KAE1D,SAAS,GAAG,mCACL,WAAW,IAAI,KACvB,EAAE,OACC,QAAQ,qBAAqB;YAUxB,4BACA,WAAW,UAAU,SAAS,QAAQ,IAAI;qBAkBzC,oBAMX,QAAQ,iBACL"}
package/dist/client.d.mts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { ClientConfig } from "./config.mjs";
2
2
  import { RustrakError } from "./errors.mjs";
3
+ import { BINARY } from "./generated/binary.gen.mjs";
3
4
  import { Rustrak } from "./generated/sdk.gen.mjs";
4
5
  import { Result } from "./result.mjs";
5
- type Binary = 'events.downloadAttachment';
6
+ type Binary = (typeof BINARY)[number];
6
7
  type RequestKeys = 'path' | 'query' | 'body' | 'headers' | 'signal';
7
8
  type Call<F, Bytes extends boolean> = F extends ((options: infer O) => infer R) ? (...args: {} extends Pick<O, Extract<keyof O, RequestKeys>> ? [options?: Pick<O, Extract<keyof O, RequestKeys>>] : [options: Pick<O, Extract<keyof O, RequestKeys>>]) => Promise<Result<Bytes extends true ? Blob : Data<Awaited<R>>, RustrakError>> : never;
8
9
  type Data<R> = Extract<R, {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;KAgBK;KAMA;KAEA,KAAK,GAAG,yBAAyB,YAAW,eAAe,YAAY,SAEnE,iBAAiB,KAAK,GAAG,cAAc,GAAG,iBACxC,UAAU,KAAK,GAAG,cAAc,GAAG,kBACnC,SAAS,KAAK,GAAG,cAAc,GAAG,mBACpC,QACH,OAAO,qBAAqB,OAAO,KAAK,QAAQ,KAAK;KAKtD,KAAK,KACR,QAAQ;EAAK;;EAA8B,YAAY;IAAM;KAE1D,SAAS,GAAG,mCACL,WAAW,IAAI,KACvB,EAAE,OACC,QAAQ,qBAAqB;YAUxB,4BACA,WAAW,UAAU,SAAS,QAAQ,IAAI;qBAkBzC,oBAMX,QAAQ,iBACL"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;KAiBK,iBAAiB;KAIjB;KAEA,KAAK,GAAG,yBAAyB,YAAW,eAAe,YAAY,SAEnE,iBAAiB,KAAK,GAAG,cAAc,GAAG,iBACxC,UAAU,KAAK,GAAG,cAAc,GAAG,kBACnC,SAAS,KAAK,GAAG,cAAc,GAAG,mBACpC,QACH,OAAO,qBAAqB,OAAO,KAAK,QAAQ,KAAK;KAKtD,KAAK,KACR,QAAQ;EAAK;;EAA8B,YAAY;IAAM;KAE1D,SAAS,GAAG,mCACL,WAAW,IAAI,KACvB,EAAE,OACC,QAAQ,qBAAqB;YAUxB,4BACA,WAAW,UAAU,SAAS,QAAQ,IAAI;qBAkBzC,oBAMX,QAAQ,iBACL"}
package/dist/client.mjs CHANGED
@@ -1,11 +1,12 @@
1
- import { createConfig } from "./generated/client/utils.gen.mjs";
1
+ import { BINARY } from "./generated/binary.gen.mjs";
2
2
  import { createClient } from "./generated/client/client.gen.mjs";
3
+ import { createConfig } from "./generated/client/utils.gen.mjs";
3
4
  import { Rustrak } from "./generated/sdk.gen.mjs";
4
5
  import { Err, Ok } from "./result.mjs";
5
- import { createFetch, isTransportFailure, transformHttpError } from "./utils/http.mjs";
6
+ import { RustrakTransportFailure, createFetch, transformHttpError } from "./utils/http.mjs";
6
7
  import { ZodError } from "zod";
7
8
  // src/client.ts
8
- const BINARY = /* @__PURE__ */ new Set(["events.downloadAttachment"]);
9
+ const BINARY_OPERATIONS = new Set(BINARY);
9
10
  /**
10
11
  * Main Rustrak API client. Every method returns a `Result`; nothing throws for
11
12
  * an expected failure.
@@ -48,7 +49,7 @@ function createApi(config) {
48
49
  methods[method] = async (options = {}) => toResult(await resource[method]({
49
50
  ...options,
50
51
  headers: options.headers instanceof Headers || Array.isArray(options.headers) ? Object.fromEntries(new Headers(options.headers)) : options.headers,
51
- ...BINARY.has(`${name}.${method}`) && { parseAs: "blob" },
52
+ ...BINARY_OPERATIONS.has(`${name}.${method}`) && { parseAs: "blob" },
52
53
  throwOnError: false
53
54
  }));
54
55
  }
@@ -66,7 +67,7 @@ function createApi(config) {
66
67
  */
67
68
  function toResult({ data, error, response }) {
68
69
  if (error === void 0) return Ok(data);
69
- if (isTransportFailure(error)) return Err(error.rustrakError);
70
+ if (error instanceof RustrakTransportFailure) return Err(error.rustrakError);
70
71
  if (response && !response.ok) return Err(transformHttpError(response, error));
71
72
  if (!response && error instanceof ZodError) return Err({
72
73
  kind: "invalid_request",
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { ZodError } from 'zod';\nimport type { ClientConfig } from './config.js';\nimport type { RustrakError } from './errors.js';\nimport { createClient, createConfig } from './generated/client/index.js';\nimport { Rustrak } from './generated/sdk.gen.js';\nimport { Err, Ok, type Result } from './result.js';\nimport {\n createFetch,\n isTransportFailure,\n transformHttpError,\n} from './utils/http.js';\n\n/**\n * Operations whose 2xx is a file, not JSON: read as a `Blob`. The spec leaves\n * the body untyped (zod has no binary type), so the type comes from here.\n */\ntype Binary = 'events.downloadAttachment';\nconst BINARY: ReadonlySet<string> = new Set<Binary>([\n 'events.downloadAttachment',\n]);\n\n/** What a caller may pass: the request's own parts, never client plumbing. */\ntype RequestKeys = 'path' | 'query' | 'body' | 'headers' | 'signal';\n\ntype Call<F, Bytes extends boolean> = F extends (options: infer O) => infer R\n ? (\n ...args: {} extends Pick<O, Extract<keyof O, RequestKeys>>\n ? [options?: Pick<O, Extract<keyof O, RequestKeys>>]\n : [options: Pick<O, Extract<keyof O, RequestKeys>>]\n ) => Promise<\n Result<Bytes extends true ? Blob : Data<Awaited<R>>, RustrakError>\n >\n : never;\n\n/** The success half of the generated `{ data, error }` union. */\ntype Data<R> =\n Extract<R, { error: undefined }> extends { data: infer D } ? D : never;\n\ntype Resource<T, Name extends string> = {\n readonly [M in keyof T]: Call<\n T[M],\n `${Name}.${M & string}` extends Binary ? true : false\n >;\n};\n\n/**\n * Every operation in `openapi.json`, grouped by tag:\n * `client.issues.list({ path: { project_id } })`.\n *\n * Generated in `src/generated/`; this file only changes what a call returns.\n */\nexport type RustrakClient = {\n readonly [R in keyof Rustrak]: Resource<Rustrak[R], R & string>;\n};\n\n/**\n * Main Rustrak API client. Every method returns a `Result`; nothing throws for\n * an expected failure.\n *\n * @example\n * ```typescript\n * const client = new RustrakClient({\n * baseUrl: 'http://localhost:8080',\n * token: 'your-api-token',\n * });\n *\n * const issues = await client.issues.list({ path: { project_id: 1 } });\n * if (issues.success) console.log(issues.data.items);\n * ```\n */\nexport const RustrakClient = class {\n constructor(config: ClientConfig) {\n // biome-ignore lint/correctness/noConstructorReturn: the instance is the generated SDK with every method wrapped\n return Object.setPrototypeOf(createApi(config), new.target.prototype);\n }\n} as unknown as new (\n config: ClientConfig,\n) => RustrakClient;\n\nfunction createApi(config: ClientConfig): RustrakClient {\n const sdk = new Rustrak({\n client: createClient(\n createConfig({\n baseUrl: config.baseUrl,\n credentials: 'include',\n fetch: createFetch(config),\n // Every 2xx in the spec is JSON or empty, except the files in\n // `BINARY`. `auto` would hand back an HTML page or a stream as a\n // success without validating it.\n parseAs: 'json',\n headers: {\n // Without it a content-negotiating hop can answer with HTML.\n Accept: 'application/json',\n ...config.headers,\n ...(config.token && { Authorization: `Bearer ${config.token}` }),\n },\n }),\n ),\n });\n\n const api: Record<string, Record<string, unknown>> = {};\n for (const name of Object.getOwnPropertyNames(Rustrak.prototype)) {\n if (name === 'constructor') continue;\n const resource = sdk[name as keyof Rustrak] as unknown as Record<\n string,\n (options: object) => Promise<Outcome>\n >;\n const methods: Record<string, unknown> = {};\n for (const method of Object.getOwnPropertyNames(\n Object.getPrototypeOf(resource),\n )) {\n if (method === 'constructor') continue;\n methods[method] = async (options: { headers?: unknown } = {}) =>\n toResult(\n await resource[method]!({\n ...options,\n // A JSON write spreads these into an object, which keeps nothing\n // of a `Headers` or an array of pairs.\n headers:\n options.headers instanceof Headers ||\n Array.isArray(options.headers)\n ? Object.fromEntries(new Headers(options.headers))\n : options.headers,\n ...(BINARY.has(`${name}.${method}`) && { parseAs: 'blob' }),\n throwOnError: false,\n }),\n );\n }\n api[name] = methods;\n }\n return api as unknown as RustrakClient;\n}\n\ntype Outcome = {\n data?: unknown;\n error?: unknown;\n response?: Response;\n};\n\n/**\n * The boundary: the generated client's `{ data, error, response }` becomes a\n * `Result`.\n *\n * Only failures this file recognises become an `Err`. Anything else, a\n * `TypeError` from a genuine bug, is rethrown so it crashes where it can be\n * debugged instead of being laundered into `{ success: false }`.\n */\nfunction toResult({\n data,\n error,\n response,\n}: Outcome): Result<unknown, RustrakError> {\n if (error === undefined) return Ok(data);\n if (isTransportFailure(error)) return Err(error.rustrakError);\n if (response && !response.ok) {\n // `error` is the parsed body, or its text when it is not JSON.\n return Err(transformHttpError(response, error));\n }\n if (!response && error instanceof ZodError) {\n return Err({\n kind: 'invalid_request',\n message: 'The request input did not match the expected schema.',\n });\n }\n if (response && error instanceof SyntaxError) {\n return Err({\n kind: 'invalid_response',\n message: 'The server returned a body that is not valid JSON.',\n });\n }\n if (response && error instanceof ZodError) {\n // No Zod issues: they embed the offending response data.\n return Err({\n kind: 'invalid_response',\n message: 'The server response did not match the expected schema.',\n });\n }\n throw error;\n}\n"],"mappings":";;;;;;;AAiBA,MAAM,yBAA8B,IAAI,IAAY,CAClD,2BACF,CAAC;;;;;;;;;;;;;;;;AAmDD,MAAa,gBAAgB,MAAM;CACjC,YAAY,QAAsB;EAEhC,OAAO,OAAO,eAAe,UAAU,MAAM,GAAG,WAAW,SAAS;CACtE;AACF;AAIA,SAAS,UAAU,QAAqC;CACtD,MAAM,MAAM,IAAI,QAAQ,EACtB,QAAQ,aACN,aAAa;EACX,SAAS,OAAO;EAChB,aAAa;EACb,OAAO,YAAY,MAAM;EAIzB,SAAS;EACT,SAAS;GAEP,QAAQ;GACR,GAAG,OAAO;GACV,GAAI,OAAO,SAAS,EAAE,eAAe,UAAU,OAAO,QAAQ;EAChE;CACF,CAAC,CACH,EACF,CAAC;CAED,MAAM,MAA+C,CAAC;CACtD,KAAK,MAAM,QAAQ,OAAO,oBAAoB,QAAQ,SAAS,GAAG;EAChE,IAAI,SAAS,eAAe;EAC5B,MAAM,WAAW,IAAI;EAIrB,MAAM,UAAmC,CAAC;EAC1C,KAAK,MAAM,UAAU,OAAO,oBAC1B,OAAO,eAAe,QAAQ,CAChC,GAAG;GACD,IAAI,WAAW,eAAe;GAC9B,QAAQ,UAAU,OAAO,UAAiC,CAAC,MACzD,SACE,MAAM,SAAS,OAAO,CAAE;IACtB,GAAG;IAGH,SACE,QAAQ,mBAAmB,WAC3B,MAAM,QAAQ,QAAQ,OAAO,IACzB,OAAO,YAAY,IAAI,QAAQ,QAAQ,OAAO,CAAC,IAC/C,QAAQ;IACd,GAAI,OAAO,IAAI,GAAG,KAAK,GAAG,QAAQ,KAAK,EAAE,SAAS,OAAO;IACzD,cAAc;GAChB,CAAC,CACH;EACJ;EACA,IAAI,QAAQ;CACd;CACA,OAAO;AACT;;;;;;;;;AAgBA,SAAS,SAAS,EAChB,MACA,OACA,YACyC;CACzC,IAAI,UAAU,KAAA,GAAW,OAAO,GAAG,IAAI;CACvC,IAAI,mBAAmB,KAAK,GAAG,OAAO,IAAI,MAAM,YAAY;CAC5D,IAAI,YAAY,CAAC,SAAS,IAExB,OAAO,IAAI,mBAAmB,UAAU,KAAK,CAAC;CAEhD,IAAI,CAAC,YAAY,iBAAiB,UAChC,OAAO,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiB,aAC/B,OAAO,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiB,UAE/B,OAAO,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,MAAM;AACR"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { ZodError } from 'zod';\nimport type { ClientConfig } from './config.js';\nimport type { RustrakError } from './errors.js';\nimport { BINARY } from './generated/binary.gen.js';\nimport { createClient, createConfig } from './generated/client/index.js';\nimport { Rustrak } from './generated/sdk.gen.js';\nimport { Err, Ok, type Result } from './result.js';\nimport {\n createFetch,\n RustrakTransportFailure,\n transformHttpError,\n} from './utils/http.js';\n\n/**\n * Operations whose 2xx is a file, read as a `Blob`. The spec leaves the body\n * untyped (zod has no binary type), so the type comes from this list.\n */\ntype Binary = (typeof BINARY)[number];\nconst BINARY_OPERATIONS: ReadonlySet<string> = new Set(BINARY);\n\n/** What a caller may pass: the request's own parts, never client plumbing. */\ntype RequestKeys = 'path' | 'query' | 'body' | 'headers' | 'signal';\n\ntype Call<F, Bytes extends boolean> = F extends (options: infer O) => infer R\n ? (\n ...args: {} extends Pick<O, Extract<keyof O, RequestKeys>>\n ? [options?: Pick<O, Extract<keyof O, RequestKeys>>]\n : [options: Pick<O, Extract<keyof O, RequestKeys>>]\n ) => Promise<\n Result<Bytes extends true ? Blob : Data<Awaited<R>>, RustrakError>\n >\n : never;\n\n/** The success half of the generated `{ data, error }` union. */\ntype Data<R> =\n Extract<R, { error: undefined }> extends { data: infer D } ? D : never;\n\ntype Resource<T, Name extends string> = {\n readonly [M in keyof T]: Call<\n T[M],\n `${Name}.${M & string}` extends Binary ? true : false\n >;\n};\n\n/**\n * Every operation in `openapi.json`, grouped by tag:\n * `client.issues.list({ path: { project_id } })`.\n *\n * Generated in `src/generated/`; this file only changes what a call returns.\n */\nexport type RustrakClient = {\n readonly [R in keyof Rustrak]: Resource<Rustrak[R], R & string>;\n};\n\n/**\n * Main Rustrak API client. Every method returns a `Result`; nothing throws for\n * an expected failure.\n *\n * @example\n * ```typescript\n * const client = new RustrakClient({\n * baseUrl: 'http://localhost:8080',\n * token: 'your-api-token',\n * });\n *\n * const issues = await client.issues.list({ path: { project_id: 1 } });\n * if (issues.success) console.log(issues.data.items);\n * ```\n */\nexport const RustrakClient = class {\n constructor(config: ClientConfig) {\n // biome-ignore lint/correctness/noConstructorReturn: the instance is the generated SDK with every method wrapped\n return Object.setPrototypeOf(createApi(config), new.target.prototype);\n }\n} as unknown as new (\n config: ClientConfig,\n) => RustrakClient;\n\nfunction createApi(config: ClientConfig): RustrakClient {\n const sdk = new Rustrak({\n client: createClient(\n createConfig({\n baseUrl: config.baseUrl,\n credentials: 'include',\n fetch: createFetch(config),\n // Every 2xx in the spec is JSON or empty, except the files in\n // `BINARY`. `auto` would hand back an HTML page or a stream as a\n // success without validating it.\n parseAs: 'json',\n headers: {\n // Without it a content-negotiating hop can answer with HTML.\n Accept: 'application/json',\n ...config.headers,\n ...(config.token && { Authorization: `Bearer ${config.token}` }),\n },\n }),\n ),\n });\n\n const api: Record<string, Record<string, unknown>> = {};\n for (const name of Object.getOwnPropertyNames(Rustrak.prototype)) {\n if (name === 'constructor') continue;\n const resource = sdk[name as keyof Rustrak] as unknown as Record<\n string,\n (options: object) => Promise<Outcome>\n >;\n const methods: Record<string, unknown> = {};\n for (const method of Object.getOwnPropertyNames(\n Object.getPrototypeOf(resource),\n )) {\n if (method === 'constructor') continue;\n methods[method] = async (options: { headers?: unknown } = {}) =>\n toResult(\n await resource[method]!({\n ...options,\n // A JSON write spreads these into an object, which keeps nothing\n // of a `Headers` or an array of pairs.\n headers:\n options.headers instanceof Headers ||\n Array.isArray(options.headers)\n ? Object.fromEntries(new Headers(options.headers))\n : options.headers,\n ...(BINARY_OPERATIONS.has(`${name}.${method}`) && {\n parseAs: 'blob',\n }),\n throwOnError: false,\n }),\n );\n }\n api[name] = methods;\n }\n return api as unknown as RustrakClient;\n}\n\ntype Outcome = {\n data?: unknown;\n error?: unknown;\n response?: Response;\n};\n\n/**\n * The boundary: the generated client's `{ data, error, response }` becomes a\n * `Result`.\n *\n * Only failures this file recognises become an `Err`. Anything else, a\n * `TypeError` from a genuine bug, is rethrown so it crashes where it can be\n * debugged instead of being laundered into `{ success: false }`.\n */\nfunction toResult({\n data,\n error,\n response,\n}: Outcome): Result<unknown, RustrakError> {\n if (error === undefined) return Ok(data);\n if (error instanceof RustrakTransportFailure) return Err(error.rustrakError);\n if (response && !response.ok) {\n // `error` is the parsed body, or its text when it is not JSON.\n return Err(transformHttpError(response, error));\n }\n if (!response && error instanceof ZodError) {\n return Err({\n kind: 'invalid_request',\n message: 'The request input did not match the expected schema.',\n });\n }\n if (response && error instanceof SyntaxError) {\n return Err({\n kind: 'invalid_response',\n message: 'The server returned a body that is not valid JSON.',\n });\n }\n if (response && error instanceof ZodError) {\n // No Zod issues: they embed the offending response data.\n return Err({\n kind: 'invalid_response',\n message: 'The server response did not match the expected schema.',\n });\n }\n throw error;\n}\n"],"mappings":";;;;;;;;AAkBA,MAAM,oBAAyC,IAAI,IAAI,MAAM;;;;;;;;;;;;;;;;AAmD7D,MAAa,gBAAgB,MAAM;CACjC,YAAY,QAAsB;EAEhC,OAAO,OAAO,eAAe,UAAU,MAAM,GAAG,WAAW,SAAS;CACtE;AACF;AAIA,SAAS,UAAU,QAAqC;CACtD,MAAM,MAAM,IAAI,QAAQ,EACtB,QAAQ,aACN,aAAa;EACX,SAAS,OAAO;EAChB,aAAa;EACb,OAAO,YAAY,MAAM;EAIzB,SAAS;EACT,SAAS;GAEP,QAAQ;GACR,GAAG,OAAO;GACV,GAAI,OAAO,SAAS,EAAE,eAAe,UAAU,OAAO,QAAQ;EAChE;CACF,CAAC,CACH,EACF,CAAC;CAED,MAAM,MAA+C,CAAC;CACtD,KAAK,MAAM,QAAQ,OAAO,oBAAoB,QAAQ,SAAS,GAAG;EAChE,IAAI,SAAS,eAAe;EAC5B,MAAM,WAAW,IAAI;EAIrB,MAAM,UAAmC,CAAC;EAC1C,KAAK,MAAM,UAAU,OAAO,oBAC1B,OAAO,eAAe,QAAQ,CAChC,GAAG;GACD,IAAI,WAAW,eAAe;GAC9B,QAAQ,UAAU,OAAO,UAAiC,CAAC,MACzD,SACE,MAAM,SAAS,OAAO,CAAE;IACtB,GAAG;IAGH,SACE,QAAQ,mBAAmB,WAC3B,MAAM,QAAQ,QAAQ,OAAO,IACzB,OAAO,YAAY,IAAI,QAAQ,QAAQ,OAAO,CAAC,IAC/C,QAAQ;IACd,GAAI,kBAAkB,IAAI,GAAG,KAAK,GAAG,QAAQ,KAAK,EAChD,SAAS,OACX;IACA,cAAc;GAChB,CAAC,CACH;EACJ;EACA,IAAI,QAAQ;CACd;CACA,OAAO;AACT;;;;;;;;;AAgBA,SAAS,SAAS,EAChB,MACA,OACA,YACyC;CACzC,IAAI,UAAU,KAAA,GAAW,OAAO,GAAG,IAAI;CACvC,IAAI,iBAAiB,yBAAyB,OAAO,IAAI,MAAM,YAAY;CAC3E,IAAI,YAAY,CAAC,SAAS,IAExB,OAAO,IAAI,mBAAmB,UAAU,KAAK,CAAC;CAEhD,IAAI,CAAC,YAAY,iBAAiB,UAChC,OAAO,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiB,aAC/B,OAAO,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,IAAI,YAAY,iBAAiB,UAE/B,OAAO,IAAI;EACT,MAAM;EACN,SAAS;CACX,CAAC;CAEH,MAAM;AACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":"A,iBAGiB;EAKf;EAOA;EAMA;EAMA;EAKA,UAAU"}
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":"A,iBAAiB;EAEf;EAGA;EAMA;EAMA;EAEA,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":"A,iBAGiB;EAKf;EAOA;EAMA;EAMA;EAKA,UAAU"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":"A,iBAAiB;EAEf;EAGA;EAMA;EAMA;EAEA,UAAU"}
package/dist/errors.cjs CHANGED
@@ -16,6 +16,7 @@ const FIELD_ERROR_CODES = [
16
16
  "already_exists",
17
17
  "too_short",
18
18
  "too_long",
19
+ "incorrect",
19
20
  "custom"
20
21
  ];
21
22
  /**
@@ -33,19 +34,31 @@ const NETWORK_ERROR_MESSAGE = "The request could not reach the server.";
33
34
  /** The fixed message a `network` error carries when the client timed out. */
34
35
  const TIMEOUT_ERROR_MESSAGE = "The request timed out.";
35
36
  /**
36
- * Whether retrying the same call could plausibly succeed.
37
- *
38
- * `network` and `server_error` are transient by nature; `rate_limited` clears
39
- * once its window rolls over. Everything else is deterministic: the same
40
- * request will fail the same way.
37
+ * Statuses where the same request may well succeed a moment later. The one
38
+ * rule behind {@link isRetryable} and the transport's automatic retries.
39
+ */
40
+ const TRANSIENT_STATUSES = /* @__PURE__ */ new Set([
41
+ 408,
42
+ 429,
43
+ 500,
44
+ 502,
45
+ 503,
46
+ 504
47
+ ]);
48
+ /**
49
+ * Whether retrying the same call could plausibly succeed: a request that never
50
+ * got a response, or a transient status. A `rate_limited` error says how long
51
+ * to wait in `retryAfter`. Everything else is deterministic: the same request
52
+ * will fail the same way.
41
53
  */
42
54
  function isRetryable(error) {
43
- return error.kind === "network" || error.kind === "server_error" || error.kind === "rate_limited";
55
+ return error.kind === "network" || "status" in error && TRANSIENT_STATUSES.has(error.status);
44
56
  }
45
57
  exports.FIELD_ERROR_CODES = FIELD_ERROR_CODES;
46
58
  exports.NETWORK_ERROR_MESSAGE = NETWORK_ERROR_MESSAGE;
47
59
  exports.SERVER_ERROR_MESSAGE = SERVER_ERROR_MESSAGE;
48
60
  exports.TIMEOUT_ERROR_MESSAGE = TIMEOUT_ERROR_MESSAGE;
61
+ exports.TRANSIENT_STATUSES = TRANSIENT_STATUSES;
49
62
  exports.isRetryable = isRetryable;
50
63
 
51
64
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * The one closed error union `@rustrak/client` reports.\n *\n * It mirrors `apps/server/src/error.rs` variant by variant, plus the three\n * failures that never reach the server (`invalid_request`, `network`,\n * `invalid_response`) and a catch-all so an unanticipated status is still\n * representable rather than unrepresentable.\n *\n * Every member is a plain object: no class, no prototype chain, no `Error`\n * instance. That is what lets a failed `Result` survive `structuredClone` and\n * therefore React's server/client boundary.\n *\n * `status` is `number`, never a literal union. Pinning literals would make a\n * proxy-generated 502 or a future status a type error at the call site rather\n * than a value the consumer can log.\n *\n * `fields` appears on every member that can carry an `AppError` body, because\n * `AppError::with_field` works on all eight Rust variants: a `Forbidden` or a\n * `NotFound` may name an input just as a `Conflict` does. Only the members\n * that never carry a server body at all (`server_error`, which discards it,\n * plus `invalid_request`, `network` and `invalid_response`, which never had\n * one) omit it. See {@link FieldError}.\n */\nexport type RustrakError =\n /**\n * 400. `AppError::Validation`: the server rejected the request.\n *\n * `fields` is present only when the server named the offending inputs; a\n * consumer that ignores it behaves exactly as it did before.\n */\n | {\n readonly kind: 'validation';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 401. `AppError::Unauthorized`: no session, or the session expired. */\n | {\n readonly kind: 'unauthenticated';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 403. `AppError::Forbidden`: authenticated, but not allowed. */\n | {\n readonly kind: 'forbidden';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 404. `AppError::NotFound`. */\n | {\n readonly kind: 'not_found';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /**\n * 409. `AppError::Conflict`.\n *\n * Where `fields` earns its keep: uniqueness is the thing only the server can\n * know, and a taken name or slug is a 409, not a 400.\n */\n | {\n readonly kind: 'conflict';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 410. Reserved for endpoints that retire a resource rather than delete it. */\n | {\n readonly kind: 'gone';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 413. `AppError::PayloadTooLarge`: envelope ingestion only. */\n | {\n readonly kind: 'payload_too_large';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 429. The ingest rate limiter (`routes/ingest.rs`), the one flat body. */\n | {\n readonly kind: 'rate_limited';\n readonly status: number;\n readonly message: string;\n /** Seconds to wait, from the `Retry-After` header, when the server sent one. */\n readonly retryAfter?: number;\n readonly fields?: readonly FieldError[];\n }\n /**\n * Any status below 500 that no other member claims. The catch-all that keeps\n * the union closed but total.\n *\n * Not only 4xx: a 3xx that reached the error path (a redirect ky did not\n * follow), a 408, an unenumerated 4xx, and an opaque `status: 0` all land\n * here. `status` is the field to read; `kind` only says \"no dedicated\n * handling exists for this one\".\n */\n | {\n readonly kind: 'client_error';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /**\n * The caller's own input failed a pre-flight check. Never reached the\n * network, so it has no status.\n */\n | { readonly kind: 'invalid_request'; readonly message: string }\n /**\n * Any 5xx. `AppError::Database` and `AppError::Internal` both land here, and\n * so does anything a reverse proxy generates.\n *\n * `message` is always {@link SERVER_ERROR_MESSAGE}: the server's body is\n * discarded at construction, inside the client, so no consumer can leak a\n * connection string or a filesystem path into a UI. `incidentId` is the\n * forward-compatible slot for a correlation id the server does not send yet.\n */\n | {\n readonly kind: 'server_error';\n readonly status: number;\n readonly message: string;\n readonly incidentId?: string;\n }\n /**\n * The request never got an HTTP response: DNS, connection refused, TLS,\n * timeout, abort. Also a connection that died while the body was still being\n * read, which is a transport failure even though a status line arrived.\n *\n * `message` is always {@link NETWORK_ERROR_MESSAGE} or\n * {@link TIMEOUT_ERROR_MESSAGE}, never the underlying error's own: fetch\n * builds that message by interpolating the request method and URL\n * (`Request failed due to a network error: GET http://rustrak.internal:8080/api/projects`),\n * so echoing it prints a self-hosted deployment's internal host and port into\n * any UI that renders `error.message`.\n *\n * Carries no `cause` for the same reason. `reason` is the machine-readable\n * discriminator a caller needs instead, derived from the error's `name` and\n * never from its message.\n */\n | {\n readonly kind: 'network';\n readonly message: string;\n /** `timeout` when the client gave up waiting; `unreachable` otherwise. */\n readonly reason: 'timeout' | 'unreachable';\n }\n /**\n * A 2xx arrived but the body is not what the schema promises: not JSON, or\n * JSON of the wrong shape.\n *\n * Carries no Zod issues: they embed the offending response data.\n */\n | { readonly kind: 'invalid_response'; readonly message: string };\n\n/** The `kind` discriminant, useful for exhaustive `switch` in consumers. */\nexport type RustrakErrorKind = RustrakError['kind'];\n\n/**\n * Every `code` a {@link FieldError} may carry, mirroring the `FieldErrorCode`\n * enum in `apps/server/src/error.rs`.\n *\n * Deliberately tiny and resource-agnostic. `tests/unit/app-error-contract.test.ts`\n * parses that Rust enum and fails if this list stops matching it, so a variant\n * added on the server cannot ship without a matching entry here.\n *\n * Exported as a value, not only a type, so a consumer can exhaustively switch\n * or validate against it at runtime.\n */\nexport const FIELD_ERROR_CODES = [\n 'required',\n 'invalid',\n 'already_exists',\n 'too_short',\n 'too_long',\n 'custom',\n] as const;\n\n/** The `code` of a {@link FieldError}. */\nexport type FieldErrorCode = (typeof FIELD_ERROR_CODES)[number];\n\n/**\n * One input the server rejected, named as data.\n *\n * `field` is a **dot path into the request body** (`slug`,\n * `credentials.webhook_url`), which is exactly what a form library's\n * `setError` takes.\n *\n * Select the copy shown to the user from `(field, code)` and never from\n * `message`: that is what makes it translatable. `message` is populated only\n * for `code: 'custom'`, where the code set genuinely cannot express the\n * reason, and is then rendered verbatim. The client strips a `message` that\n * arrives on any other code, so the rule holds even against a server that\n * breaks it.\n *\n * The same `(field, code)` pair can mean two things at two statuses:\n * `(role, 'invalid')` is \"not a role at all\" on a 400 and \"a real role, but\n * not acceptable right now\" on a 409. Branch on `kind` as well when the copy\n * has to differ.\n *\n * A `field` naming an input the form does not have must fall back to a\n * form-level error. Passing an unknown path to `setError` registers a\n * phantom field that nothing can ever clear.\n */\nexport type FieldError = {\n readonly field: string;\n readonly code: FieldErrorCode;\n readonly message?: string;\n};\n\n/**\n * The fixed message every `server_error` carries. Exported so a consumer (or a\n * test) can assert that nothing server-supplied survived the 5xx redaction.\n */\nexport const SERVER_ERROR_MESSAGE = 'The server failed to handle the request.';\n\n/**\n * The fixed message a `network` error carries when the request never reached\n * the server. Fixed for the same reason as {@link SERVER_ERROR_MESSAGE}: the\n * underlying message interpolates the request URL, which is the deployment's\n * internal host and port. Read `reason` to branch, not `message`.\n */\nexport const NETWORK_ERROR_MESSAGE = 'The request could not reach the server.';\n\n/** The fixed message a `network` error carries when the client timed out. */\nexport const TIMEOUT_ERROR_MESSAGE = 'The request timed out.';\n\n/**\n * Whether retrying the same call could plausibly succeed.\n *\n * `network` and `server_error` are transient by nature; `rate_limited` clears\n * once its window rolls over. Everything else is deterministic: the same\n * request will fail the same way.\n */\nexport function isRetryable(error: RustrakError): boolean {\n return (\n error.kind === 'network' ||\n error.kind === 'server_error' ||\n error.kind === 'rate_limited'\n );\n}\n"],"mappings":";;;;;;;;;;;;AA2KA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAsCA,MAAa,uBAAuB;;;;;;;AAQpC,MAAa,wBAAwB;;AAGrC,MAAa,wBAAwB;;;;;;;;AASrC,SAAgB,YAAY,OAA8B;CACxD,OACE,MAAM,SAAS,aACf,MAAM,SAAS,kBACf,MAAM,SAAS;AAEnB"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * The one closed error union `@rustrak/client` reports.\n *\n * It mirrors `apps/server/src/error.rs` variant by variant, plus the three\n * failures that never reach the server (`invalid_request`, `network`,\n * `invalid_response`) and a catch-all so an unanticipated status is still\n * representable rather than unrepresentable.\n *\n * Every member is a plain object: no class, no prototype chain, no `Error`\n * instance. That is what lets a failed `Result` survive `structuredClone` and\n * therefore React's server/client boundary.\n *\n * `status` is `number`, never a literal union. Pinning literals would make a\n * proxy-generated 502 or a future status a type error at the call site rather\n * than a value the consumer can log.\n *\n * `fields` appears on every member that can carry an `AppError` body, because\n * `AppError::with_field` works on all eight Rust variants: a `Forbidden` or a\n * `NotFound` may name an input just as a `Conflict` does. Only the members\n * that never carry a server body at all (`server_error`, which discards it,\n * plus `invalid_request`, `network` and `invalid_response`, which never had\n * one) omit it. See {@link FieldError}.\n */\nexport type RustrakError =\n /**\n * 400. `AppError::Validation`: the server rejected the request.\n *\n * `fields` is present only when the server named the offending inputs; a\n * consumer that ignores it behaves exactly as it did before.\n */\n | {\n readonly kind: 'validation';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 401. `AppError::Unauthorized`: no session, or the session expired. */\n | {\n readonly kind: 'unauthenticated';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 403. `AppError::Forbidden`: authenticated, but not allowed. */\n | {\n readonly kind: 'forbidden';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 404. `AppError::NotFound`. */\n | {\n readonly kind: 'not_found';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /**\n * 409. `AppError::Conflict`.\n *\n * Where `fields` earns its keep: uniqueness is the thing only the server can\n * know, and a taken name or slug is a 409, not a 400.\n */\n | {\n readonly kind: 'conflict';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 410. Reserved for endpoints that retire a resource rather than delete it. */\n | {\n readonly kind: 'gone';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 413. `AppError::PayloadTooLarge`: envelope ingestion only. */\n | {\n readonly kind: 'payload_too_large';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /** 429. The ingest rate limiter (`routes/ingest.rs`), the one flat body. */\n | {\n readonly kind: 'rate_limited';\n readonly status: number;\n readonly message: string;\n /** Seconds to wait, from the `Retry-After` header, when the server sent one. */\n readonly retryAfter?: number;\n readonly fields?: readonly FieldError[];\n }\n /**\n * Any status below 500 that no other member claims. The catch-all that keeps\n * the union closed but total.\n *\n * Not only 4xx: a 3xx that reached the error path (a redirect `fetch` did\n * not follow), a 408, an unenumerated 4xx, and an opaque `status: 0` all land\n * here. `status` is the field to read; `kind` only says \"no dedicated\n * handling exists for this one\".\n */\n | {\n readonly kind: 'client_error';\n readonly status: number;\n readonly message: string;\n readonly fields?: readonly FieldError[];\n }\n /**\n * The caller's own input failed a pre-flight check. Never reached the\n * network, so it has no status.\n */\n | { readonly kind: 'invalid_request'; readonly message: string }\n /**\n * Any 5xx. `AppError::Database` and `AppError::Internal` both land here, and\n * so does anything a reverse proxy generates.\n *\n * `message` is always {@link SERVER_ERROR_MESSAGE}: the server's body is\n * discarded at construction, inside the client, so no consumer can leak a\n * connection string or a filesystem path into a UI. `incidentId` names the\n * server log line that holds what was discarded, when the body carries one.\n */\n | {\n readonly kind: 'server_error';\n readonly status: number;\n readonly message: string;\n readonly incidentId?: string;\n }\n /**\n * The request never got an HTTP response: DNS, connection refused, TLS,\n * timeout, abort. Also a connection that died while the body was still being\n * read, which is a transport failure even though a status line arrived.\n *\n * `message` is always {@link NETWORK_ERROR_MESSAGE} or\n * {@link TIMEOUT_ERROR_MESSAGE}, never the underlying error's own: fetch\n * builds that message by interpolating the request method and URL\n * (`Request failed due to a network error: GET http://rustrak.internal:8080/api/projects`),\n * so echoing it prints a self-hosted deployment's internal host and port into\n * any UI that renders `error.message`.\n *\n * Carries no `cause` for the same reason. `reason` is the machine-readable\n * discriminator a caller needs instead, derived from the error's `name` and\n * never from its message.\n */\n | {\n readonly kind: 'network';\n readonly message: string;\n /** `timeout` when the client gave up waiting; `unreachable` otherwise. */\n readonly reason: 'timeout' | 'unreachable';\n }\n /**\n * A 2xx arrived but the body is not what the schema promises: not JSON, or\n * JSON of the wrong shape.\n *\n * Carries no Zod issues: they embed the offending response data.\n */\n | { readonly kind: 'invalid_response'; readonly message: string };\n\nexport type RustrakErrorKind = RustrakError['kind'];\n\n/**\n * Every `code` a {@link FieldError} may carry, mirroring the `FieldErrorCode`\n * enum in `apps/server/src/error.rs`.\n *\n * Deliberately tiny and resource-agnostic. `tests/unit/app-error-contract.test.ts`\n * parses that Rust enum and fails if this list stops matching it, so a variant\n * added on the server cannot ship without a matching entry here.\n *\n * Exported as a value, not only a type, so a consumer can exhaustively switch\n * or validate against it at runtime.\n */\nexport const FIELD_ERROR_CODES = [\n 'required',\n 'invalid',\n 'already_exists',\n 'too_short',\n 'too_long',\n 'incorrect',\n 'custom',\n] as const;\n\nexport type FieldErrorCode = (typeof FIELD_ERROR_CODES)[number];\n\n/**\n * One input the server rejected, named as data.\n *\n * `field` is a **dot path into the request body** (`slug`,\n * `credentials.webhook_url`), which is exactly what a form library's\n * `setError` takes.\n *\n * Select the copy shown to the user from `(field, code)` and never from\n * `message`: that is what makes it translatable. `message` is populated only\n * for `code: 'custom'`, where the code set genuinely cannot express the\n * reason, and is then rendered verbatim. The client strips a `message` that\n * arrives on any other code, so the rule holds even against a server that\n * breaks it.\n *\n * The same `(field, code)` pair can mean two things at two statuses:\n * `(role, 'invalid')` is \"not a role at all\" on a 400 and \"a real role, but\n * not acceptable right now\" on a 409. Branch on `kind` as well when the copy\n * has to differ.\n *\n * A `field` naming an input the form does not have must fall back to a\n * form-level error. Passing an unknown path to `setError` registers a\n * phantom field that nothing can ever clear.\n */\nexport type FieldError = {\n readonly field: string;\n readonly code: FieldErrorCode;\n readonly message?: string;\n};\n\n/**\n * The fixed message every `server_error` carries. Exported so a consumer (or a\n * test) can assert that nothing server-supplied survived the 5xx redaction.\n */\nexport const SERVER_ERROR_MESSAGE = 'The server failed to handle the request.';\n\n/**\n * The fixed message a `network` error carries when the request never reached\n * the server. Fixed for the same reason as {@link SERVER_ERROR_MESSAGE}: the\n * underlying message interpolates the request URL, which is the deployment's\n * internal host and port. Read `reason` to branch, not `message`.\n */\nexport const NETWORK_ERROR_MESSAGE = 'The request could not reach the server.';\n\n/** The fixed message a `network` error carries when the client timed out. */\nexport const TIMEOUT_ERROR_MESSAGE = 'The request timed out.';\n\n/**\n * Statuses where the same request may well succeed a moment later. The one\n * rule behind {@link isRetryable} and the transport's automatic retries.\n */\nexport const TRANSIENT_STATUSES: ReadonlySet<number> = new Set([\n 408, 429, 500, 502, 503, 504,\n]);\n\n/**\n * Whether retrying the same call could plausibly succeed: a request that never\n * got a response, or a transient status. A `rate_limited` error says how long\n * to wait in `retryAfter`. Everything else is deterministic: the same request\n * will fail the same way.\n */\nexport function isRetryable(error: RustrakError): boolean {\n return (\n error.kind === 'network' ||\n ('status' in error && TRANSIENT_STATUSES.has(error.status))\n );\n}\n"],"mappings":";;;;;;;;;;;;AA0KA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAqCA,MAAa,uBAAuB;;;;;;;AAQpC,MAAa,wBAAwB;;AAGrC,MAAa,wBAAwB;;;;;AAMrC,MAAa,qCAA0C,IAAI,IAAI;CAC7D;CAAK;CAAK;CAAK;CAAK;CAAK;AAC3B,CAAC;;;;;;;AAQD,SAAgB,YAAY,OAA8B;CACxD,OACE,MAAM,SAAS,aACd,YAAY,SAAS,mBAAmB,IAAI,MAAM,MAAM;AAE7D"}
package/dist/errors.d.cts CHANGED
@@ -61,7 +61,7 @@ export type RustrakError = {
61
61
  readonly message: string;
62
62
  };
63
63
  export type RustrakErrorKind = RustrakError['kind'];
64
- export declare const FIELD_ERROR_CODES: readonly ["required", "invalid", "already_exists", "too_short", "too_long", "custom"];
64
+ export declare const FIELD_ERROR_CODES: readonly ["required", "invalid", "already_exists", "too_short", "too_long", "incorrect", "custom"];
65
65
  export type FieldErrorCode = (typeof FIELD_ERROR_CODES)[number];
66
66
  export type FieldError = {
67
67
  readonly field: string;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.cts","names":[],"sources":["../src/errors.ts"],"mappings":"A,YAuBY;WAQG;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WASlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WAEA;WACA,kBAAkB;;WAYlB;WACA;WACA;WACA,kBAAkB;;WAMlB;WAAkC;;WAWlC;WACA;WACA;WACA;;WAmBA;WACA;WAEA;;WAQA;WAAmC;;YAGtC,mBAAmB;qBAalB;YAUD,yBAAyB;YAyBzB;WACD;WACA,MAAM;WACN;;qBAOE;qBAQA;qBAGA;wBASG,YAAY,OAAO"}
1
+ {"version":3,"file":"errors.d.cts","names":[],"sources":["../src/errors.ts"],"mappings":"A,YAuBY;WAQG;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WASlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WACA,kBAAkB;;WAIlB;WACA;WACA;WAEA;WACA,kBAAkB;;WAYlB;WACA;WACA;WACA,kBAAkB;;WAMlB;WAAkC;;WAWlC;WACA;WACA;WACA;;WAmBA;WACA;WAEA;;WAQA;WAAmC;;YAEtC,mBAAmB;qBAalB;YAUD,yBAAyB;YAyBzB;WACD;WACA,MAAM;WACN;;qBAOE;qBAQA;qBAGA;wBAgBG,YAAY,OAAO"}