@shipstatic/types 2.5.0-beta.21 → 2.5.0-beta.23

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/dist/index.d.ts CHANGED
@@ -79,97 +79,6 @@ export interface DeploymentCreateResponse extends Deployment {
79
79
  /** Claim URL for public deployments. Present when deployed without credentials. */
80
80
  readonly claim?: string;
81
81
  }
82
- /**
83
- * Every path the public API answers on, declared once.
84
- *
85
- * The URL surface was written out in four places — the API's mounts, the
86
- * SDK's client, the dashboard's client, and the post-deploy smoke — so a
87
- * rename meant finding all four. The first three now read this table.
88
- *
89
- * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
90
- * five of its nine paths are `/admin/*`, which this table excludes by
91
- * design, and splitting one list between a registry and literals reads worse
92
- * than keeping it uniform.
93
- *
94
- * **What this guarantees, exactly.** Collection paths are mounted from here,
95
- * so producer and consumer cannot diverge. Item paths are declared here and
96
- * consumed by clients, but the API spells them relative to their mount
97
- * (`/:deployment/config`), so the table does not *generate* them — it is
98
- * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
99
- * any entry names a path no route answers. Some entries have no client yet
100
- * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
101
- * deliberately does not reach); the fence is what keeps those honest rather
102
- * than merely asserted.
103
- *
104
- * **The operator surface is deliberately absent.** `/admin/*` paths belong
105
- * to `web/my`, for the same reason its row types do: this package is
106
- * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
107
- * types"). A path here is a promise to every npm consumer; `/admin` is a
108
- * promise to one dashboard.
109
- *
110
- * Item paths are functions rather than templates so the key is interpolated
111
- * in one place, encoded the same way by every caller.
112
- */
113
- export declare const API_PATHS: {
114
- readonly DEPLOYMENTS: "/deployments";
115
- readonly DEPLOYMENT: (deployment: string) => string;
116
- readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
117
- readonly DOMAINS: "/domains";
118
- readonly DOMAIN: (domain: string) => string;
119
- readonly DOMAIN_VERIFY: (domain: string) => string;
120
- readonly DOMAIN_DNS: (domain: string) => string;
121
- readonly DOMAIN_RECORDS: (domain: string) => string;
122
- readonly DOMAIN_SHARE: (domain: string) => string;
123
- readonly DOMAIN_PROPAGATION: (domain: string) => string;
124
- readonly DOMAINS_VALIDATE: "/domains/validate";
125
- readonly TOKENS: "/tokens";
126
- readonly TOKEN: (token: string) => string;
127
- readonly ACCOUNT: "/account";
128
- readonly ACCOUNT_KEY: "/account/key";
129
- readonly ACCOUNT_CLAIM: "/account/claim";
130
- readonly ACTIVITIES: "/activities";
131
- readonly LABELS: "/labels";
132
- readonly LIMITS: "/limits";
133
- readonly PING: "/ping";
134
- readonly SETUP: "/setup";
135
- readonly SPA_CHECK: "/spa-check";
136
- readonly UPLOAD: "/upload";
137
- };
138
- /**
139
- * The deploy request's multipart field names — the other half of the wire
140
- * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
141
- * `/upload`) is multipart/form-data, and these are the names the API reads.
142
- *
143
- * Declared once because the body has three independent WRITERS — the SDK's
144
- * Node and browser body builders, and the n8n community node's hand-rolled
145
- * client (which cannot import this under n8n Cloud's zero-dependency rule,
146
- * and fences its restated copy instead) — and until this export every writer
147
- * restated the strings the API parses, with nothing comparing them.
148
- *
149
- * `FILES` carries one entry per file (the API reads it with `getAll`); every
150
- * other field is single. The `@internal` flags are serialized as the literal
151
- * string `'true'` and belong to first-party surfaces only.
152
- */
153
- export declare const DEPLOY_FIELDS: {
154
- /** One entry per file — read with `getAll`. */
155
- readonly FILES: "files[]";
156
- /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
157
- readonly CHECKSUMS: "checksums";
158
- /** JSON array of label strings. */
159
- readonly LABELS: "labels";
160
- /** The deploying surface's {@link DeploymentVia} member. */
161
- readonly VIA: "via";
162
- /** Plaintext password — the API hashes it server-side. */
163
- readonly PASSWORD: "password";
164
- /** @internal Server-processing flag — first-party `/upload` only. */
165
- readonly BUILD: "build";
166
- /** @internal Server-processing flag — first-party `/upload` only. */
167
- readonly PRERENDER: "prerender";
168
- /** @internal Server-processing flag — first-party `/upload` only. */
169
- readonly SPA: "spa";
170
- /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
171
- readonly CAPTCHA: "captcha";
172
- };
173
82
  /**
174
83
  * The half of a list response that is identical on every list.
175
84
  *
@@ -188,6 +97,28 @@ export interface ListResponse {
188
97
  /** Opaque cursor from this page; `null` on the last page. */
189
98
  cursor: string | null;
190
99
  }
