@shipstatic/types 2.5.0-beta.10 → 2.5.0-beta.12

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 CHANGED
@@ -20,14 +20,24 @@ npm install @shipstatic/types
20
20
 
21
21
  ```typescript
22
22
  import type {
23
- Deployment, DeploymentListResponse,
24
- Domain, DomainSetResult, DomainListResponse, DnsRecord, DomainDnsResponse, DomainRecordsResponse, DomainValidateResponse,
25
- Token, TokenListItem, TokenListResponse, TokenCreateResponse,
26
- Account, AccountUsage, AccountOverrides,
23
+ ListResponse, ListOptions,
24
+ Deployment, DeploymentListResponse, DeploymentDeleteResponse, DeploymentSetOptions,
25
+ Domain, DomainSetResult, DomainSetOptions, DomainListResponse, DnsRecord, DnsLookup, DomainDnsResponse, DomainRecordsResponse, DomainShareResponse, DomainValidateResponse, DomainDeleteResponse, DomainVerifyResponse,
26
+ Token, TokenListResponse, TokenCreateResponse, TokenCreateOptions, TokenDeleteResponse,
27
+ Account, AccountUsage, AccountOverrides, AccountDeleteResponse, AccountKeyResponse,
28
+ LabelsResponse, SetupInstructionsResponse,
27
29
  StaticFile
28
30
  } from '@shipstatic/types';
31
+
32
+ // Every public path, declared once — the API mounts from it, clients request against it.
33
+ import { API_PATHS } from '@shipstatic/types';
29
34
  ```
30
35
 
36
+ A mutation answers with the resource it affected — the entity when it
37
+ survives, otherwise the `*DeleteResponse` shape: the resource noun carrying
38
+ the canonical key, plus the resource's own state field where the resource is
39
+ mid-transition. No `message`, no `success`, no constant flags.
40
+
31
41
  ### Error System
32
42
 
33
43
  ```typescript
@@ -82,7 +92,7 @@ import {
82
92
  DomainStatus, // pending | partial | success | paused
83
93
  AccountPlan, // free | standard | sponsored | enterprise | suspended | terminating | terminated
84
94
  FileValidationStatus, // pending | processing_error | excluded | validation_failed | ready
85
- AuthMethod, // jwt | apiKey | token | webhook | system
95
+ AuthMethod, // session | apiKey | token | agent | oauth | webhook | system
86
96
  } from '@shipstatic/types';
87
97
  ```
88
98
 
@@ -132,7 +142,6 @@ import type {
132
142
  FileValidationResult,
133
143
  ValidationIssue,
134
144
  UploadedFile,
135
- ProgressInfo,
136
145
  } from '@shipstatic/types';
