@secureport/sdk 0.5.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/client.d.ts +135 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +191 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/errors.d.ts +80 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +118 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +31 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +10 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/pagination.d.ts +27 -0
  16. package/dist/pagination.d.ts.map +1 -0
  17. package/dist/pagination.js +19 -0
  18. package/dist/pagination.js.map +1 -0
  19. package/dist/resources/branding.d.ts +49 -0
  20. package/dist/resources/branding.d.ts.map +1 -0
  21. package/dist/resources/branding.js +38 -0
  22. package/dist/resources/branding.js.map +1 -0
  23. package/dist/resources/issues.d.ts +248 -0
  24. package/dist/resources/issues.d.ts.map +1 -0
  25. package/dist/resources/issues.js +100 -0
  26. package/dist/resources/issues.js.map +1 -0
  27. package/dist/resources/reports.d.ts +83 -0
  28. package/dist/resources/reports.d.ts.map +1 -0
  29. package/dist/resources/reports.js +50 -0
  30. package/dist/resources/reports.js.map +1 -0
  31. package/dist/resources/runs.d.ts +474 -0
  32. package/dist/resources/runs.d.ts.map +1 -0
  33. package/dist/resources/runs.js +281 -0
  34. package/dist/resources/runs.js.map +1 -0
  35. package/dist/resources/suppression-rules.d.ts +43 -0
  36. package/dist/resources/suppression-rules.d.ts.map +1 -0
  37. package/dist/resources/suppression-rules.js +33 -0
  38. package/dist/resources/suppression-rules.js.map +1 -0
  39. package/dist/resources/targets.d.ts +126 -0
  40. package/dist/resources/targets.d.ts.map +1 -0
  41. package/dist/resources/targets.js +83 -0
  42. package/dist/resources/targets.js.map +1 -0
  43. package/package.json +55 -0
  44. package/src/client.ts +328 -0
  45. package/src/errors.ts +120 -0
  46. package/src/index.ts +100 -0
  47. package/src/pagination.ts +41 -0
  48. package/src/resources/branding.ts +59 -0
  49. package/src/resources/issues.ts +344 -0
  50. package/src/resources/reports.ts +104 -0
  51. package/src/resources/runs.ts +635 -0
  52. package/src/resources/suppression-rules.ts +69 -0
  53. package/src/resources/targets.ts +179 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrew Jordan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @secureport/sdk