100
+ /**
101
+ * Pagination options for every list endpoint. The response's `cursor` feeds
102
+ * the next request; a `null` cursor means the last page. Omitting both
103
+ * returns the server's default first page.
104
+ *
105
+ * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
106
+ * carries the entire has-more signal, so no redundant boolean, and no
107
+ * `total`. **A count is an aggregate over a collection, not a property of a
108
+ * page:** including one makes every read pay for a full scan it did not ask
109
+ * for, which is precisely the cost keyset pagination exists to avoid.
110
+ *
111
+ * Counts therefore live on the summary resource that owns them —
112
+ * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
113
+ * platform-wide ones. Ask for a count when you want a count; ask for a page
114
+ * when you want a page.
115
+ */
116
+ export interface ListOptions {
117
+ /** Maximum number of items to return in one page. */
118
+ limit?: number;
119
+ /** Opaque cursor from the previous page's response. */
120
+ cursor?: string;
121
+ }
191
122
  /**
192
123
  * Response for listing deployments
193
124
  */
@@ -658,6 +589,97 @@ export interface AccountOverrides {
658
589
  /** Override for maximum total deployment size in bytes */
659
590
  totalSize?: number;
660
591
  }
592
+ /**
593
+ * Every path the public API answers on, declared once.
594
+ *
595
+ * The URL surface was written out in four places — the API's mounts, the
596
+ * SDK's client, the dashboard's client, and the post-deploy smoke — so a
597
+ * rename meant finding all four. The first three now read this table.
598
+ *
599
+ * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
600
+ * five of its nine paths are `/admin/*`, which this table excludes by
601
+ * design, and splitting one list between a registry and literals reads worse
602
+ * than keeping it uniform.
603
+ *
604
+ * **What this guarantees, exactly.** Collection paths are mounted from here,
605
+ * so producer and consumer cannot diverge. Item paths are declared here and
606
+ * consumed by clients, but the API spells them relative to their mount
607
+ * (`/:deployment/config`), so the table does not *generate* them — it is
608
+ * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
609
+ * any entry names a path no route answers. Some entries have no client yet
610
+ * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
611
+ * deliberately does not reach); the fence is what keeps those honest rather
612
+ * than merely asserted.
613
+ *
614
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
615
+ * to `web/my`, for the same reason its row types do: this package is
616
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
617
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
618
+ * promise to one dashboard.
619
+ *
620
+ * Item paths are functions rather than templates so the key is interpolated
621
+ * in one place, encoded the same way by every caller.
622
+ */
623
+ export declare const API_PATHS: {
624
+ readonly DEPLOYMENTS: "/deployments";
625
+ readonly DEPLOYMENT: (deployment: string) => string;
626
+ readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
627
+ readonly DOMAINS: "/domains";
628
+ readonly DOMAIN: (domain: string) => string;
629
+ readonly DOMAIN_VERIFY: (domain: string) => string;
630
+ readonly DOMAIN_DNS: (domain: string) => string;
631
+ readonly DOMAIN_RECORDS: (domain: string) => string;
632
+ readonly DOMAIN_SHARE: (domain: string) => string;
633
+ readonly DOMAIN_PROPAGATION: (domain: string) => string;
634
+ readonly DOMAINS_VALIDATE: "/domains/validate";
635
+ readonly TOKENS: "/tokens";
636
+ readonly TOKEN: (token: string) => string;
637
+ readonly ACCOUNT: "/account";
638
+ readonly ACCOUNT_KEY: "/account/key";
639
+ readonly ACCOUNT_CLAIM: "/account/claim";
640
+ readonly ACTIVITIES: "/activities";
641
+ readonly LABELS: "/labels";
642
+ readonly LIMITS: "/limits";
643
+ readonly PING: "/ping";
644
+ readonly SETUP: "/setup";
645
+ readonly SPA_CHECK: "/spa-check";
646
+ readonly UPLOAD: "/upload";
647
+ };
648
+ /**
649
+ * The deploy request's multipart field names — the other half of the wire
650
+ * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
651
+ * `/upload`) is multipart/form-data, and these are the names the API reads.
652
+ *
653
+ * Declared once because the body has three independent WRITERS — the SDK's
654
+ * Node and browser body builders, and the n8n community node's hand-rolled
655
+ * client (which cannot import this under n8n Cloud's zero-dependency rule,
656
+ * and fences its restated copy instead) — and until this export every writer
657
+ * restated the strings the API parses, with nothing comparing them.
658
+ *
659
+ * `FILES` carries one entry per file (the API reads it with `getAll`); every
660
+ * other field is single. The `@internal` flags are serialized as the literal
661
+ * string `'true'` and belong to first-party surfaces only.
662
+ */
663
+ export declare const DEPLOY_FIELDS: {
664
+ /** One entry per file — read with `getAll`. */
665
+ readonly FILES: "files[]";
666
+ /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
667
+ readonly CHECKSUMS: "checksums";
668
+ /** JSON array of label strings. */
669
+ readonly LABELS: "labels";
670
+ /** The deploying surface's {@link DeploymentVia} member. */
671
+ readonly VIA: "via";
672
+ /** Plaintext password — the API hashes it server-side. */
673
+ readonly PASSWORD: "password";
674
+ /** @internal Server-processing flag — first-party `/upload` only. */
675
+ readonly BUILD: "build";
676
+ /** @internal Server-processing flag — first-party `/upload` only. */
677
+ readonly PRERENDER: "prerender";
678
+ /** @internal Server-processing flag — first-party `/upload` only. */
679
+ readonly SPA: "spa";
680
+ /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
681
+ readonly CAPTCHA: "captcha";
682
+ };
661
683
  /**
662
684
  * All possible error types in the ShipStatic platform.
663
685
  *
@@ -690,6 +712,17 @@ export declare const ErrorType: {
690
712
  readonly Business: "business_logic_error";
691
713
  /** API server error (500). Generic server-side fault. */