137
146
  ```
138
147
 
package/dist/index.d.ts CHANGED
@@ -49,6 +49,49 @@ export interface DeploymentCreateResponse extends Deployment {
49
49
  /** Claim URL for public deployments. Present when deployed without credentials. */
50
50
  readonly claim?: string;
51
51
  }
52
+ /**
53
+ * Every path the public API answers on, declared once.
54
+ *
55
+ * The URL surface used to be written out in four places — the API's mounts,
56
+ * the SDK's client, the dashboard's client, and the post-deploy smoke — so a
57
+ * rename meant finding all four. Here it is one table that the producer
58
+ * mounts from and the consumers request against, which is the only way a
59
+ * path and its handler cannot drift apart.
60
+ *
61
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
62
+ * to `web/my`, for the same reason its row types do: this package is
63
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
64
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
65
+ * promise to one dashboard.
66
+ *
67
+ * Item paths are functions rather than templates so the key is interpolated
68
+ * in one place, encoded the same way by every caller.
69
+ */
70
+ export declare const API_PATHS: {
71
+ readonly DEPLOYMENTS: "/deployments";
72
+ readonly DEPLOYMENT: (deployment: string) => string;
73
+ readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
74
+ readonly DOMAINS: "/domains";
75
+ readonly DOMAIN: (domain: string) => string;
76
+ readonly DOMAIN_VERIFY: (domain: string) => string;
77
+ readonly DOMAIN_DNS: (domain: string) => string;
78
+ readonly DOMAIN_RECORDS: (domain: string) => string;
79
+ readonly DOMAIN_SHARE: (domain: string) => string;
80
+ readonly DOMAIN_PROPAGATION: (domain: string) => string;
81
+ readonly DOMAINS_VALIDATE: "/domains/validate";
82
+ readonly TOKENS: "/tokens";
83
+ readonly TOKEN: (token: string) => string;
84
+ readonly ACCOUNT: "/account";
85
+ readonly ACCOUNT_KEY: "/account/key";
86
+ readonly ACCOUNT_CLAIM: "/account/claim";
87
+ readonly ACTIVITIES: "/activities";
88
+ readonly LABELS: "/labels";
89
+ readonly LIMITS: "/limits";
90
+ readonly PING: "/ping";
91
+ readonly SETUP: "/setup";
92
+ readonly SPA_CHECK: "/spa-check";
93
+ readonly UPLOAD: "/upload";
94
+ };
52
95
  /**
53
96
  * The half of a list response that is identical on every list.
54
97
  *
@@ -74,6 +117,42 @@ export interface DeploymentListResponse extends ListResponse {
74
117
  /** Array of deployments */
75
118
  deployments: Deployment[];
76
119
  }
120
+ /**
121
+ * Acknowledgement of `DELETE /deployments/:deployment` — and the shape every
122
+ * mutation with no entity left to return follows.
123
+ *
124
+ * **The law:** a mutation answers with the resource it affected. If the
125
+ * resource still exists, that means the entity itself (`Deployment`,
126
+ * `Domain`, …). Otherwise it means this: the resource noun carrying the
127
+ * item's canonical key, plus the resource's own state field — and ONLY when
128
+ * the resource survived in a transitional state, as an async deletion's does.
129
+ * Where the resource is simply gone, the key alone is the whole answer
130
+ * ({@link DomainDeleteResponse}, {@link TokenDeleteResponse}).
131
+ *
132
+ * Put positively: **an acknowledgement is a projection of the resource** —
133
+ * its key, plus its own state field where the state changed. That is the
134
+ * test to apply, and it is sharper than "no constant", which this shape
135
+ * would fail on its own terms: `status` here is the literal `'deleting'` on
136
+ * every success, exactly as fixed as a `changed: true` would be.
137
+ *
138
+ * The difference is not how predictable the value is, it is what the field
139
+ * IS. `status` is the deployment's own field — the same one `GET
140
+ * /deployments/:deployment` returns — so this response is `Deployment`
141
+ * narrowed to two members, and a client renders it with the code it already
142
+ * has. `changed: true`, `queued: true` and `success: true` are not fields of
143
+ * any entity; they exist only to assert that the call worked, which the
144
+ * status code already said. Sync versus accepted is likewise the status
145
+ * code's job — 200 versus 202 — not a boolean's.
146
+ *
147
+ * No prose either (`message`): an acknowledgement is data, and each surface
148
+ * composes its own copy.
149
+ */
150
+ export interface DeploymentDeleteResponse {
151
+ /** The deployment hostname that was marked for removal */
152
+ readonly deployment: string;
153
+ /** The state the deployment is in while background cleanup runs */
154
+ readonly status: DeploymentStatusType;
155
+ }
77
156
  /**
78
157
  * Domain status constants
79
158
  *
@@ -131,6 +210,25 @@ export interface DomainListResponse extends ListResponse {
131
210
  /** Array of domains */
132
211
  domains: Domain[];
133
212
  }
213
+ /**
214
+ * Acknowledgement of `DELETE /domains/:domain`. The row is gone, so there is
215
+ * no state to state — the canonical domain name is the whole answer. See
216
+ * {@link DeploymentDeleteResponse} for the law.
217
+ */
218
+ export interface DomainDeleteResponse {
219
+ /** The domain name that was removed, normalized */
220
+ readonly domain: string;
221
+ }
222
+ /**
223
+ * Acknowledgement of `POST /domains/:domain/verify` (202). The DNS check is
224
+ * queued, not performed — the accepted status code says so, and the domain's
225
+ * own status is unchanged until the check runs, which is why none is stated
226
+ * here. See {@link DeploymentDeleteResponse} for the law.
227
+ */
228
+ export interface DomainVerifyResponse {
229
+ /** The domain whose DNS verification was queued, normalized */
230
+ readonly domain: string;
231
+ }
134
232
  /**
135
233
  * DNS record types supported for domain configuration
136
234
  */
@@ -156,13 +254,33 @@ export interface DnsProvider {
156
254
  /**
157
255
  * Response for domain DNS provider lookup
158
256
  */
257
+ /**
258
+ * What a DNS lookup found for a domain. An envelope rather than a bare
259
+ * {@link DnsProvider} because a lookup can succeed and learn more than the
260
+ * provider later; the shape is named so a consumer can hold one.
261
+ */
262
+ export interface DnsLookup {
263
+ /** The provider serving this domain's DNS, absent when unidentified */
264
+ provider?: DnsProvider;
265
+ }
159
266
  export interface DomainDnsResponse {
160
267
  /** The domain name */
161
268
  domain: string;
162
269
  /** DNS provider information, null if not yet looked up */
163
- dns: {
164
- provider?: DnsProvider;
165
- } | null;
270
+ dns: DnsLookup | null;
271
+ }
272
+ /**
273
+ * Response for `GET /domains/:domain/share` — the domain plus the salted
274
+ * hash that lets someone else complete its DNS setup without an account.
275
+ *
276
+ * `/admin/domains/:domain/share` answers the same shape, which is the admin
277
+ * law working: the operator surface is the public grammar with a prefix.
278
+ */
279
+ export interface DomainShareResponse {
280
+ /** The domain the setup link is for */
281
+ readonly domain: string;
282
+ /** The salted setup hash that authorizes the share */
283
+ readonly hash: string;
166
284
  }
167
285
  /**
168
286
  * Response for domain DNS records
@@ -175,6 +293,35 @@ export interface DomainRecordsResponse {
175
293
  /** Required DNS records for configuration */
176
294
  records: DnsRecord[];
177
295
  }
296
+ /**
297
+ * Response for `GET /labels` — every label in use across the caller's
298
+ * deployments, domains and tokens, grouped and ordered by last use.
299
+ *
300
+ * The one plural noun outside the list contract, deliberately: labels have
301
+ * no identity, no row and no `created`, so there is nothing for a keyset
302
+ * cursor to resume after, and its consumer is an autocomplete that wants the
303
+ * whole set. Bounded by `PAGINATION.GLOBAL_LIMIT` rather than paginated.
304
+ */
305
+ export interface LabelsResponse {
306
+ readonly labels: string[];
307
+ }
308
+ /**
309
+ * Response for `POST /setup` — the DNS instructions for one domain, written
310
+ * for a human to follow at their registrar.
311
+ *
312
+ * `custom` is the provider-specific walkthrough when the provider is known;
313
+ * `generic` always answers, so a caller never has nothing to show.
314
+ */
315
+ export interface SetupInstructionsResponse {
316
+ /** One-line summary of what to do */
317
+ readonly tldr: string;
318
+ /** Provider-specific instructions, null when the provider is unknown */
319
+ readonly custom: string | null;
320
+ /** Provider-agnostic instructions — always present */
321
+ readonly generic: string;
322
+ /** The identified DNS provider, null when unknown */
323
+ readonly provider: string | null;
324
+ }
178
325
  /**
179
326
  * Response for domain validation
180
327
  */
@@ -224,6 +371,15 @@ export interface TokenCreateResponse extends Token {
224
371
  /** The raw credential value (shown once at creation, then never again) */
225
372
  readonly secret: string;
226
373
  }
374
+ /**
375
+ * Acknowledgement of `DELETE /tokens/:token`. The credential is revoked and
376
+ * its row is gone, so the management identifier is the whole answer. See
377
+ * {@link DeploymentDeleteResponse} for the law.
378
+ */
379
+ export interface TokenDeleteResponse {
380
+ /** The 7-char management identifier that was revoked */
381
+ readonly token: string;
382
+ }
227
383
  /**
228
384
  * Account plan constants
229
385
  */
@@ -313,6 +469,32 @@ export interface AccountGetResponse extends Account {
313
469
  /** Present only during read-only admin impersonation: the operator's account id. */
314
470
  readonly impersonatedBy?: string;
315
471
  }
472
+ /**
473
+ * Acknowledgement of `DELETE /account` (202). Termination is asynchronous —
474
+ * a cleanup consumer finishes the job — so the account survives long enough
475
+ * to state the plan it is transitioning through. `plan` is the account's
476
+ * state field, the way `status` is a deployment's. See
477
+ * {@link DeploymentDeleteResponse} for the law.
478
+ */
479
+ export interface AccountDeleteResponse {
480
+ /** The account that was marked for termination */
481
+ readonly account: string;
482
+ /** The plan the account is in while cleanup runs */
483
+ readonly plan: AccountPlanType;
484
+ }
485
+ /**
486
+ * Response from `PUT /account/key` — the account's single API key, minted in
487
+ * place of whatever was there before.
488
+ *
489
+ * There is no entity to return: only the key's last-4 `hint` is durable
490
+ * (`Account.hint`), and the plaintext exists exactly once, in this response.
491
+ * The raw credential is `secret` on every surface that mints one — the same
492
+ * field `TokenCreateResponse` carries — because one concept gets one name.
493
+ */
494
+ export interface AccountKeyResponse {
495
+ /** The raw API key (shown once at mint, then never again) */
496
+ readonly secret: string;
497
+ }
316
498
  /**
317
499
  * Account-specific configuration overrides
318
500
  * Allows per-account customization of limits without changing plan
@@ -760,16 +942,22 @@ export interface SPACheckRequest {
760
942
  /**
761
943
  * Response from SPA check endpoint
762
944
  */
945
+ /**
946
+ * Which of the classifier's tiers reached the verdict, and why. Named rather
947
+ * than inline so the API's own `checkSPA` can return `SPACheckResponse`
948
+ * instead of restating its shape.
949
+ */
950
+ export interface SPACheckDebug {
951
+ /** Which tier made the detection */
952
+ tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
953
+ /** The reason for the detection result */
954
+ reason: string;
955
+ }
763
956
  export interface SPACheckResponse {
764
957
  /** Whether the project is detected as a Single Page Application */
765
958
  isSPA: boolean;
766
959
  /** Debugging information about detection */
767
- debug: {
768
- /** Which tier made the detection: 'exclusions', 'inclusions', 'scoring', 'ai', or 'fallback' */
769
- tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
770
- /** The reason for the detection result */
771
- reason: string;
772
- };
960
+ debug: SPACheckDebug;
773
961
  }
774
962
  /**
775
963
  * Represents a file that has been processed and is ready for deploy.
@@ -864,6 +1052,33 @@ export interface ListOptions {
864
1052
  /** Opaque cursor from the previous page's response. */
865
1053
  cursor?: string;
866
1054
  }
1055
+ /**
1056
+ * What a caller may change on an existing deployment.
1057
+ *
1058
+ * Labels and nothing else: a deployment's content is immutable by design, so
1059
+ * this is the whole mutable surface rather than a subset someone chose.
1060
+ */
1061
+ export interface DeploymentSetOptions {
1062
+ labels: string[];
1063
+ }
1064
+ /**
1065
+ * What `domains.set()` may create or change. Every field is optional because
1066
+ * the call is a natural-key upsert: omitting `deployment` reserves the
1067
+ * domain, naming one links or re-points it, and labels travel either way.
1068
+ *
1069
+ * `deployment` is deliberately not nullable — unlinking is refused (400).
1070
+ * See `npm/ship/CLAUDE.md`, "Domain Write Semantics".
1071
+ */
1072
+ export interface DomainSetOptions {
1073
+ deployment?: string;
1074
+ labels?: string[];
1075
+ }
1076
+ /** What a caller may set when minting a deploy token. */
1077
+ export interface TokenCreateOptions {
1078
+ /** Seconds until expiry; omit for a token that never expires. */
1079
+ ttl?: number;
1080
+ labels?: string[];
1081
+ }
867
1082
  /**
868
1083
  * Deployment resource interface - the contract all implementations must follow.
869
1084
  *
@@ -876,32 +1091,22 @@ export interface DeploymentResource<UploadOptions extends DeploymentUploadOption
876
1091
  upload: (input: DeployInput, options?: UploadOptions) => Promise<DeploymentCreateResponse>;
877
1092
  list: (options?: ListOptions) => Promise<DeploymentListResponse>;
878
1093
  get: (id: string) => Promise<Deployment>;
879
- set: (id: string, options: {
880
- labels: string[];
881
- }) => Promise<Deployment>;
882
- remove: (id: string) => Promise<void>;
1094
+ set: (id: string, options: DeploymentSetOptions) => Promise<Deployment>;
1095
+ remove: (id: string) => Promise<DeploymentDeleteResponse>;
883
1096
  }
884
1097
  /**
885
1098
  * Domain resource interface - the contract all implementations must follow
886
1099
  */
887
1100
  export interface DomainResource {
888
- set: (name: string, options?: {
889
- deployment?: string;
890
- labels?: string[];
891
- }) => Promise<DomainSetResult>;
1101
+ set: (name: string, options?: DomainSetOptions) => Promise<DomainSetResult>;
892
1102
  list: (options?: ListOptions) => Promise<DomainListResponse>;
893
1103
  get: (name: string) => Promise<Domain>;
894
- remove: (name: string) => Promise<void>;
895
- verify: (name: string) => Promise<{
896
- message: string;
897
- }>;
1104
+ remove: (name: string) => Promise<DomainDeleteResponse>;
1105
+ verify: (name: string) => Promise<DomainVerifyResponse>;
898
1106
  validate: (name: string) => Promise<DomainValidateResponse>;
899
1107
  dns: (name: string) => Promise<DomainDnsResponse>;
900
1108
  records: (name: string) => Promise<DomainRecordsResponse>;
901
- share: (name: string) => Promise<{
902
- domain: string;
903
- hash: string;
904
- }>;
1109
+ share: (name: string) => Promise<DomainShareResponse>;
905
1110
  }
906
1111
  /**
907
1112
  * Account resource interface - the contract all implementations must follow
@@ -913,12 +1118,10 @@ export interface AccountResource {
913
1118
  * Token resource interface - the contract all implementations must follow
914
1119
  */
915
1120
  export interface TokenResource {
916
- create: (options?: {
917
- ttl?: number;
918
- labels?: string[];
919
- }) => Promise<TokenCreateResponse>;
1121
+ create: (options?: TokenCreateOptions) => Promise<TokenCreateResponse>;
920
1122
  list: (options?: ListOptions) => Promise<TokenListResponse>;
921
- remove: (token: string) => Promise<void>;
1123
+ get: (token: string) => Promise<Token>;
1124
+ remove: (token: string) => Promise<TokenDeleteResponse>;
922
1125
  }
923
1126
  /**
924
1127
  * Billing status response from GET /billing/status
package/dist/index.js CHANGED
@@ -14,6 +14,49 @@ export const DeploymentStatus = {
14
14
  FAILED: 'failed',
15
15
  DELETING: 'deleting',
16
16
  };
17
+ /**
18
+ * Every path the public API answers on, declared once.
19
+ *
20
+ * The URL surface used to be written out in four places — the API's mounts,
21
+ * the SDK's client, the dashboard's client, and the post-deploy smoke — so a
22
+ * rename meant finding all four. Here it is one table that the producer
23
+ * mounts from and the consumers request against, which is the only way a
24
+ * path and its handler cannot drift apart.
25
+ *
26
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
27
+ * to `web/my`, for the same reason its row types do: this package is
28
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
29
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
30
+ * promise to one dashboard.
31
+ *
32
+ * Item paths are functions rather than templates so the key is interpolated
33
+ * in one place, encoded the same way by every caller.
34
+ */
35
+ export const API_PATHS = {
36
+ DEPLOYMENTS: '/deployments',
37
+ DEPLOYMENT: (deployment) => `/deployments/${deployment}`,
38
+ DEPLOYMENT_CONFIG: (deployment) => `/deployments/${deployment}/config`,
39
+ DOMAINS: '/domains',
40
+ DOMAIN: (domain) => `/domains/${domain}`,
41
+ DOMAIN_VERIFY: (domain) => `/domains/${domain}/verify`,
42
+ DOMAIN_DNS: (domain) => `/domains/${domain}/dns`,
43
+ DOMAIN_RECORDS: (domain) => `/domains/${domain}/records`,
44
+ DOMAIN_SHARE: (domain) => `/domains/${domain}/share`,
45
+ DOMAIN_PROPAGATION: (domain) => `/domains/${domain}/propagation`,
46
+ DOMAINS_VALIDATE: '/domains/validate',
47
+ TOKENS: '/tokens',
48
+ TOKEN: (token) => `/tokens/${token}`,
49
+ ACCOUNT: '/account',
50
+ ACCOUNT_KEY: '/account/key',
51
+ ACCOUNT_CLAIM: '/account/claim',
52
+ ACTIVITIES: '/activities',
53
+ LABELS: '/labels',
54
+ LIMITS: '/limits',
55
+ PING: '/ping',
56
+ SETUP: '/setup',
57
+ SPA_CHECK: '/spa-check',
58
+ UPLOAD: '/upload',
59
+ };
17
60
  // =============================================================================
18
61
  // DOMAIN TYPES
19
62
  // =============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.5.0-beta.10",
3
+ "version": "2.5.0-beta.12",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -58,6 +58,50 @@ export interface DeploymentCreateResponse extends Deployment {
58
58
  readonly claim?: string;
59
59
  }
60
60
 
61
+ /**
62
+ * Every path the public API answers on, declared once.
63
+ *
64
+ * The URL surface used to be written out in four places — the API's mounts,
65
+ * the SDK's client, the dashboard's client, and the post-deploy smoke — so a
66
+ * rename meant finding all four. Here it is one table that the producer
67
+ * mounts from and the consumers request against, which is the only way a
68
+ * path and its handler cannot drift apart.
69
+ *
70
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
71
+ * to `web/my`, for the same reason its row types do: this package is
72
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
73
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
74
+ * promise to one dashboard.
75
+ *
76
+ * Item paths are functions rather than templates so the key is interpolated
77
+ * in one place, encoded the same way by every caller.
78
+ */
79
+ export const API_PATHS = {
80
+ DEPLOYMENTS: '/deployments',
81
+ DEPLOYMENT: (deployment: string) => `/deployments/${deployment}`,
82
+ DEPLOYMENT_CONFIG: (deployment: string) => `/deployments/${deployment}/config`,
83
+ DOMAINS: '/domains',
84
+ DOMAIN: (domain: string) => `/domains/${domain}`,
85
+ DOMAIN_VERIFY: (domain: string) => `/domains/${domain}/verify`,
86
+ DOMAIN_DNS: (domain: string) => `/domains/${domain}/dns`,
87
+ DOMAIN_RECORDS: (domain: string) => `/domains/${domain}/records`,
88
+ DOMAIN_SHARE: (domain: string) => `/domains/${domain}/share`,
89
+ DOMAIN_PROPAGATION: (domain: string) => `/domains/${domain}/propagation`,
90
+ DOMAINS_VALIDATE: '/domains/validate',
91
+ TOKENS: '/tokens',
92
+ TOKEN: (token: string) => `/tokens/${token}`,
93
+ ACCOUNT: '/account',
94
+ ACCOUNT_KEY: '/account/key',
95
+ ACCOUNT_CLAIM: '/account/claim',
96
+ ACTIVITIES: '/activities',
97
+ LABELS: '/labels',
98
+ LIMITS: '/limits',
99
+ PING: '/ping',
100
+ SETUP: '/setup',
101
+ SPA_CHECK: '/spa-check',
102
+ UPLOAD: '/upload',
103
+ } as const;
104
+
61
105
  /**
62
106
  * The half of a list response that is identical on every list.
63
107
  *
@@ -85,6 +129,43 @@ export interface DeploymentListResponse extends ListResponse {
85
129
  deployments: Deployment[];
86
130
  }
87
131
 
132
+ /**
133
+ * Acknowledgement of `DELETE /deployments/:deployment` — and the shape every
134
+ * mutation with no entity left to return follows.
135
+ *
136
+ * **The law:** a mutation answers with the resource it affected. If the
137
+ * resource still exists, that means the entity itself (`Deployment`,
138
+ * `Domain`, …). Otherwise it means this: the resource noun carrying the
139
+ * item's canonical key, plus the resource's own state field — and ONLY when
140
+ * the resource survived in a transitional state, as an async deletion's does.
141
+ * Where the resource is simply gone, the key alone is the whole answer
142
+ * ({@link DomainDeleteResponse}, {@link TokenDeleteResponse}).
143
+ *
144
+ * Put positively: **an acknowledgement is a projection of the resource** —
145
+ * its key, plus its own state field where the state changed. That is the
146
+ * test to apply, and it is sharper than "no constant", which this shape
147
+ * would fail on its own terms: `status` here is the literal `'deleting'` on
148
+ * every success, exactly as fixed as a `changed: true` would be.
149
+ *
150
+ * The difference is not how predictable the value is, it is what the field
151
+ * IS. `status` is the deployment's own field — the same one `GET
152
+ * /deployments/:deployment` returns — so this response is `Deployment`
153
+ * narrowed to two members, and a client renders it with the code it already
154
+ * has. `changed: true`, `queued: true` and `success: true` are not fields of
155
+ * any entity; they exist only to assert that the call worked, which the
156
+ * status code already said. Sync versus accepted is likewise the status
157
+ * code's job — 200 versus 202 — not a boolean's.
158
+ *
159
+ * No prose either (`message`): an acknowledgement is data, and each surface
160
+ * composes its own copy.
161
+ */
162
+ export interface DeploymentDeleteResponse {
163
+ /** The deployment hostname that was marked for removal */
164
+ readonly deployment: string;
165
+ /** The state the deployment is in while background cleanup runs */
166
+ readonly status: DeploymentStatusType;
167
+ }
168
+
88
169
  // =============================================================================
89
170
  // DOMAIN TYPES
90
171
  // =============================================================================
@@ -151,6 +232,27 @@ export interface DomainListResponse extends ListResponse {
151
232
  domains: Domain[];
152
233
  }
153
234
 
235
+ /**
236
+ * Acknowledgement of `DELETE /domains/:domain`. The row is gone, so there is
237
+ * no state to state — the canonical domain name is the whole answer. See
238
+ * {@link DeploymentDeleteResponse} for the law.
239
+ */
240
+ export interface DomainDeleteResponse {
241
+ /** The domain name that was removed, normalized */
242
+ readonly domain: string;
243
+ }
244
+
245
+ /**
246
+ * Acknowledgement of `POST /domains/:domain/verify` (202). The DNS check is
247
+ * queued, not performed — the accepted status code says so, and the domain's
248
+ * own status is unchanged until the check runs, which is why none is stated
249
+ * here. See {@link DeploymentDeleteResponse} for the law.
250
+ */
251
+ export interface DomainVerifyResponse {
252
+ /** The domain whose DNS verification was queued, normalized */
253
+ readonly domain: string;
254
+ }
255
+
154
256
  /**
155
257
  * DNS record types supported for domain configuration
156
258
  */
@@ -179,11 +281,35 @@ export interface DnsProvider {
179
281
  /**
180
282
  * Response for domain DNS provider lookup
181
283
  */
284
+ /**
285
+ * What a DNS lookup found for a domain. An envelope rather than a bare
286
+ * {@link DnsProvider} because a lookup can succeed and learn more than the
287
+ * provider later; the shape is named so a consumer can hold one.
288
+ */
289
+ export interface DnsLookup {
290
+ /** The provider serving this domain's DNS, absent when unidentified */
291
+ provider?: DnsProvider;
292
+ }
293
+
182
294
  export interface DomainDnsResponse {
183
295
  /** The domain name */
184
296
  domain: string;
185
297
  /** DNS provider information, null if not yet looked up */
186
- dns: { provider?: DnsProvider } | null;
298
+ dns: DnsLookup | null;
299
+ }
300
+
301
+ /**
302
+ * Response for `GET /domains/:domain/share` — the domain plus the salted
303
+ * hash that lets someone else complete its DNS setup without an account.
304
+ *
305
+ * `/admin/domains/:domain/share` answers the same shape, which is the admin
306
+ * law working: the operator surface is the public grammar with a prefix.
307
+ */
308
+ export interface DomainShareResponse {
309
+ /** The domain the setup link is for */
310
+ readonly domain: string;
311
+ /** The salted setup hash that authorizes the share */
312
+ readonly hash: string;
187
313
  }
188
314
 
189
315
  /**
@@ -198,6 +324,37 @@ export interface DomainRecordsResponse {
198
324
  records: DnsRecord[];
199
325
  }
200
326
 
327
+ /**
328
+ * Response for `GET /labels` — every label in use across the caller's
329
+ * deployments, domains and tokens, grouped and ordered by last use.
330
+ *
331
+ * The one plural noun outside the list contract, deliberately: labels have
332
+ * no identity, no row and no `created`, so there is nothing for a keyset
333
+ * cursor to resume after, and its consumer is an autocomplete that wants the
334
+ * whole set. Bounded by `PAGINATION.GLOBAL_LIMIT` rather than paginated.
335
+ */
336
+ export interface LabelsResponse {
337
+ readonly labels: string[];
338
+ }
339
+
340
+ /**
341
+ * Response for `POST /setup` — the DNS instructions for one domain, written
342
+ * for a human to follow at their registrar.
343
+ *
344
+ * `custom` is the provider-specific walkthrough when the provider is known;
345
+ * `generic` always answers, so a caller never has nothing to show.
346
+ */
347
+ export interface SetupInstructionsResponse {
348
+ /** One-line summary of what to do */
349
+ readonly tldr: string;
350
+ /** Provider-specific instructions, null when the provider is unknown */
351
+ readonly custom: string | null;
352
+ /** Provider-agnostic instructions — always present */
353
+ readonly generic: string;
354
+ /** The identified DNS provider, null when unknown */
355
+ readonly provider: string | null;
356
+ }
357
+
201
358
  /**
202
359
  * Response for domain validation
203
360
  */
@@ -255,6 +412,16 @@ export interface TokenCreateResponse extends Token {
255
412
  readonly secret: string;
256
413
  }
257
414
 
415
+ /**
416
+ * Acknowledgement of `DELETE /tokens/:token`. The credential is revoked and
417
+ * its row is gone, so the management identifier is the whole answer. See
418
+ * {@link DeploymentDeleteResponse} for the law.
419
+ */
420
+ export interface TokenDeleteResponse {
421
+ /** The 7-char management identifier that was revoked */
422
+ readonly token: string;
423
+ }
424
+
258
425
  // =============================================================================
259
426
  // ACCOUNT TYPES
260
427
  // =============================================================================
@@ -353,6 +520,34 @@ export interface AccountGetResponse extends Account {
353
520
  readonly impersonatedBy?: string;
354
521
  }
355
522
 
523
+ /**
524
+ * Acknowledgement of `DELETE /account` (202). Termination is asynchronous —
525
+ * a cleanup consumer finishes the job — so the account survives long enough
526
+ * to state the plan it is transitioning through. `plan` is the account's
527
+ * state field, the way `status` is a deployment's. See
528
+ * {@link DeploymentDeleteResponse} for the law.
529
+ */
530
+ export interface AccountDeleteResponse {
531
+ /** The account that was marked for termination */
532
+ readonly account: string;
533
+ /** The plan the account is in while cleanup runs */
534
+ readonly plan: AccountPlanType;
535
+ }
536
+
537
+ /**
538
+ * Response from `PUT /account/key` — the account's single API key, minted in
539
+ * place of whatever was there before.
540
+ *
541
+ * There is no entity to return: only the key's last-4 `hint` is durable
542
+ * (`Account.hint`), and the plaintext exists exactly once, in this response.
543
+ * The raw credential is `secret` on every surface that mints one — the same
544
+ * field `TokenCreateResponse` carries — because one concept gets one name.
545
+ */
546
+ export interface AccountKeyResponse {
547
+ /** The raw API key (shown once at mint, then never again) */
548
+ readonly secret: string;
549
+ }
550
+
356
551
  /**
357
552
  * Account-specific configuration overrides
358
553
  * Allows per-account customization of limits without changing plan
@@ -1235,16 +1430,23 @@ export interface SPACheckRequest {
1235
1430
  /**
1236
1431
  * Response from SPA check endpoint
1237
1432
  */
1433
+ /**
1434
+ * Which of the classifier's tiers reached the verdict, and why. Named rather
1435
+ * than inline so the API's own `checkSPA` can return `SPACheckResponse`
1436
+ * instead of restating its shape.
1437
+ */
1438
+ export interface SPACheckDebug {
1439
+ /** Which tier made the detection */
1440
+ tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
1441
+ /** The reason for the detection result */
1442
+ reason: string;
1443
+ }
1444
+
1238
1445
  export interface SPACheckResponse {
1239
1446
  /** Whether the project is detected as a Single Page Application */
1240
1447
  isSPA: boolean;
1241
1448
  /** Debugging information about detection */
1242
- debug: {
1243
- /** Which tier made the detection: 'exclusions', 'inclusions', 'scoring', 'ai', or 'fallback' */
1244
- tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
1245
- /** The reason for the detection result */
1246
- reason: string;
1247
- };
1449
+ debug: SPACheckDebug;
1248
1450
  }
1249
1451
 
1250
1452
  // =============================================================================
@@ -1357,6 +1559,36 @@ export interface ListOptions {
1357
1559
  cursor?: string;
1358
1560
  }
1359
1561
 
1562
+ /**
1563
+ * What a caller may change on an existing deployment.
1564
+ *
1565
+ * Labels and nothing else: a deployment's content is immutable by design, so
1566
+ * this is the whole mutable surface rather than a subset someone chose.
1567
+ */
1568
+ export interface DeploymentSetOptions {
1569
+ labels: string[];
1570
+ }
1571
+
1572
+ /**
1573
+ * What `domains.set()` may create or change. Every field is optional because
1574
+ * the call is a natural-key upsert: omitting `deployment` reserves the
1575
+ * domain, naming one links or re-points it, and labels travel either way.
1576
+ *
1577
+ * `deployment` is deliberately not nullable — unlinking is refused (400).
1578
+ * See `npm/ship/CLAUDE.md`, "Domain Write Semantics".
1579
+ */
1580
+ export interface DomainSetOptions {
1581
+ deployment?: string;
1582
+ labels?: string[];
1583
+ }
1584
+
1585
+ /** What a caller may set when minting a deploy token. */
1586
+ export interface TokenCreateOptions {
1587
+ /** Seconds until expiry; omit for a token that never expires. */
1588
+ ttl?: number;
1589
+ labels?: string[];
1590
+ }
1591
+
1360
1592
  /**
1361
1593
  * Deployment resource interface - the contract all implementations must follow.
1362
1594
  *
@@ -1371,26 +1603,23 @@ export interface DeploymentResource<
1371
1603
  upload: (input: DeployInput, options?: UploadOptions) => Promise<DeploymentCreateResponse>;
1372
1604
  list: (options?: ListOptions) => Promise<DeploymentListResponse>;
1373
1605
  get: (id: string) => Promise<Deployment>;
1374
- set: (id: string, options: { labels: string[] }) => Promise<Deployment>;
1375
- remove: (id: string) => Promise<void>;
1606
+ set: (id: string, options: DeploymentSetOptions) => Promise<Deployment>;
1607
+ remove: (id: string) => Promise<DeploymentDeleteResponse>;
1376
1608
  }
1377
1609
 
1378
1610
  /**
1379
1611
  * Domain resource interface - the contract all implementations must follow
1380
1612
  */
1381
1613
  export interface DomainResource {
1382
- set: (
1383
- name: string,
1384
- options?: { deployment?: string; labels?: string[] },
1385
- ) => Promise<DomainSetResult>;
1614
+ set: (name: string, options?: DomainSetOptions) => Promise<DomainSetResult>;
1386
1615
  list: (options?: ListOptions) => Promise<DomainListResponse>;
1387
1616
  get: (name: string) => Promise<Domain>;
1388
- remove: (name: string) => Promise<void>;
1389
- verify: (name: string) => Promise<{ message: string }>;
1617
+ remove: (name: string) => Promise<DomainDeleteResponse>;
1618
+ verify: (name: string) => Promise<DomainVerifyResponse>;
1390
1619
  validate: (name: string) => Promise<DomainValidateResponse>;
1391
1620
  dns: (name: string) => Promise<DomainDnsResponse>;
1392
1621
  records: (name: string) => Promise<DomainRecordsResponse>;
1393
- share: (name: string) => Promise<{ domain: string; hash: string }>;
1622
+ share: (name: string) => Promise<DomainShareResponse>;
1394
1623
  }
1395
1624
 
1396
1625
  /**
@@ -1404,9 +1633,10 @@ export interface AccountResource {
1404
1633
  * Token resource interface - the contract all implementations must follow
1405
1634
  */
1406
1635
  export interface TokenResource {
1407
- create: (options?: { ttl?: number; labels?: string[] }) => Promise<TokenCreateResponse>;
1636
+ create: (options?: TokenCreateOptions) => Promise<TokenCreateResponse>;
1408
1637
  list: (options?: ListOptions) => Promise<TokenListResponse>;
1409
- remove: (token: string) => Promise<void>;
1638
+ get: (token: string) => Promise<Token>;
1639
+ remove: (token: string) => Promise<TokenDeleteResponse>;
1410
1640
  }
1411
1641
 
1412
1642
  // =============================================================================