2
+
3
+ A typed client for the [Secureport](https://secureport.io/) hosted API.
4
+
5
+ ```ts
6
+ import { createClient } from '@secureport/sdk';
7
+
8
+ const client = createClient({
9
+ apiKey: process.env.SECUREPORT_API_KEY!,
10
+ baseUrl: 'https://api.secureport.io',
11
+ });
12
+ ```
13
+
14
+ The transport authenticates every call, retries `429`/`502`/`503`/`504`
15
+ with backoff (honouring `Retry-After` when the API sends one), and throws a
16
+ typed `SecureportError` — carrying the same closed `code` union the API
17
+ answers with — for everything else. A request that never reaches a server at
18
+ all throws `SecureportNetworkError` instead, and is not retried.
19
+
20
+ Resource methods — `targets`, `runs`, `issues`, `reports` and `branding` —
21
+ sit on top of this transport; `client.request()` / `client.requestRaw()` are
22
+ there too, for anything they don't cover yet (`/analytics`, once it ships).
23
+
24
+ ## Upload a scan, get a report
25
+
26
+ From an existing target to a rendered report:
27
+
28
+ ```ts
29
+ import { readFileSync } from 'node:fs';
30
+
31
+ const run = await client.runs.create({
32
+ targetId: 'target_9c1c1347-5539-4432-b1b9-3bdba5a4f576',
33
+ kind: 'upload',
34
+ trigger: 'api',
35
+ });
36
+
37
+ const artifact = await client.runs.import(run.id, {
38
+ body: readFileSync('scan.jsonl'),
39
+ contentType: 'application/x-ndjson',
40
+ engine: 'nuclei',
41
+ });
42
+
43
+ // A file at or over 5 MiB parses off-request and answers `202 queued` —
44
+ // `finish` refuses with `conflict` until it's done. A small file like this
45
+ // one already comes back `done`, so this resolves immediately.
46
+ const finished =
47
+ artifact.processingStatus === 'queued' || artifact.processingStatus === 'processing'
48
+ ? await client.runs.waitForArtifact(run.id, artifact.id)
49
+ : artifact;
50
+
51
+ await client.runs.finish(run.id, {
52
+ status: finished.processingStatus === 'failed' ? 'failed' : 'finished',
53
+ });
54
+
55
+ const report = await client.reports.request(run.id, { kind: 'vap', format: 'json' });
56
+ console.log(await report.text());
57
+ ```
58
+
59
+ **A file too large for `runs.import()`** — Cloud Run caps a request body at
60
+ 32 MiB, whatever the API wants — uses `runs.createUploadUrl()` and
61
+ `runs.completeUpload()` instead: mint a signed URL, `PUT` the bytes yourself
62
+ with a plain `fetch` (not through this client — it would attach your API key
63
+ and the wrong base URL), then complete it. See
64
+ [`examples/runs-create-upload-url.ts`](examples/runs-create-upload-url.ts)
65
+ and [`examples/runs-complete-upload.ts`](examples/runs-complete-upload.ts)
66
+ for the full sequence.
67
+
68
+ `reports.request` is the one method that doesn't answer typed JSON — every
69
+ format it can ask for streams back as the raw `Response`, since a report is
70
+ Markdown, CSV or SARIF as often as it is JSON. `docs/api.md` has the curl
71
+ equivalent of every endpoint above, plus `issues` and target verification.
72
+
73
+ **MIT licensed.**
@@ -0,0 +1,135 @@
1
+ import { BrandingResource } from './resources/branding.js';
2
+ import { IssuesResource } from './resources/issues.js';
3
+ import { ReportsResource } from './resources/reports.js';
4
+ import { RunsResource } from './resources/runs.js';
5
+ import { SuppressionRulesResource } from './resources/suppression-rules.js';
6
+ import { TargetsResource } from './resources/targets.js';
7
+ /**
8
+ * The narrow interface a resource (`targets`, and later `issues`/`runs`/
9
+ * `reports`) depends on — {@link SecureportClient.request} bound to its
10
+ * instance, not the whole client. Keeps a resource's own tests, if it ever
11
+ * needs its own, from having to construct a full client.
12
+ */
13
+ export type Requester = <T = unknown>(input: RequestInput) => Promise<T>;
14
+ /**
15
+ * The narrow interface {@link ReportsResource} depends on —
16
+ * {@link SecureportClient.requestRaw} bound to its instance, the same way
17
+ * {@link Requester} is {@link SecureportClient.request} bound.
18
+ */
19
+ export type RawRequester = (input: RequestInput) => Promise<Response>;
20
+ /** The `fetch` shape the transport needs — the global function satisfies it. */
21
+ export type Fetch = typeof globalThis.fetch;
22
+ /**
23
+ * What a `fetch` body may be — `RequestInit['body']` rather than the DOM
24
+ * lib's `BodyInit`, since `tsconfig.base.json`'s `lib` is `ES2023` only and
25
+ * does not carry that name; the global `RequestInit`/`Response` interfaces
26
+ * `@types/node` merges in are what `Fetch` itself already relies on.
27
+ */
28
+ export type FetchBody = Exclude<RequestInit['body'], null | undefined>;
29
+ /**
30
+ * What a client is constructed with.
31
+ *
32
+ * `apiKey` comes from wherever the caller reads it — this package takes no
33
+ * position on environment variables, unlike `@secureport/cli`'s hosted mode,
34
+ * which is a terminal program and can insist on one. A library has no
35
+ * terminal to insist to.
36
+ */
37
+ export interface ClientOptions {
38
+ /** Sent as `Authorization: Bearer <apiKey>` on every request. */
39
+ readonly apiKey: string;
40
+ /** The API's base URL, with no trailing slash required. */
41
+ readonly baseUrl: string;
42
+ /** Overrides the transport's `fetch`. Defaults to the global. Tests use this. */
43
+ readonly fetch?: Fetch;
44
+ /**
45
+ * How many times a retryable response is retried before giving up.
46
+ *
47
+ * Sized for scan polling, not just upload-and-done calls: a hosted scan
48
+ * run can sit behind a busy queue for longer than an upload ever would, so
49
+ * the default favours patience over a fast failure.
50
+ *
51
+ * @defaultValue 4
52
+ */
53
+ readonly maxAttempts?: number;
54
+ /**
55
+ * How long a single attempt may take before it is aborted and treated as a
56
+ * network failure.
57
+ *
58
+ * @defaultValue 30_000
59
+ */
60
+ readonly timeoutMs?: number;
61
+ }
62
+ /** One HTTP call the transport makes. */
63
+ export interface RequestInput {
64
+ /** e.g. `'GET'`, `'POST'`. */
65
+ readonly method: string;
66
+ /** Path only, e.g. `'/issues'` — joined onto {@link ClientOptions.baseUrl}. */
67
+ readonly path: string;
68
+ /** Sent as `?name=value`. A `undefined` value is omitted, not sent empty. */
69
+ readonly query?: Readonly<Record<string, string | number | boolean | undefined>>;
70
+ /** JSON-serialised and sent with `content-type: application/json`. Omitted entirely when absent. */
71
+ readonly body?: unknown;
72
+ /**
73
+ * A body sent exactly as given, with this `content-type` — never
74
+ * JSON-encoded. Mutually exclusive with {@link RequestInput.body}; used
75
+ * only by `runs.import()`, the one route that accepts a raw scanner
76
+ * artifact rather than a JSON payload.
77
+ */
78
+ readonly rawBody?: {
79
+ readonly data: FetchBody;
80
+ readonly contentType: string;
81
+ };
82
+ }
83
+ /**
84
+ * The transport every resource client is built on: authentication, retry
85
+ * with backoff on the four statuses the API answers transiently with, and
86
+ * typed errors for everything else. Usable on its own via
87
+ * {@link SecureportClient.request}/{@link SecureportClient.requestRaw}
88
+ * without going through a resource.
89
+ */
90
+ export declare class SecureportClient {
91
+ private readonly apiKey;
92
+ private readonly baseUrl;
93
+ private readonly fetchImpl;
94
+ private readonly maxAttempts;
95
+ private readonly timeoutMs;
96
+ /** Targets, and the per-target verification flow every hosted scan needs. */
97
+ readonly targets: TargetsResource;
98
+ /** Issues — the tracked entity findings reconcile into. */
99
+ readonly issues: IssuesResource;
100
+ /** Runs — one execution against a target, and the scan lifecycle around it. */
101
+ readonly runs: RunsResource;
102
+ /** Reports — rendering a run's snapshot into a document or a machine-readable export. */
103
+ readonly reports: ReportsResource;
104
+ /** Branding — the one record per organisation reports render with. */
105
+ readonly branding: BrandingResource;
106
+ /** Suppression rules — pre-emptive "never open an issue for this" globs. */
107
+ readonly suppressionRules: SuppressionRulesResource;
108
+ constructor(options: ClientOptions);
109
+ /**
110
+ * Makes one logical call, retrying transient failures internally. Resolves
111
+ * with the parsed JSON body, or `undefined` for a `204`.
112
+ *
113
+ * @throws {SecureportError} for any response the API answered with, after
114
+ * retries (if any) are exhausted.
115
+ * @throws {SecureportNetworkError} if no response was ever received.
116
+ */
117
+ request<T = unknown>(input: RequestInput): Promise<T>;
118
+ /**
119
+ * The same authentication, retry-with-backoff and typed-error handling as
120
+ * {@link SecureportClient.request}, but without the JSON parse: resolves with the raw
121
+ * `Response` itself, for a route whose body is not JSON — currently just
122
+ * `reports.request()`, whose formats are streamed text (`text/csv`,
123
+ * `application/sarif+json`, …) rather than a parsed envelope.
124
+ *
125
+ * @throws {SecureportError} for any response the API answered with, after
126
+ * retries (if any) are exhausted.
127
+ * @throws {SecureportNetworkError} if no response was ever received.
128
+ */
129
+ requestRaw(input: RequestInput): Promise<Response>;
130
+ private attempt;
131
+ private buildUrl;
132
+ }
133
+ /** Constructs a {@link SecureportClient}. The package's one entry point. */
134
+ export declare function createClient(options: ClientOptions): SecureportClient;
135
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,191 @@
1
+ import { FALLBACK_CODE_BY_STATUS, RETRYABLE_STATUSES, SecureportError, SecureportNetworkError, } from './errors.js';
2
+ import { BrandingResource } from './resources/branding.js';
3
+ import { IssuesResource } from './resources/issues.js';
4
+ import { ReportsResource } from './resources/reports.js';
5
+ import { RunsResource } from './resources/runs.js';
6
+ import { SuppressionRulesResource } from './resources/suppression-rules.js';
7
+ import { TargetsResource } from './resources/targets.js';
8
+ const DEFAULT_MAX_ATTEMPTS = 4;
9
+ const DEFAULT_TIMEOUT_MS = 30_000;
10
+ const BASE_DELAY_MS = 500;
11
+ const MAX_DELAY_MS = 8_000;
12
+ /**
13
+ * The transport every resource client is built on: authentication, retry
14
+ * with backoff on the four statuses the API answers transiently with, and
15
+ * typed errors for everything else. Usable on its own via
16
+ * {@link SecureportClient.request}/{@link SecureportClient.requestRaw}
17
+ * without going through a resource.
18
+ */
19
+ export class SecureportClient {
20
+ apiKey;
21
+ baseUrl;
22
+ fetchImpl;
23
+ maxAttempts;
24
+ timeoutMs;
25
+ /** Targets, and the per-target verification flow every hosted scan needs. */
26
+ targets;
27
+ /** Issues — the tracked entity findings reconcile into. */
28
+ issues;
29
+ /** Runs — one execution against a target, and the scan lifecycle around it. */
30
+ runs;
31
+ /** Reports — rendering a run's snapshot into a document or a machine-readable export. */
32
+ reports;
33
+ /** Branding — the one record per organisation reports render with. */
34
+ branding;
35
+ /** Suppression rules — pre-emptive "never open an issue for this" globs. */
36
+ suppressionRules;
37
+ constructor(options) {
38
+ this.apiKey = options.apiKey;
39
+ this.baseUrl = options.baseUrl.replace(/\/+$/u, '');
40
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
41
+ this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
42
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
43
+ this.targets = new TargetsResource(this.request.bind(this));
44
+ this.issues = new IssuesResource(this.request.bind(this));
45
+ this.runs = new RunsResource(this.request.bind(this));
46
+ this.reports = new ReportsResource(this.requestRaw.bind(this));
47
+ this.branding = new BrandingResource(this.request.bind(this));
48
+ this.suppressionRules = new SuppressionRulesResource(this.request.bind(this));
49
+ }
50
+ /**
51
+ * Makes one logical call, retrying transient failures internally. Resolves
52
+ * with the parsed JSON body, or `undefined` for a `204`.
53
+ *
54
+ * @throws {SecureportError} for any response the API answered with, after
55
+ * retries (if any) are exhausted.
56
+ * @throws {SecureportNetworkError} if no response was ever received.
57
+ */
58
+ async request(input) {
59
+ const response = await this.requestRaw(input);
60
+ if (response.status === 204)
61
+ return undefined;
62
+ return (await response.json());
63
+ }
64
+ /**
65
+ * The same authentication, retry-with-backoff and typed-error handling as
66
+ * {@link SecureportClient.request}, but without the JSON parse: resolves with the raw
67
+ * `Response` itself, for a route whose body is not JSON — currently just
68
+ * `reports.request()`, whose formats are streamed text (`text/csv`,
69
+ * `application/sarif+json`, …) rather than a parsed envelope.
70
+ *
71
+ * @throws {SecureportError} for any response the API answered with, after
72
+ * retries (if any) are exhausted.
73
+ * @throws {SecureportNetworkError} if no response was ever received.
74
+ */
75
+ async requestRaw(input) {
76
+ const url = this.buildUrl(input.path, input.query);
77
+ const { headers, body } = requestBody(input);
78
+ for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
79
+ const response = await this.attempt(url, input.method, headers, body);
80
+ if (RETRYABLE_STATUSES.has(response.status) && attempt < this.maxAttempts) {
81
+ await delay(retryDelayMs(response, attempt));
82
+ continue;
83
+ }
84
+ if (response.status >= 200 && response.status < 300)
85
+ return response;
86
+ throw await toSecureportError(response);
87
+ }
88
+ // Unreachable while maxAttempts >= 1: the loop above always either
89
+ // returns or throws on its final iteration.
90
+ throw new Error('request() exhausted its attempts without a terminal response');
91
+ }
92
+ async attempt(url, method, headers, body) {
93
+ const controller = new AbortController();
94
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
95
+ try {
96
+ return await this.fetchImpl(url, {
97
+ method,
98
+ headers: {
99
+ authorization: `Bearer ${this.apiKey}`,
100
+ accept: 'application/json',
101
+ ...headers,
102
+ },
103
+ ...(body === undefined ? {} : { body }),
104
+ signal: controller.signal,
105
+ });
106
+ }
107
+ catch (error) {
108
+ throw new SecureportNetworkError(`cannot reach ${this.baseUrl}: ${error instanceof Error ? error.message : String(error)}`, error);
109
+ }
110
+ finally {
111
+ clearTimeout(timer);
112
+ }
113
+ }
114
+ buildUrl(path, query) {
115
+ const url = new URL(`${this.baseUrl}${path}`);
116
+ for (const [name, value] of Object.entries(query ?? {})) {
117
+ if (value !== undefined)
118
+ url.searchParams.set(name, String(value));
119
+ }
120
+ return url.toString();
121
+ }
122
+ }
123
+ /** Constructs a {@link SecureportClient}. The package's one entry point. */
124
+ export function createClient(options) {
125
+ return new SecureportClient(options);
126
+ }
127
+ /** How long to wait before the next attempt, honouring `Retry-After` when the API sends one. */
128
+ function retryDelayMs(response, attempt) {
129
+ const retryAfter = response.headers.get('retry-after');
130
+ if (retryAfter !== null) {
131
+ const seconds = Number(retryAfter);
132
+ if (Number.isFinite(seconds) && seconds >= 0)
133
+ return seconds * 1000;
134
+ }
135
+ return Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
136
+ }
137
+ function delay(ms) {
138
+ return new Promise((resolve) => setTimeout(resolve, ms));
139
+ }
140
+ /**
141
+ * The headers and body one request sends — {@link RequestInput.rawBody} wins
142
+ * when both it and {@link RequestInput.body} are given, though a resource
143
+ * method never sets both.
144
+ */
145
+ function requestBody(input) {
146
+ if (input.rawBody !== undefined) {
147
+ return { headers: { 'content-type': input.rawBody.contentType }, body: input.rawBody.data };
148
+ }
149
+ if (input.body !== undefined) {
150
+ return { headers: { 'content-type': 'application/json' }, body: JSON.stringify(input.body) };
151
+ }
152
+ return { headers: {}, body: undefined };
153
+ }
154
+ /**
155
+ * Builds the typed error for a terminal (non-retried, non-2xx) response.
156
+ *
157
+ * `502`/`504` are the edge answering for a dead or slow origin — the body is
158
+ * Cloudflare's, not this API's envelope — so those (and `429`, sent by this
159
+ * API but read defensively the same way) fall back to a status-derived code
160
+ * rather than assuming the body parses.
161
+ */
162
+ async function toSecureportError(response) {
163
+ const parsed = await parseErrorBody(response);
164
+ if (parsed !== undefined) {
165
+ return new SecureportError(parsed.error.code, parsed.error.message, response.status, parsed.error.requestId);
166
+ }
167
+ const fallbackCode = FALLBACK_CODE_BY_STATUS[response.status];
168
+ return new SecureportError(fallbackCode ?? 'internal', fallbackCode === undefined
169
+ ? `the API answered ${String(response.status)} with a body this SDK could not read.`
170
+ : `the API answered ${String(response.status)} with no readable body — the edge, not the API itself.`, response.status);
171
+ }
172
+ /** The response body as this API's error envelope, or `undefined` if it doesn't parse as one. */
173
+ async function parseErrorBody(response) {
174
+ try {
175
+ const body = await response.json();
176
+ if (typeof body !== 'object' || body === null || !('error' in body))
177
+ return undefined;
178
+ const error = body.error;
179
+ if (typeof error !== 'object' || error === null)
180
+ return undefined;
181
+ if (!('code' in error) || !('message' in error))
182
+ return undefined;
183
+ if (typeof error.code !== 'string' || typeof error.message !== 'string')
184
+ return undefined;
185
+ return body;
186
+ }
187
+ catch {
188
+ return undefined;
189
+ }
190
+ }
191
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Every error code the hosted API can answer with.
3
+ *
4
+ * Mirrored from `apps/api/src/errors.ts`'s `ERROR_CODES` rather than imported
5
+ * from it: `apps/api` is `"private": true` and pulls in Hono, drizzle and
6
+ * postgres, so a published package cannot depend on it. Kept honest by a
7
+ * drift assertion in `apps/api/tests/openapi.test.ts`, which compares this
8
+ * list against the live `/openapi.json` document's `Error` schema — the same
9
+ * technique that file already uses to keep `docs/api-endpoints.md` from
10
+ * drifting.
11
+ *
12
+ * The union is closed on purpose: a `switch` over {@link ErrorCode} is
13
+ * exhaustive, and adding a code here without the API test agreeing fails
14
+ * that test rather than shipping quietly out of sync.
15
+ */
16
+ export declare const ERROR_CODES: readonly ["malformed", "validation", "unauthorized", "payment_required", "forbidden", "not_entitled", "org_deleted", "terms_required", "verification_required", "not_found", "method_not_allowed", "not_acceptable", "request_timeout", "conflict", "gone", "precondition_failed", "payload_too_large", "uri_too_long", "unsupported_media_type", "misdirected_request", "rate_limited", "quota_exceeded", "headers_too_large", "internal", "not_implemented", "bad_gateway", "unavailable", "gateway_timeout"];
17
+ /** One of {@link ERROR_CODES}. What a caller branches on. */
18
+ export type ErrorCode = (typeof ERROR_CODES)[number];
19
+ /**
20
+ * The four statuses the transport retries with backoff, and nothing else.
21
+ *
22
+ * `terms_required` and `verification_required` both answer `403` — a settled
23
+ * refusal, not a transient one — so `403` is deliberately absent here even
24
+ * though it shares a status family with codes that could look retryable.
25
+ * Retrying an authorisation refusal would turn a clear "do this first" into a
26
+ * slow, confusing one.
27
+ */
28
+ export declare const RETRYABLE_STATUSES: ReadonlySet<number>;
29
+ /**
30
+ * The code a response is assumed to carry when its body cannot be read as
31
+ * this API's error envelope — which happens exactly on `502`/`504`, because
32
+ * those are the edge answering for a dead or slow origin and the body is
33
+ * Cloudflare's, not this API's. Each of the four statuses here maps to
34
+ * exactly one {@link ErrorCode}, so the fallback is unambiguous; no other
35
+ * status gets one; a `403` with an unparseable body, for instance, could be
36
+ * any of four different codes and none should be silently guessed.
37
+ */
38
+ export declare const FALLBACK_CODE_BY_STATUS: Readonly<Partial<Record<number, ErrorCode>>>;
39
+ /**
40
+ * Thrown for every non-2xx response the API returns, and for a response the
41
+ * transport gave up retrying.
42
+ *
43
+ * `requestId` is the same id the API's own `x-request-id` response header and
44
+ * error envelope carry, worth including in a support request. It is absent
45
+ * only when the body could not be read as this API's envelope at all (the
46
+ * `502`/`504` edge case above).
47
+ */
48
+ export declare class SecureportError extends Error {
49
+ /** What to branch on. */
50
+ readonly code: ErrorCode;
51
+ /** The HTTP status the response actually carried. */
52
+ readonly status: number;
53
+ /** The API's `x-request-id` for this call, when the body could be read as its envelope. */
54
+ readonly requestId?: string | undefined;
55
+ constructor(
56
+ /** What to branch on. */
57
+ code: ErrorCode, message: string,
58
+ /** The HTTP status the response actually carried. */
59
+ status: number,
60
+ /** The API's `x-request-id` for this call, when the body could be read as its envelope. */
61
+ requestId?: string | undefined);
62
+ }
63
+ /**
64
+ * Thrown when the request never reached a server at all — DNS failure,
65
+ * connection refused, TLS error. Distinct from {@link SecureportError}
66
+ * because there is no status and no code: nothing answered.
67
+ *
68
+ * Not retried by the transport. A network failure of this kind is usually a
69
+ * configuration problem (wrong `baseUrl`, no connectivity) rather than a
70
+ * transient one, and retrying it silently would turn a five-second failure
71
+ * into a much slower one for no better odds of success.
72
+ */
73
+ export declare class SecureportNetworkError extends Error {
74
+ /** Whatever `fetch` itself threw or rejected with. */
75
+ readonly cause: unknown;
76
+ constructor(message: string,
77
+ /** Whatever `fetch` itself threw or rejected with. */
78
+ cause: unknown);
79
+ }
80
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,ifA6Bd,CAAC;AAEX,6DAA6D;AAC7D,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAiC,CAAC;AAErF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAKhF,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAEtC,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS;IAExB,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,2FAA2F;IAC3F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM;;IAN3B,yBAAyB;IAChB,IAAI,EAAE,SAAS,EACxB,OAAO,EAAE,MAAM;IACf,qDAAqD;IAC5C,MAAM,EAAE,MAAM;IACvB,2FAA2F;IAClF,SAAS,CAAC,EAAE,MAAM,YAAA;CAK9B;AAED;;;;;;;;;GASG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAG7C,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,OAAO;gBAFvB,OAAO,EAAE,MAAM;IACf,sDAAsD;IAC7C,KAAK,EAAE,OAAO;CAK1B"}
package/dist/errors.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Every error code the hosted API can answer with.
3
+ *
4
+ * Mirrored from `apps/api/src/errors.ts`'s `ERROR_CODES` rather than imported
5
+ * from it: `apps/api` is `"private": true` and pulls in Hono, drizzle and
6
+ * postgres, so a published package cannot depend on it. Kept honest by a
7
+ * drift assertion in `apps/api/tests/openapi.test.ts`, which compares this
8
+ * list against the live `/openapi.json` document's `Error` schema — the same
9
+ * technique that file already uses to keep `docs/api-endpoints.md` from
10
+ * drifting.
11
+ *
12
+ * The union is closed on purpose: a `switch` over {@link ErrorCode} is
13
+ * exhaustive, and adding a code here without the API test agreeing fails
14
+ * that test rather than shipping quietly out of sync.
15
+ */
16
+ export const ERROR_CODES = [
17
+ 'malformed',
18
+ 'validation',
19
+ 'unauthorized',
20
+ 'payment_required',
21
+ 'forbidden',
22
+ 'not_entitled',
23
+ 'org_deleted',
24
+ 'terms_required',
25
+ 'verification_required',
26
+ 'not_found',
27
+ 'method_not_allowed',
28
+ 'not_acceptable',
29
+ 'request_timeout',
30
+ 'conflict',
31
+ 'gone',
32
+ 'precondition_failed',
33
+ 'payload_too_large',
34
+ 'uri_too_long',
35
+ 'unsupported_media_type',
36
+ 'misdirected_request',
37
+ 'rate_limited',
38
+ 'quota_exceeded',
39
+ 'headers_too_large',
40
+ 'internal',
41
+ 'not_implemented',
42
+ 'bad_gateway',
43
+ 'unavailable',
44
+ 'gateway_timeout',
45
+ ];
46
+ /**
47
+ * The four statuses the transport retries with backoff, and nothing else.
48
+ *
49
+ * `terms_required` and `verification_required` both answer `403` — a settled
50
+ * refusal, not a transient one — so `403` is deliberately absent here even
51
+ * though it shares a status family with codes that could look retryable.
52
+ * Retrying an authorisation refusal would turn a clear "do this first" into a
53
+ * slow, confusing one.
54
+ */
55
+ export const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
56
+ /**
57
+ * The code a response is assumed to carry when its body cannot be read as
58
+ * this API's error envelope — which happens exactly on `502`/`504`, because
59
+ * those are the edge answering for a dead or slow origin and the body is
60
+ * Cloudflare's, not this API's. Each of the four statuses here maps to
61
+ * exactly one {@link ErrorCode}, so the fallback is unambiguous; no other
62
+ * status gets one; a `403` with an unparseable body, for instance, could be
63
+ * any of four different codes and none should be silently guessed.
64
+ */
65
+ export const FALLBACK_CODE_BY_STATUS = {
66
+ 429: 'rate_limited',
67
+ 502: 'bad_gateway',
68
+ 503: 'unavailable',
69
+ 504: 'gateway_timeout',
70
+ };
71
+ /**
72
+ * Thrown for every non-2xx response the API returns, and for a response the
73
+ * transport gave up retrying.
74
+ *
75
+ * `requestId` is the same id the API's own `x-request-id` response header and
76
+ * error envelope carry, worth including in a support request. It is absent
77
+ * only when the body could not be read as this API's envelope at all (the
78
+ * `502`/`504` edge case above).
79
+ */
80
+ export class SecureportError extends Error {
81
+ code;
82
+ status;
83
+ requestId;
84
+ constructor(
85
+ /** What to branch on. */
86
+ code, message,
87
+ /** The HTTP status the response actually carried. */
88
+ status,
89
+ /** The API's `x-request-id` for this call, when the body could be read as its envelope. */
90
+ requestId) {
91
+ super(message);
92
+ this.code = code;
93
+ this.status = status;
94
+ this.requestId = requestId;
95
+ this.name = 'SecureportError';
96
+ }
97
+ }
98
+ /**
99
+ * Thrown when the request never reached a server at all — DNS failure,
100
+ * connection refused, TLS error. Distinct from {@link SecureportError}
101
+ * because there is no status and no code: nothing answered.
102
+ *
103
+ * Not retried by the transport. A network failure of this kind is usually a
104
+ * configuration problem (wrong `baseUrl`, no connectivity) rather than a
105
+ * transient one, and retrying it silently would turn a five-second failure
106
+ * into a much slower one for no better odds of success.
107
+ */
108
+ export class SecureportNetworkError extends Error {
109
+ cause;
110
+ constructor(message,
111
+ /** Whatever `fetch` itself threw or rejected with. */
112
+ cause) {
113
+ super(message);
114
+ this.cause = cause;
115
+ this.name = 'SecureportNetworkError';
116
+ }
117
+ }
118
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,YAAY;IACZ,cAAc;IACd,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,aAAa;IACb,gBAAgB;IAChB,uBAAuB;IACvB,WAAW;IACX,oBAAoB;IACpB,gBAAgB;IAChB,iBAAiB;IACjB,UAAU;IACV,MAAM;IACN,qBAAqB;IACrB,mBAAmB;IACnB,cAAc;IACd,wBAAwB;IACxB,qBAAqB;IACrB,cAAc;IACd,gBAAgB;IAChB,mBAAmB;IACnB,UAAU;IACV,iBAAiB;IACjB,aAAa;IACb,aAAa;IACb,iBAAiB;CACT,CAAC;AAKX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAiD;IACnF,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,iBAAiB;CACvB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAG7B;IAGA;IAEA;IAPX;IACE,yBAAyB;IAChB,IAAe,EACxB,OAAe;IACf,qDAAqD;IAC5C,MAAc;IACvB,2FAA2F;IAClF,SAAkB;QAE3B,KAAK,CAAC,OAAO,CAAC,CAAC;QAPN,SAAI,GAAJ,IAAI,CAAW;QAGf,WAAM,GAAN,MAAM,CAAQ;QAEd,cAAS,GAAT,SAAS,CAAS;QAG3B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAIpC;IAHX,YACE,OAAe;IACf,sDAAsD;IAC7C,KAAc;QAEvB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,UAAK,GAAL,KAAK,CAAS;QAGvB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF"}