692
714
  readonly Api: "internal_server_error";
715
+ /**
716
+ * The platform is closed for maintenance (503). A deliberate operator
717
+ * state, not a fault — nothing errored; the API is refusing work on
718
+ * purpose, and deployed sites keep serving throughout.
719
+ *
720
+ * Distinct from `Api` at 503, which the platform already uses for a
721
+ * dependency that failed (moderation unavailable). A consumer has to tell
722
+ * "we closed the door" from "something broke": the two get opposite words
723
+ * and opposite retry behaviour.
724
+ */
725
+ readonly Maintenance: "maintenance";
693
726
  /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
694
727
  readonly Network: "network_error";
695
728
  /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
@@ -790,6 +823,16 @@ export declare class ShipError extends Error {
790
823
  static file(message: string, details?: unknown): ShipError;
791
824
  static config(message: string, details?: unknown): ShipError;
792
825
  static api(message: string, status?: number, details?: unknown): ShipError;
826
+ /**
827
+ * The platform is closed for maintenance (503).
828
+ *
829
+ * `message` is REQUIRED and has no default here. The API is the only
830
+ * producer of that sentence, and a default in this file would be a second
831
+ * owner of one fact — see CLAUDE.md, "The Constellation Law" (stopping
832
+ * rule). It is also the one factory whose status is fixed rather than
833
+ * defaulted: a maintenance refusal is 503 or it is not this error.
834
+ */
835
+ static maintenance(message: string, details?: unknown): ShipError;
793
836
  /**
794
837
  * The caller is at fault — by HTTP's own definition of a 4xx, or by a type
795
838
  * that is client-attributable without ever having a status (`Config`,
@@ -1309,28 +1352,6 @@ export interface DeploymentUploadOptions {
1309
1352
  */
1310
1353
  idempotencyKey?: string;
1311
1354
  }
1312
- /**
1313
- * Pagination options for every list endpoint. The response's `cursor` feeds
1314
- * the next request; a `null` cursor means the last page. Omitting both
1315
- * returns the server's default first page.
1316
- *
1317
- * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
1318
- * carries the entire has-more signal, so no redundant boolean, and no
1319
- * `total`. **A count is an aggregate over a collection, not a property of a
1320
- * page:** including one makes every read pay for a full scan it did not ask
1321
- * for, which is precisely the cost keyset pagination exists to avoid.
1322
- *
1323
- * Counts therefore live on the summary resource that owns them —
1324
- * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
1325
- * platform-wide ones. Ask for a count when you want a count; ask for a page
1326
- * when you want a page.
1327
- */
1328
- export interface ListOptions {
1329
- /** Maximum number of items to return in one page. */
1330
- limit?: number;
1331
- /** Opaque cursor from the previous page's response. */
1332
- cursor?: string;
1333
- }
1334
1355
  /**
1335
1356
  * What a caller may change on an existing deployment.
1336
1357
  *
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * This package is the single source of truth for all shared data structures.
4
4
  */
5
5
  // =============================================================================
6
- // I. CORE ENTITIES
6
+ // DEPLOYMENT TYPES
7
7
  // =============================================================================
