@shipstatic/types 2.5.0-beta.12 → 2.5.0-beta.14
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.
- package/README.md +1 -0
- package/dist/index.d.ts +55 -5
- package/dist/index.js +68 -7
- package/package.json +1 -1
- package/src/index.ts +92 -7
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -52,11 +52,24 @@ export interface DeploymentCreateResponse extends Deployment {
|
|
|
52
52
|
/**
|
|
53
53
|
* Every path the public API answers on, declared once.
|
|
54
54
|
*
|
|
55
|
-
* The URL surface
|
|
56
|
-
*
|
|
57
|
-
* rename meant finding all four.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
55
|
+
* The URL surface was written out in four places — the API's mounts, the
|
|
56
|
+
* SDK's client, the dashboard's client, and the post-deploy smoke — so a
|
|
57
|
+
* rename meant finding all four. The first three now read this table.
|
|
58
|
+
*
|
|
59
|
+
* The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
|
|
60
|
+
* five of its nine paths are `/admin/*`, which this table excludes by
|
|
61
|
+
* design, and splitting one list between a registry and literals reads worse
|
|
62
|
+
* than keeping it uniform.
|
|
63
|
+
*
|
|
64
|
+
* **What this guarantees, exactly.** Collection paths are mounted from here,
|
|
65
|
+
* so producer and consumer cannot diverge. Item paths are declared here and
|
|
66
|
+
* consumed by clients, but the API spells them relative to their mount
|
|
67
|
+
* (`/:deployment/config`), so the table does not *generate* them — it is
|
|
68
|
+
* held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
|
|
69
|
+
* any entry names a path no route answers. Some entries have no client yet
|
|
70
|
+
* (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
|
|
71
|
+
* deliberately does not reach); the fence is what keeps those honest rather
|
|
72
|
+
* than merely asserted.
|
|
60
73
|
*
|
|
61
74
|
* **The operator surface is deliberately absent.** `/admin/*` paths belong
|
|
62
75
|
* to `web/my`, for the same reason its row types do: this package is
|
|
@@ -293,6 +306,24 @@ export interface DomainRecordsResponse {
|
|
|
293
306
|
/** Required DNS records for configuration */
|
|
294
307
|
records: DnsRecord[];
|
|
295
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* The envelope an `Idempotency-Key` must fit, and how long a replay lasts.
|
|
311
|
+
*
|
|
312
|
+
* Format lives here rather than on the server alone by the format-vs-policy
|
|
313
|
+
* rule: a client can decide offline whether a key is well-formed, and the
|
|
314
|
+
* API would reject the same value the same way.
|
|
315
|
+
*/
|
|
316
|
+
export declare const IDEMPOTENCY_KEY_CONSTRAINTS: {
|
|
317
|
+
readonly MAX_LENGTH: 256;
|
|
318
|
+
/** How long a stored 201 stays replayable. */
|
|
319
|
+
readonly WINDOW_SECONDS: number;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Validate an idempotency key, returning the trimmed value or `undefined`
|
|
323
|
+
* when none was supplied. Throws {@link ShipError.validation} when the value
|
|
324
|
+
* cannot be sent — the same verdict the API would reach, reached earlier.
|
|
325
|
+
*/
|
|
326
|
+
export declare function validateIdempotencyKey(value: unknown): string | undefined;
|
|
296
327
|
/**
|
|
297
328
|
* Response for `GET /labels` — every label in use across the caller's
|
|
298
329
|
* deployments, domains and tokens, grouped and ordered by last use.
|
|
@@ -1029,6 +1060,25 @@ export interface DeploymentUploadOptions {
|
|
|
1029
1060
|
spa?: boolean;
|
|
1030
1061
|
/** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
|
|
1031
1062
|
captcha?: string;
|
|
1063
|
+
/**
|
|
1064
|
+
* Makes this deploy replayable instead of repeatable.
|
|
1065
|
+
*
|
|
1066
|
+
* A deploy is not naturally idempotent: a client-side timeout on a slow
|
|
1067
|
+
* one leaves the caller unable to tell "it never landed" from "it landed
|
|
1068
|
+
* and the response was lost", and retrying produces a second deployment.
|
|
1069
|
+
* Send the same key on the retry and the platform replays the original
|
|
1070
|
+
* 201 verbatim rather than creating anything
|
|
1071
|
+
* ({@link IDEMPOTENCY_KEY_CONSTRAINTS.WINDOW_SECONDS}).
|
|
1072
|
+
*
|
|
1073
|
+
* **Agents are the audience.** A human notices a duplicate; an automated
|
|
1074
|
+
* retry does not. Pick a key that identifies the ATTEMPT — a run id, a
|
|
1075
|
+
* commit sha, a uuid minted before the first try — never one that varies
|
|
1076
|
+
* per attempt, which would defeat the point.
|
|
1077
|
+
*
|
|
1078
|
+
* The replay is per-caller, and it stores successes only: a failed deploy
|
|
1079
|
+
* retries fresh under the same key.
|
|
1080
|
+
*/
|
|
1081
|
+
idempotencyKey?: string;
|
|
1032
1082
|
}
|
|
1033
1083
|
/**
|
|
1034
1084
|
* Pagination options for every list endpoint. The response's `cursor` feeds
|
package/dist/index.js
CHANGED
|
@@ -17,11 +17,24 @@ export const DeploymentStatus = {
|
|
|
17
17
|
/**
|
|
18
18
|
* Every path the public API answers on, declared once.
|
|
19
19
|
*
|
|
20
|
-
* The URL surface
|
|
21
|
-
*
|
|
22
|
-
* rename meant finding all four.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* The URL surface was written out in four places — the API's mounts, the
|
|
21
|
+
* SDK's client, the dashboard's client, and the post-deploy smoke — so a
|
|
22
|
+
* rename meant finding all four. The first three now read this table.
|
|
23
|
+
*
|
|
24
|
+
* The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
|
|
25
|
+
* five of its nine paths are `/admin/*`, which this table excludes by
|
|
26
|
+
* design, and splitting one list between a registry and literals reads worse
|
|
27
|
+
* than keeping it uniform.
|
|
28
|
+
*
|
|
29
|
+
* **What this guarantees, exactly.** Collection paths are mounted from here,
|
|
30
|
+
* so producer and consumer cannot diverge. Item paths are declared here and
|
|
31
|
+
* consumed by clients, but the API spells them relative to their mount
|
|
32
|
+
* (`/:deployment/config`), so the table does not *generate* them — it is
|
|
33
|
+
* held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
|
|
34
|
+
* any entry names a path no route answers. Some entries have no client yet
|
|
35
|
+
* (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
|
|
36
|
+
* deliberately does not reach); the fence is what keeps those honest rather
|
|
37
|
+
* than merely asserted.
|
|
25
38
|
*
|
|
26
39
|
* **The operator surface is deliberately absent.** `/admin/*` paths belong
|
|
27
40
|
* to `web/my`, for the same reason its row types do: this package is
|
|
@@ -74,6 +87,38 @@ export const DomainStatus = {
|
|
|
74
87
|
SUCCESS: 'success',
|
|
75
88
|
PAUSED: 'paused',
|
|
76
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* The envelope an `Idempotency-Key` must fit, and how long a replay lasts.
|
|
92
|
+
*
|
|
93
|
+
* Format lives here rather than on the server alone by the format-vs-policy
|
|
94
|
+
* rule: a client can decide offline whether a key is well-formed, and the
|
|
95
|
+
* API would reject the same value the same way.
|
|
96
|
+
*/
|
|
97
|
+
export const IDEMPOTENCY_KEY_CONSTRAINTS = {
|
|
98
|
+
MAX_LENGTH: 256,
|
|
99
|
+
/** How long a stored 201 stays replayable. */
|
|
100
|
+
WINDOW_SECONDS: 24 * 60 * 60,
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Validate an idempotency key, returning the trimmed value or `undefined`
|
|
104
|
+
* when none was supplied. Throws {@link ShipError.validation} when the value
|
|
105
|
+
* cannot be sent — the same verdict the API would reach, reached earlier.
|
|
106
|
+
*/
|
|
107
|
+
export function validateIdempotencyKey(value) {
|
|
108
|
+
if (value === undefined || value === null)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (typeof value !== 'string') {
|
|
111
|
+
throw ShipError.validation('Idempotency key must be a string.');
|
|
112
|
+
}
|
|
113
|
+
const key = value.trim();
|
|
114
|
+
if (!key) {
|
|
115
|
+
throw ShipError.validation('Idempotency key must not be empty.');
|
|
116
|
+
}
|
|
117
|
+
if (key.length > IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH) {
|
|
118
|
+
throw ShipError.validation(`Idempotency key must be at most ${IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH} characters.`);
|
|
119
|
+
}
|
|
120
|
+
return key;
|
|
121
|
+
}
|
|
77
122
|
// =============================================================================
|
|
78
123
|
// ACCOUNT TYPES
|
|
79
124
|
// =============================================================================
|
|
@@ -169,6 +214,14 @@ const ERROR_CATEGORIES = {
|
|
|
169
214
|
* `ErrorType` is automatically picked up.
|
|
170
215
|
*/
|
|
171
216
|
const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)));
|
|
217
|
+
/**
|
|
218
|
+
* Ceiling on a message adopted from a **non-JSON** error body — a foreign
|
|
219
|
+
* responder's, never this platform's. Generous for the plain-text one-liners
|
|
220
|
+
* intermediaries actually send (`error code: 1015`), far below a document.
|
|
221
|
+
* Our own messages are never measured against it: a JSON body is the API's
|
|
222
|
+
* contract, and truncating a long validation message would be the bug.
|
|
223
|
+
*/
|
|
224
|
+
const MAX_FOREIGN_MESSAGE_LENGTH = 200;
|
|
172
225
|
/**
|
|
173
226
|
* Simple unified error class for both API and SDK
|
|
174
227
|
*/
|
|
@@ -240,9 +293,17 @@ export class ShipError extends Error {
|
|
|
240
293
|
}
|
|
241
294
|
}
|
|
242
295
|
else {
|
|
243
|
-
|
|
244
|
-
|
|
296
|
+
// A non-JSON body did not come from this platform — every API error
|
|
297
|
+
// is `ErrorResponse` JSON — so it is an intermediary's output, and
|
|
298
|
+
// the two kinds it produces need opposite treatment. A CDN's plain
|
|
299
|
+
// `error code: 1015` is the most useful thing there is to say. A
|
|
300
|
+
// proxy's HTML error page is a *document*, not a message: adopting it
|
|
301
|
+
// verbatim made a misconfigured `apiUrl` print 2,059 characters of
|
|
302
|
+
// markup as the error. Trust it only when it reads as a message.
|
|
303
|
+
const text = (await response.text()).trim();
|
|
304
|
+
if (text && !text.startsWith('<') && text.length <= MAX_FOREIGN_MESSAGE_LENGTH) {
|
|
245
305
|
message = text;
|
|
306
|
+
}
|
|
246
307
|
}
|
|
247
308
|
}
|
|
248
309
|
catch {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -61,11 +61,24 @@ export interface DeploymentCreateResponse extends Deployment {
|
|
|
61
61
|
/**
|
|
62
62
|
* Every path the public API answers on, declared once.
|
|
63
63
|
*
|
|
64
|
-
* The URL surface
|
|
65
|
-
*
|
|
66
|
-
* rename meant finding all four.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
64
|
+
* The URL surface was written out in four places — the API's mounts, the
|
|
65
|
+
* SDK's client, the dashboard's client, and the post-deploy smoke — so a
|
|
66
|
+
* rename meant finding all four. The first three now read this table.
|
|
67
|
+
*
|
|
68
|
+
* The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
|
|
69
|
+
* five of its nine paths are `/admin/*`, which this table excludes by
|
|
70
|
+
* design, and splitting one list between a registry and literals reads worse
|
|
71
|
+
* than keeping it uniform.
|
|
72
|
+
*
|
|
73
|
+
* **What this guarantees, exactly.** Collection paths are mounted from here,
|
|
74
|
+
* so producer and consumer cannot diverge. Item paths are declared here and
|
|
75
|
+
* consumed by clients, but the API spells them relative to their mount
|
|
76
|
+
* (`/:deployment/config`), so the table does not *generate* them — it is
|
|
77
|
+
* held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
|
|
78
|
+
* any entry names a path no route answers. Some entries have no client yet
|
|
79
|
+
* (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
|
|
80
|
+
* deliberately does not reach); the fence is what keeps those honest rather
|
|
81
|
+
* than merely asserted.
|
|
69
82
|
*
|
|
70
83
|
* **The operator surface is deliberately absent.** `/admin/*` paths belong
|
|
71
84
|
* to `web/my`, for the same reason its row types do: this package is
|
|
@@ -324,6 +337,41 @@ export interface DomainRecordsResponse {
|
|
|
324
337
|
records: DnsRecord[];
|
|
325
338
|
}
|
|
326
339
|
|
|
340
|
+
/**
|
|
341
|
+
* The envelope an `Idempotency-Key` must fit, and how long a replay lasts.
|
|
342
|
+
*
|
|
343
|
+
* Format lives here rather than on the server alone by the format-vs-policy
|
|
344
|
+
* rule: a client can decide offline whether a key is well-formed, and the
|
|
345
|
+
* API would reject the same value the same way.
|
|
346
|
+
*/
|
|
347
|
+
export const IDEMPOTENCY_KEY_CONSTRAINTS = {
|
|
348
|
+
MAX_LENGTH: 256,
|
|
349
|
+
/** How long a stored 201 stays replayable. */
|
|
350
|
+
WINDOW_SECONDS: 24 * 60 * 60,
|
|
351
|
+
} as const;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Validate an idempotency key, returning the trimmed value or `undefined`
|
|
355
|
+
* when none was supplied. Throws {@link ShipError.validation} when the value
|
|
356
|
+
* cannot be sent — the same verdict the API would reach, reached earlier.
|
|
357
|
+
*/
|
|
358
|
+
export function validateIdempotencyKey(value: unknown): string | undefined {
|
|
359
|
+
if (value === undefined || value === null) return undefined;
|
|
360
|
+
if (typeof value !== 'string') {
|
|
361
|
+
throw ShipError.validation('Idempotency key must be a string.');
|
|
362
|
+
}
|
|
363
|
+
const key = value.trim();
|
|
364
|
+
if (!key) {
|
|
365
|
+
throw ShipError.validation('Idempotency key must not be empty.');
|
|
366
|
+
}
|
|
367
|
+
if (key.length > IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH) {
|
|
368
|
+
throw ShipError.validation(
|
|
369
|
+
`Idempotency key must be at most ${IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH} characters.`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return key;
|
|
373
|
+
}
|
|
374
|
+
|
|
327
375
|
/**
|
|
328
376
|
* Response for `GET /labels` — every label in use across the caller's
|
|
329
377
|
* deployments, domains and tokens, grouped and ordered by last use.
|
|
@@ -654,6 +702,15 @@ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
|
|
|
654
702
|
Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)),
|
|
655
703
|
);
|
|
656
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Ceiling on a message adopted from a **non-JSON** error body — a foreign
|
|
707
|
+
* responder's, never this platform's. Generous for the plain-text one-liners
|
|
708
|
+
* intermediaries actually send (`error code: 1015`), far below a document.
|
|
709
|
+
* Our own messages are never measured against it: a JSON body is the API's
|
|
710
|
+
* contract, and truncating a long validation message would be the bug.
|
|
711
|
+
*/
|
|
712
|
+
const MAX_FOREIGN_MESSAGE_LENGTH = 200;
|
|
713
|
+
|
|
657
714
|
/**
|
|
658
715
|
* Standard error response format used everywhere
|
|
659
716
|
*/
|
|
@@ -740,8 +797,17 @@ export class ShipError extends Error {
|
|
|
740
797
|
}
|
|
741
798
|
}
|
|
742
799
|
} else {
|
|
743
|
-
|
|
744
|
-
|
|
800
|
+
// A non-JSON body did not come from this platform — every API error
|
|
801
|
+
// is `ErrorResponse` JSON — so it is an intermediary's output, and
|
|
802
|
+
// the two kinds it produces need opposite treatment. A CDN's plain
|
|
803
|
+
// `error code: 1015` is the most useful thing there is to say. A
|
|
804
|
+
// proxy's HTML error page is a *document*, not a message: adopting it
|
|
805
|
+
// verbatim made a misconfigured `apiUrl` print 2,059 characters of
|
|
806
|
+
// markup as the error. Trust it only when it reads as a message.
|
|
807
|
+
const text = (await response.text()).trim();
|
|
808
|
+
if (text && !text.startsWith('<') && text.length <= MAX_FOREIGN_MESSAGE_LENGTH) {
|
|
809
|
+
message = text;
|
|
810
|
+
}
|
|
745
811
|
}
|
|
746
812
|
} catch {
|
|
747
813
|
// Body unreadable; fall through to operationName-derived message.
|
|
@@ -1534,6 +1600,25 @@ export interface DeploymentUploadOptions {
|
|
|
1534
1600
|
spa?: boolean;
|
|
1535
1601
|
/** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
|
|
1536
1602
|
captcha?: string;
|
|
1603
|
+
/**
|
|
1604
|
+
* Makes this deploy replayable instead of repeatable.
|
|
1605
|
+
*
|
|
1606
|
+
* A deploy is not naturally idempotent: a client-side timeout on a slow
|
|
1607
|
+
* one leaves the caller unable to tell "it never landed" from "it landed
|
|
1608
|
+
* and the response was lost", and retrying produces a second deployment.
|
|
1609
|
+
* Send the same key on the retry and the platform replays the original
|
|
1610
|
+
* 201 verbatim rather than creating anything
|
|
1611
|
+
* ({@link IDEMPOTENCY_KEY_CONSTRAINTS.WINDOW_SECONDS}).
|
|
1612
|
+
*
|
|
1613
|
+
* **Agents are the audience.** A human notices a duplicate; an automated
|
|
1614
|
+
* retry does not. Pick a key that identifies the ATTEMPT — a run id, a
|
|
1615
|
+
* commit sha, a uuid minted before the first try — never one that varies
|
|
1616
|
+
* per attempt, which would defeat the point.
|
|
1617
|
+
*
|
|
1618
|
+
* The replay is per-caller, and it stores successes only: a failed deploy
|
|
1619
|
+
* retries fresh under the same key.
|
|
1620
|
+
*/
|
|
1621
|
+
idempotencyKey?: string;
|
|
1537
1622
|
}
|
|
1538
1623
|
|
|
1539
1624
|
/**
|