8
8
  /**
9
9
  * Deployment status constants
@@ -35,97 +35,6 @@ export const DeploymentVia = {
35
35
  GPT: 'gpt',
36
36
  VSC: 'vsc',
37
37
  };
38
- /**
39
- * Every path the public API answers on, declared once.
40
- *
41
- * The URL surface was written out in four places — the API's mounts, the
42
- * SDK's client, the dashboard's client, and the post-deploy smoke — so a
43
- * rename meant finding all four. The first three now read this table.
44
- *
45
- * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
46
- * five of its nine paths are `/admin/*`, which this table excludes by
47
- * design, and splitting one list between a registry and literals reads worse
48
- * than keeping it uniform.
49
- *
50
- * **What this guarantees, exactly.** Collection paths are mounted from here,
51
- * so producer and consumer cannot diverge. Item paths are declared here and
52
- * consumed by clients, but the API spells them relative to their mount
53
- * (`/:deployment/config`), so the table does not *generate* them — it is
54
- * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
55
- * any entry names a path no route answers. Some entries have no client yet
56
- * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
57
- * deliberately does not reach); the fence is what keeps those honest rather
58
- * than merely asserted.
59
- *
60
- * **The operator surface is deliberately absent.** `/admin/*` paths belong
61
- * to `web/my`, for the same reason its row types do: this package is
62
- * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
63
- * types"). A path here is a promise to every npm consumer; `/admin` is a
64
- * promise to one dashboard.
65
- *
66
- * Item paths are functions rather than templates so the key is interpolated
67
- * in one place, encoded the same way by every caller.
68
- */
69
- export const API_PATHS = {
70
- DEPLOYMENTS: '/deployments',
71
- DEPLOYMENT: (deployment) => `/deployments/${deployment}`,
72
- DEPLOYMENT_CONFIG: (deployment) => `/deployments/${deployment}/config`,
73
- DOMAINS: '/domains',
74
- DOMAIN: (domain) => `/domains/${domain}`,
75
- DOMAIN_VERIFY: (domain) => `/domains/${domain}/verify`,
76
- DOMAIN_DNS: (domain) => `/domains/${domain}/dns`,
77
- DOMAIN_RECORDS: (domain) => `/domains/${domain}/records`,
78
- DOMAIN_SHARE: (domain) => `/domains/${domain}/share`,
79
- DOMAIN_PROPAGATION: (domain) => `/domains/${domain}/propagation`,
80
- DOMAINS_VALIDATE: '/domains/validate',
81
- TOKENS: '/tokens',
82
- TOKEN: (token) => `/tokens/${token}`,
83
- ACCOUNT: '/account',
84
- ACCOUNT_KEY: '/account/key',
85
- ACCOUNT_CLAIM: '/account/claim',
86
- ACTIVITIES: '/activities',
87
- LABELS: '/labels',
88
- LIMITS: '/limits',
89
- PING: '/ping',
90
- SETUP: '/setup',
91
- SPA_CHECK: '/spa-check',
92
- UPLOAD: '/upload',
93
- };
94
- /**
95
- * The deploy request's multipart field names — the other half of the wire
96
- * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
97
- * `/upload`) is multipart/form-data, and these are the names the API reads.
98
- *
99
- * Declared once because the body has three independent WRITERS — the SDK's
100
- * Node and browser body builders, and the n8n community node's hand-rolled
101
- * client (which cannot import this under n8n Cloud's zero-dependency rule,
102
- * and fences its restated copy instead) — and until this export every writer
103
- * restated the strings the API parses, with nothing comparing them.
104
- *
105
- * `FILES` carries one entry per file (the API reads it with `getAll`); every
106
- * other field is single. The `@internal` flags are serialized as the literal
107
- * string `'true'` and belong to first-party surfaces only.
108
- */
109
- export const DEPLOY_FIELDS = {
110
- /** One entry per file — read with `getAll`. */
111
- FILES: 'files[]',
112
- /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
113
- CHECKSUMS: 'checksums',
114
- /** JSON array of label strings. */
115
- LABELS: 'labels',
116
- /** The deploying surface's {@link DeploymentVia} member. */
117
- VIA: 'via',
118
- /** Plaintext password — the API hashes it server-side. */
119
- PASSWORD: 'password',
120
- /** @internal Server-processing flag — first-party `/upload` only. */
121
- BUILD: 'build',
122
- /** @internal Server-processing flag — first-party `/upload` only. */
123
- PRERENDER: 'prerender',
124
- /** @internal Server-processing flag — first-party `/upload` only. */
125
- SPA: 'spa',
126
- /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
127
- CAPTCHA: 'captcha',
128
- };
129
38
  // =============================================================================
130
39
  // DOMAIN TYPES
131
40
  // =============================================================================
@@ -221,6 +130,100 @@ export const AccountPlan = {
221
130
  TERMINATED: 'terminated',
222
131
  };
223
132
  // =============================================================================
133
+ // WIRE SURFACE
134
+ // =============================================================================
135
+ /**
136
+ * Every path the public API answers on, declared once.
137
+ *
138
+ * The URL surface was written out in four places — the API's mounts, the
139
+ * SDK's client, the dashboard's client, and the post-deploy smoke — so a
140
+ * rename meant finding all four. The first three now read this table.
141
+ *
142
+ * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
143
+ * five of its nine paths are `/admin/*`, which this table excludes by
144
+ * design, and splitting one list between a registry and literals reads worse
145
+ * than keeping it uniform.
146
+ *
147
+ * **What this guarantees, exactly.** Collection paths are mounted from here,
148
+ * so producer and consumer cannot diverge. Item paths are declared here and
149
+ * consumed by clients, but the API spells them relative to their mount
150
+ * (`/:deployment/config`), so the table does not *generate* them — it is
151
+ * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
152
+ * any entry names a path no route answers. Some entries have no client yet
153
+ * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
154
+ * deliberately does not reach); the fence is what keeps those honest rather
155
+ * than merely asserted.
156
+ *
157
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
158
+ * to `web/my`, for the same reason its row types do: this package is
159
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
160
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
161
+ * promise to one dashboard.
162
+ *
163
+ * Item paths are functions rather than templates so the key is interpolated
164
+ * in one place, encoded the same way by every caller.
165
+ */
166
+ export const API_PATHS = {
167
+ DEPLOYMENTS: '/deployments',
168
+ DEPLOYMENT: (deployment) => `/deployments/${deployment}`,
169
+ DEPLOYMENT_CONFIG: (deployment) => `/deployments/${deployment}/config`,
170
+ DOMAINS: '/domains',
171
+ DOMAIN: (domain) => `/domains/${domain}`,
172
+ DOMAIN_VERIFY: (domain) => `/domains/${domain}/verify`,
173
+ DOMAIN_DNS: (domain) => `/domains/${domain}/dns`,
174
+ DOMAIN_RECORDS: (domain) => `/domains/${domain}/records`,
175
+ DOMAIN_SHARE: (domain) => `/domains/${domain}/share`,
176
+ DOMAIN_PROPAGATION: (domain) => `/domains/${domain}/propagation`,
177
+ DOMAINS_VALIDATE: '/domains/validate',
178
+ TOKENS: '/tokens',
179
+ TOKEN: (token) => `/tokens/${token}`,
180
+ ACCOUNT: '/account',
181
+ ACCOUNT_KEY: '/account/key',
182
+ ACCOUNT_CLAIM: '/account/claim',
183
+ ACTIVITIES: '/activities',
184
+ LABELS: '/labels',
185
+ LIMITS: '/limits',
186
+ PING: '/ping',
187
+ SETUP: '/setup',
188
+ SPA_CHECK: '/spa-check',
189
+ UPLOAD: '/upload',
190
+ };
191
+ /**
192
+ * The deploy request's multipart field names — the other half of the wire
193
+ * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
194
+ * `/upload`) is multipart/form-data, and these are the names the API reads.
195
+ *
196
+ * Declared once because the body has three independent WRITERS — the SDK's
197
+ * Node and browser body builders, and the n8n community node's hand-rolled
198
+ * client (which cannot import this under n8n Cloud's zero-dependency rule,
199
+ * and fences its restated copy instead) — and until this export every writer
200
+ * restated the strings the API parses, with nothing comparing them.
201
+ *
202
+ * `FILES` carries one entry per file (the API reads it with `getAll`); every
203
+ * other field is single. The `@internal` flags are serialized as the literal
204
+ * string `'true'` and belong to first-party surfaces only.
205
+ */
206
+ export const DEPLOY_FIELDS = {
207
+ /** One entry per file — read with `getAll`. */
208
+ FILES: 'files[]',
209
+ /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
210
+ CHECKSUMS: 'checksums',
211
+ /** JSON array of label strings. */
212
+ LABELS: 'labels',
213
+ /** The deploying surface's {@link DeploymentVia} member. */
214
+ VIA: 'via',
215
+ /** Plaintext password — the API hashes it server-side. */
216
+ PASSWORD: 'password',
217
+ /** @internal Server-processing flag — first-party `/upload` only. */
218
+ BUILD: 'build',
219
+ /** @internal Server-processing flag — first-party `/upload` only. */
220
+ PRERENDER: 'prerender',
221
+ /** @internal Server-processing flag — first-party `/upload` only. */
222
+ SPA: 'spa',
223
+ /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
224
+ CAPTCHA: 'captcha',
225
+ };
226
+ // =============================================================================
224
227
  // ERROR SYSTEM
225
228
  // =============================================================================
226
229
  /**
@@ -255,6 +258,17 @@ export const ErrorType = {
255
258
  Business: 'business_logic_error',
256
259
  /** API server error (500). Generic server-side fault. */
257
260
  Api: 'internal_server_error',
261
+ /**
262
+ * The platform is closed for maintenance (503). A deliberate operator
263
+ * state, not a fault — nothing errored; the API is refusing work on
264
+ * purpose, and deployed sites keep serving throughout.
265
+ *
266
+ * Distinct from `Api` at 503, which the platform already uses for a
267
+ * dependency that failed (moderation unavailable). A consumer has to tell
268
+ * "we closed the door" from "something broke": the two get opposite words
269
+ * and opposite retry behaviour.
270
+ */
271
+ Maintenance: 'maintenance',
258
272
  /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
259
273
  Network: 'network_error',
260
274
  /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
@@ -559,6 +573,18 @@ export class ShipError extends Error {
559
573
  static api(message, status = 500, details) {
560
574
  return new ShipError(ErrorType.Api, message, status, details);
561
575
  }
576
+ /**
577
+ * The platform is closed for maintenance (503).
578
+ *
579
+ * `message` is REQUIRED and has no default here. The API is the only
580
+ * producer of that sentence, and a default in this file would be a second
581
+ * owner of one fact — see CLAUDE.md, "The Constellation Law" (stopping
582
+ * rule). It is also the one factory whose status is fixed rather than
583
+ * defaulted: a maintenance refusal is 503 or it is not this error.
584
+ */
585
+ static maintenance(message, details) {
586
+ return new ShipError(ErrorType.Maintenance, message, 503, details);
587
+ }
562
588
  // Semantic-category guards. For specific-type checks, use
563
589
  // `error.type === ErrorType.X` directly or the generic `isType(t)`.
564
590
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.5.0-beta.21",
3
+ "version": "2.5.0-beta.23",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  // =============================================================================
7
- // I. CORE ENTITIES
7
+ // DEPLOYMENT TYPES
8
8
  // =============================================================================
9
9
 
10
10
  /**
@@ -90,99 +90,6 @@ export interface DeploymentCreateResponse extends Deployment {
90
90
  readonly claim?: string;
91
91
  }
92
92
 
93
- /**
94
- * Every path the public API answers on, declared once.
95
- *
96
- * The URL surface was written out in four places — the API's mounts, the
97
- * SDK's client, the dashboard's client, and the post-deploy smoke — so a
98
- * rename meant finding all four. The first three now read this table.
99
- *
100
- * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
101
- * five of its nine paths are `/admin/*`, which this table excludes by
102
- * design, and splitting one list between a registry and literals reads worse
103
- * than keeping it uniform.
104
- *
105
- * **What this guarantees, exactly.** Collection paths are mounted from here,
106
- * so producer and consumer cannot diverge. Item paths are declared here and
107
- * consumed by clients, but the API spells them relative to their mount
108
- * (`/:deployment/config`), so the table does not *generate* them — it is
109
- * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
110
- * any entry names a path no route answers. Some entries have no client yet
111
- * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
112
- * deliberately does not reach); the fence is what keeps those honest rather
113
- * than merely asserted.
114
- *
115
- * **The operator surface is deliberately absent.** `/admin/*` paths belong
116
- * to `web/my`, for the same reason its row types do: this package is
117
- * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
118
- * types"). A path here is a promise to every npm consumer; `/admin` is a
119
- * promise to one dashboard.
120
- *
121
- * Item paths are functions rather than templates so the key is interpolated
122
- * in one place, encoded the same way by every caller.
123
- */
124
- export const API_PATHS = {
125
- DEPLOYMENTS: '/deployments',
126
- DEPLOYMENT: (deployment: string) => `/deployments/${deployment}`,
127
- DEPLOYMENT_CONFIG: (deployment: string) => `/deployments/${deployment}/config`,
128
- DOMAINS: '/domains',
129
- DOMAIN: (domain: string) => `/domains/${domain}`,
130
- DOMAIN_VERIFY: (domain: string) => `/domains/${domain}/verify`,
131
- DOMAIN_DNS: (domain: string) => `/domains/${domain}/dns`,
132
- DOMAIN_RECORDS: (domain: string) => `/domains/${domain}/records`,
133
- DOMAIN_SHARE: (domain: string) => `/domains/${domain}/share`,
134
- DOMAIN_PROPAGATION: (domain: string) => `/domains/${domain}/propagation`,
135
- DOMAINS_VALIDATE: '/domains/validate',
136
- TOKENS: '/tokens',
137
- TOKEN: (token: string) => `/tokens/${token}`,
138
- ACCOUNT: '/account',
139
- ACCOUNT_KEY: '/account/key',
140
- ACCOUNT_CLAIM: '/account/claim',
141
- ACTIVITIES: '/activities',
142
- LABELS: '/labels',
143
- LIMITS: '/limits',
144
- PING: '/ping',
145
- SETUP: '/setup',
146
- SPA_CHECK: '/spa-check',
147
- UPLOAD: '/upload',
148
- } as const;
149
-
150
- /**
151
- * The deploy request's multipart field names — the other half of the wire
152
- * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
153
- * `/upload`) is multipart/form-data, and these are the names the API reads.
154
- *
155
- * Declared once because the body has three independent WRITERS — the SDK's
156
- * Node and browser body builders, and the n8n community node's hand-rolled
157
- * client (which cannot import this under n8n Cloud's zero-dependency rule,
158
- * and fences its restated copy instead) — and until this export every writer
159
- * restated the strings the API parses, with nothing comparing them.
160
- *
161
- * `FILES` carries one entry per file (the API reads it with `getAll`); every
162
- * other field is single. The `@internal` flags are serialized as the literal
163
- * string `'true'` and belong to first-party surfaces only.
164
- */
165
- export const DEPLOY_FIELDS = {
166
- /** One entry per file — read with `getAll`. */
167
- FILES: 'files[]',
168
- /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
169
- CHECKSUMS: 'checksums',
170
- /** JSON array of label strings. */
171
- LABELS: 'labels',
172
- /** The deploying surface's {@link DeploymentVia} member. */
173
- VIA: 'via',
174
- /** Plaintext password — the API hashes it server-side. */
175
- PASSWORD: 'password',
176
- /** @internal Server-processing flag — first-party `/upload` only. */
177
- BUILD: 'build',
178
- /** @internal Server-processing flag — first-party `/upload` only. */
179
- PRERENDER: 'prerender',
180
- /** @internal Server-processing flag — first-party `/upload` only. */
181
- SPA: 'spa',
182
- /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
183
- CAPTCHA: 'captcha',
184
- } as const;
185
-
186
93
  /**
187
94
  * The half of a list response that is identical on every list.
188
95
  *
@@ -202,6 +109,29 @@ export interface ListResponse {
202
109
  cursor: string | null;
203
110
  }
204
111
 
112
+ /**
113
+ * Pagination options for every list endpoint. The response's `cursor` feeds
114
+ * the next request; a `null` cursor means the last page. Omitting both
115
+ * returns the server's default first page.
116
+ *
117
+ * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
118
+ * carries the entire has-more signal, so no redundant boolean, and no
119
+ * `total`. **A count is an aggregate over a collection, not a property of a
120
+ * page:** including one makes every read pay for a full scan it did not ask
121
+ * for, which is precisely the cost keyset pagination exists to avoid.
122
+ *
123
+ * Counts therefore live on the summary resource that owns them —
124
+ * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
125
+ * platform-wide ones. Ask for a count when you want a count; ask for a page
126
+ * when you want a page.
127
+ */
128
+ export interface ListOptions {
129
+ /** Maximum number of items to return in one page. */
130
+ limit?: number;
131
+ /** Opaque cursor from the previous page's response. */
132
+ cursor?: string;
133
+ }
134
+
205
135
  /**
206
136
  * Response for listing deployments
207
137
  */
@@ -739,6 +669,103 @@ export interface AccountOverrides {
739
669
  totalSize?: number;
740
670
  }
741
671
 
672
+ // =============================================================================
673
+ // WIRE SURFACE
674
+ // =============================================================================
675
+
676
+ /**
677
+ * Every path the public API answers on, declared once.
678
+ *
679
+ * The URL surface was written out in four places — the API's mounts, the
680
+ * SDK's client, the dashboard's client, and the post-deploy smoke — so a
681
+ * rename meant finding all four. The first three now read this table.
682
+ *
683
+ * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
684
+ * five of its nine paths are `/admin/*`, which this table excludes by
685
+ * design, and splitting one list between a registry and literals reads worse
686
+ * than keeping it uniform.
687
+ *
688
+ * **What this guarantees, exactly.** Collection paths are mounted from here,
689
+ * so producer and consumer cannot diverge. Item paths are declared here and
690
+ * consumed by clients, but the API spells them relative to their mount
691
+ * (`/:deployment/config`), so the table does not *generate* them — it is
692
+ * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
693
+ * any entry names a path no route answers. Some entries have no client yet
694
+ * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
695
+ * deliberately does not reach); the fence is what keeps those honest rather
696
+ * than merely asserted.
697
+ *
698
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
699
+ * to `web/my`, for the same reason its row types do: this package is
700
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
701
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
702
+ * promise to one dashboard.
703
+ *
704
+ * Item paths are functions rather than templates so the key is interpolated
705
+ * in one place, encoded the same way by every caller.
706
+ */
707
+ export const API_PATHS = {
708
+ DEPLOYMENTS: '/deployments',
709
+ DEPLOYMENT: (deployment: string) => `/deployments/${deployment}`,
710
+ DEPLOYMENT_CONFIG: (deployment: string) => `/deployments/${deployment}/config`,
711
+ DOMAINS: '/domains',
712
+ DOMAIN: (domain: string) => `/domains/${domain}`,
713
+ DOMAIN_VERIFY: (domain: string) => `/domains/${domain}/verify`,
714
+ DOMAIN_DNS: (domain: string) => `/domains/${domain}/dns`,
715
+ DOMAIN_RECORDS: (domain: string) => `/domains/${domain}/records`,
716
+ DOMAIN_SHARE: (domain: string) => `/domains/${domain}/share`,
717
+ DOMAIN_PROPAGATION: (domain: string) => `/domains/${domain}/propagation`,
718
+ DOMAINS_VALIDATE: '/domains/validate',
719
+ TOKENS: '/tokens',
720
+ TOKEN: (token: string) => `/tokens/${token}`,
721
+ ACCOUNT: '/account',
722
+ ACCOUNT_KEY: '/account/key',
723
+ ACCOUNT_CLAIM: '/account/claim',
724
+ ACTIVITIES: '/activities',
725
+ LABELS: '/labels',
726
+ LIMITS: '/limits',
727
+ PING: '/ping',
728
+ SETUP: '/setup',
729
+ SPA_CHECK: '/spa-check',
730
+ UPLOAD: '/upload',
731
+ } as const;
732
+
733
+ /**
734
+ * The deploy request's multipart field names — the other half of the wire
735
+ * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
736
+ * `/upload`) is multipart/form-data, and these are the names the API reads.
737
+ *
738
+ * Declared once because the body has three independent WRITERS — the SDK's
739
+ * Node and browser body builders, and the n8n community node's hand-rolled
740
+ * client (which cannot import this under n8n Cloud's zero-dependency rule,
741
+ * and fences its restated copy instead) — and until this export every writer
742
+ * restated the strings the API parses, with nothing comparing them.
743
+ *
744
+ * `FILES` carries one entry per file (the API reads it with `getAll`); every
745
+ * other field is single. The `@internal` flags are serialized as the literal
746
+ * string `'true'` and belong to first-party surfaces only.
747
+ */
748
+ export const DEPLOY_FIELDS = {
749
+ /** One entry per file — read with `getAll`. */
750
+ FILES: 'files[]',
751
+ /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
752
+ CHECKSUMS: 'checksums',
753
+ /** JSON array of label strings. */
754
+ LABELS: 'labels',
755
+ /** The deploying surface's {@link DeploymentVia} member. */
756
+ VIA: 'via',
757
+ /** Plaintext password — the API hashes it server-side. */
758
+ PASSWORD: 'password',
759
+ /** @internal Server-processing flag — first-party `/upload` only. */
760
+ BUILD: 'build',
761
+ /** @internal Server-processing flag — first-party `/upload` only. */
762
+ PRERENDER: 'prerender',
763
+ /** @internal Server-processing flag — first-party `/upload` only. */
764
+ SPA: 'spa',
765
+ /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
766
+ CAPTCHA: 'captcha',
767
+ } as const;
768
+
742
769
  // =============================================================================
743
770
  // ERROR SYSTEM
744
771
  // =============================================================================
@@ -775,6 +802,17 @@ export const ErrorType = {
775
802
  Business: 'business_logic_error',
776
803
  /** API server error (500). Generic server-side fault. */
777
804
  Api: 'internal_server_error',
805
+ /**
806
+ * The platform is closed for maintenance (503). A deliberate operator
807
+ * state, not a fault — nothing errored; the API is refusing work on
808
+ * purpose, and deployed sites keep serving throughout.
809
+ *
810
+ * Distinct from `Api` at 503, which the platform already uses for a
811
+ * dependency that failed (moderation unavailable). A consumer has to tell
812
+ * "we closed the door" from "something broke": the two get opposite words
813
+ * and opposite retry behaviour.
814
+ */
815
+ Maintenance: 'maintenance',
778
816
  /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
779
817
  Network: 'network_error',
780
818
  /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
@@ -1124,6 +1162,19 @@ export class ShipError extends Error {
1124
1162
  return new ShipError(ErrorType.Api, message, status, details);
1125
1163
  }
1126
1164
 
1165
+ /**
1166
+ * The platform is closed for maintenance (503).
1167
+ *
1168
+ * `message` is REQUIRED and has no default here. The API is the only
1169
+ * producer of that sentence, and a default in this file would be a second
1170
+ * owner of one fact — see CLAUDE.md, "The Constellation Law" (stopping
1171
+ * rule). It is also the one factory whose status is fixed rather than
1172
+ * defaulted: a maintenance refusal is 503 or it is not this error.
1173
+ */
1174
+ static maintenance(message: string, details?: unknown): ShipError {
1175
+ return new ShipError(ErrorType.Maintenance, message, 503, details);
1176
+ }
1177
+
1127
1178
  // Semantic-category guards. For specific-type checks, use
1128
1179
  // `error.type === ErrorType.X` directly or the generic `isType(t)`.
1129
1180
 
@@ -1179,7 +1230,7 @@ export function isShipError(error: unknown): error is ShipError {
1179
1230
  }
1180
1231
 
1181
1232
  // =============================================================================
1182
- // CONFIG TYPES
1233
+ // PLATFORM LIMITS
1183
1234
  // =============================================================================
1184
1235
 
1185
1236
  /**
@@ -2007,29 +2058,6 @@ export interface DeploymentUploadOptions {
2007
2058
  idempotencyKey?: string;
2008
2059
  }
2009
2060
 
2010
- /**
2011
- * Pagination options for every list endpoint. The response's `cursor` feeds
2012
- * the next request; a `null` cursor means the last page. Omitting both
2013
- * returns the server's default first page.
2014
- *
2015
- * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
2016
- * carries the entire has-more signal, so no redundant boolean, and no
2017
- * `total`. **A count is an aggregate over a collection, not a property of a
2018
- * page:** including one makes every read pay for a full scan it did not ask
2019
- * for, which is precisely the cost keyset pagination exists to avoid.
2020
- *
2021
- * Counts therefore live on the summary resource that owns them —
2022
- * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
2023
- * platform-wide ones. Ask for a count when you want a count; ask for a page
2024
- * when you want a page.
2025
- */
2026
- export interface ListOptions {
2027
- /** Maximum number of items to return in one page. */
2028
- limit?: number;
2029
- /** Opaque cursor from the previous page's response. */
2030
- cursor?: string;
2031
- }
2032
-
2033
2061
  /**
2034
2062
  * What a caller may change on an existing deployment.
2035
2063
  *