@shipstatic/types 2.24.0-beta.2 → 2.25.0-beta.1

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.
@@ -0,0 +1,234 @@
1
+ /**
2
+ * The wire schemas: what the API's responses look like, as data a consumer
3
+ * can hand to a validator or publish as a tool's `outputSchema`.
4
+ *
5
+ * `index.ts` owns the TYPES. Each interface there carries the platform's
6
+ * reasoning in JSDoc, field by field, and the `readonly`/mutable split a
7
+ * type can express and a schema cannot. This module RESTATES those shapes
8
+ * as zod schemas, and the restatement is fenced rather than trusted:
9
+ * `tests/schemas.test.ts` holds every schema to its interface at compile
10
+ * time (the same keys, and the interface assignable to what the schema
11
+ * parses), so a field added to one and not the other fails `pnpm typecheck`
12
+ * of this package, which every release runs. Two declarations in one
13
+ * package, one commit apart, one fence between them.
14
+ *
15
+ * Why the schemas are not the owner: `z.infer` cannot carry per-field JSDoc
16
+ * into an editor, and it makes every field mutable or every field readonly,
17
+ * where the entities deliberately mix the two. The interface is the better
18
+ * declaration for a developer; the schema is the better one for a wire.
19
+ *
20
+ * **What a schema promises is what the wire promises ACROSS versions**, and
21
+ * that decides where it is looser than the type. The platform grows its own
22
+ * vocabularies: `via` gained three members in one month, `status` gained
23
+ * `deleting`, `plan` gained `team`. A consumer validating a response holds
24
+ * the schema it shipped with, so an enum there would turn the platform's
25
+ * next word into a validation failure on every older client. Those fields
26
+ * are therefore strings whose DESCRIPTION names today's members (derived
27
+ * from the constant, never typed out), while vocabularies that are not ours
28
+ * to grow (a DNS record type, Stripe's billing interval) are enums. The
29
+ * fence admits this on purpose: the interface must be assignable to the
30
+ * schema's output, not equal to it.
31
+ *
32
+ * Every field is described, and that is fenced too: these descriptions are
33
+ * what an agent reads when a tool publishes the schema, and an undescribed
34
+ * field is a field an agent cannot use. The sentences are the interfaces'
35
+ * own, shortened to what a reader of a result needs.
36
+ *
37
+ * A subpath export (`@shipstatic/types/schemas`), so a consumer that never
38
+ * imports it carries no zod: the browser bundles read `index.ts` alone.
39
+ */
40
+ import { z } from 'zod';
41
+ /** The half of every list response that is identical on every list. */
42
+ export declare const ListResponseSchema: z.ZodObject<{
43
+ cursor: z.ZodNullable<z.ZodString>;
44
+ }, z.core.$strip>;
45
+ export declare const DeploymentSchema: z.ZodObject<{
46
+ deployment: z.ZodString;
47
+ url: z.ZodURL;
48
+ files: z.ZodInt;
49
+ size: z.ZodInt;
50
+ status: z.ZodString;
51
+ config: z.ZodBoolean;
52
+ password: z.ZodBoolean;
53
+ labels: z.ZodArray<z.ZodString>;
54
+ via: z.ZodNullable<z.ZodString>;
55
+ created: z.ZodInt;
56
+ expires: z.ZodNullable<z.ZodInt>;
57
+ screenshot: z.ZodURL;
58
+ }, z.core.$strip>;
59
+ export declare const DeploymentCreateResponseSchema: z.ZodObject<{
60
+ deployment: z.ZodString;
61
+ url: z.ZodURL;
62
+ files: z.ZodInt;
63
+ size: z.ZodInt;
64
+ status: z.ZodString;
65
+ config: z.ZodBoolean;
66
+ password: z.ZodBoolean;
67
+ labels: z.ZodArray<z.ZodString>;
68
+ via: z.ZodNullable<z.ZodString>;
69
+ created: z.ZodInt;
70
+ expires: z.ZodNullable<z.ZodInt>;
71
+ screenshot: z.ZodURL;
72
+ claim: z.ZodOptional<z.ZodURL>;
73
+ }, z.core.$strip>;
74
+ export declare const DeploymentListResponseSchema: z.ZodObject<{
75
+ cursor: z.ZodNullable<z.ZodString>;
76
+ deployments: z.ZodArray<z.ZodObject<{
77
+ deployment: z.ZodString;
78
+ url: z.ZodURL;
79
+ files: z.ZodInt;
80
+ size: z.ZodInt;
81
+ status: z.ZodString;
82
+ config: z.ZodBoolean;
83
+ password: z.ZodBoolean;
84
+ labels: z.ZodArray<z.ZodString>;
85
+ via: z.ZodNullable<z.ZodString>;
86
+ created: z.ZodInt;
87
+ expires: z.ZodNullable<z.ZodInt>;
88
+ screenshot: z.ZodURL;
89
+ }, z.core.$strip>>;
90
+ }, z.core.$strip>;
91
+ export declare const DeploymentDeleteResponseSchema: z.ZodObject<{
92
+ deployment: z.ZodString;
93
+ status: z.ZodString;
94
+ }, z.core.$strip>;
95
+ export declare const DomainSchema: z.ZodObject<{
96
+ domain: z.ZodString;
97
+ url: z.ZodURL;
98
+ deployment: z.ZodNullable<z.ZodString>;
99
+ status: z.ZodString;
100
+ labels: z.ZodArray<z.ZodString>;
101
+ created: z.ZodInt;
102
+ linked: z.ZodNullable<z.ZodInt>;
103
+ links: z.ZodInt;
104
+ }, z.core.$strip>;
105
+ /** `Domain` plus the SDK's own create-versus-update flag; the wire body is a plain `Domain`. */
106
+ export declare const DomainSetResultSchema: z.ZodObject<{
107
+ domain: z.ZodString;
108
+ url: z.ZodURL;
109
+ deployment: z.ZodNullable<z.ZodString>;
110
+ status: z.ZodString;
111
+ labels: z.ZodArray<z.ZodString>;
112
+ created: z.ZodInt;
113
+ linked: z.ZodNullable<z.ZodInt>;
114
+ links: z.ZodInt;
115
+ isCreate: z.ZodBoolean;
116
+ }, z.core.$strip>;
117
+ export declare const DomainListResponseSchema: z.ZodObject<{
118
+ cursor: z.ZodNullable<z.ZodString>;
119
+ domains: z.ZodArray<z.ZodObject<{
120
+ domain: z.ZodString;
121
+ url: z.ZodURL;
122
+ deployment: z.ZodNullable<z.ZodString>;
123
+ status: z.ZodString;
124
+ labels: z.ZodArray<z.ZodString>;
125
+ created: z.ZodInt;
126
+ linked: z.ZodNullable<z.ZodInt>;
127
+ links: z.ZodInt;
128
+ }, z.core.$strip>>;
129
+ }, z.core.$strip>;
130
+ export declare const DomainDeleteResponseSchema: z.ZodObject<{
131
+ domain: z.ZodString;
132
+ }, z.core.$strip>;
133
+ export declare const DomainVerifyResponseSchema: z.ZodObject<{
134
+ domain: z.ZodString;
135
+ }, z.core.$strip>;
136
+ export declare const DnsRecordSchema: z.ZodObject<{
137
+ type: z.ZodEnum<{
138
+ A: "A";
139
+ CNAME: "CNAME";
140
+ }>;
141
+ name: z.ZodString;
142
+ value: z.ZodString;
143
+ }, z.core.$strip>;
144
+ export declare const DomainRecordsResponseSchema: z.ZodObject<{
145
+ domain: z.ZodString;
146
+ apex: z.ZodString;
147
+ records: z.ZodArray<z.ZodObject<{
148
+ type: z.ZodEnum<{
149
+ A: "A";
150
+ CNAME: "CNAME";
151
+ }>;
152
+ name: z.ZodString;
153
+ value: z.ZodString;
154
+ }, z.core.$strip>>;
155
+ }, z.core.$strip>;
156
+ export declare const DnsProviderSchema: z.ZodObject<{
157
+ name: z.ZodNullable<z.ZodString>;
158
+ url: z.ZodOptional<z.ZodNullable<z.ZodURL>>;
159
+ }, z.core.$strip>;
160
+ export declare const DnsLookupSchema: z.ZodObject<{
161
+ provider: z.ZodOptional<z.ZodObject<{
162
+ name: z.ZodNullable<z.ZodString>;
163
+ url: z.ZodOptional<z.ZodNullable<z.ZodURL>>;
164
+ }, z.core.$strip>>;
165
+ }, z.core.$strip>;
166
+ export declare const DomainDnsResponseSchema: z.ZodObject<{
167
+ domain: z.ZodString;
168
+ dns: z.ZodNullable<z.ZodObject<{
169
+ provider: z.ZodOptional<z.ZodObject<{
170
+ name: z.ZodNullable<z.ZodString>;
171
+ url: z.ZodOptional<z.ZodNullable<z.ZodURL>>;
172
+ }, z.core.$strip>>;
173
+ }, z.core.$strip>>;
174
+ }, z.core.$strip>;
175
+ export declare const DomainShareResponseSchema: z.ZodObject<{
176
+ domain: z.ZodString;
177
+ url: z.ZodURL;
178
+ }, z.core.$strip>;
179
+ export declare const DomainValidateResponseSchema: z.ZodObject<{
180
+ valid: z.ZodBoolean;
181
+ normalized: z.ZodNullable<z.ZodString>;
182
+ available: z.ZodNullable<z.ZodBoolean>;
183
+ reason: z.ZodNullable<z.ZodString>;
184
+ }, z.core.$strip>;
185
+ export declare const CapsSchema: z.ZodObject<{
186
+ deployments: z.ZodInt;
187
+ platformDomains: z.ZodInt;
188
+ customDomains: z.ZodInt;
189
+ }, z.core.$strip>;
190
+ export declare const ScheduledChangeSchema: z.ZodObject<{
191
+ plan: z.ZodString;
192
+ interval: z.ZodEnum<{
193
+ month: "month";
194
+ year: "year";
195
+ }>;
196
+ at: z.ZodInt;
197
+ }, z.core.$strip>;
198
+ export declare const AccountSchema: z.ZodObject<{
199
+ email: z.ZodString;
200
+ name: z.ZodNullable<z.ZodString>;
201
+ picture: z.ZodNullable<z.ZodURL>;
202
+ plan: z.ZodString;
203
+ suspended: z.ZodBoolean;
204
+ usage: z.ZodObject<{
205
+ deployments: z.ZodInt;
206
+ platformDomains: z.ZodInt;
207
+ customDomains: z.ZodInt;
208
+ }, z.core.$strip>;
209
+ caps: z.ZodObject<{
210
+ deployments: z.ZodInt;
211
+ platformDomains: z.ZodInt;
212
+ customDomains: z.ZodInt;
213
+ }, z.core.$strip>;
214
+ created: z.ZodInt;
215
+ activated: z.ZodNullable<z.ZodInt>;
216
+ hint: z.ZodNullable<z.ZodString>;
217
+ used: z.ZodOptional<z.ZodNullable<z.ZodInt>>;
218
+ pastDue: z.ZodBoolean;
219
+ billed: z.ZodBoolean;
220
+ upgrade: z.ZodNullable<z.ZodString>;
221
+ interval: z.ZodNullable<z.ZodEnum<{
222
+ month: "month";
223
+ year: "year";
224
+ }>>;
225
+ scheduled: z.ZodNullable<z.ZodObject<{
226
+ plan: z.ZodString;
227
+ interval: z.ZodEnum<{
228
+ month: "month";
229
+ year: "year";
230
+ }>;
231
+ at: z.ZodInt;
232
+ }, z.core.$strip>>;
233
+ cancelAt: z.ZodNullable<z.ZodInt>;
234
+ }, z.core.$strip>;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The wire schemas: what the API's responses look like, as data a consumer
3
+ * can hand to a validator or publish as a tool's `outputSchema`.
4
+ *
5
+ * `index.ts` owns the TYPES. Each interface there carries the platform's
6
+ * reasoning in JSDoc, field by field, and the `readonly`/mutable split a
7
+ * type can express and a schema cannot. This module RESTATES those shapes
8
+ * as zod schemas, and the restatement is fenced rather than trusted:
9
+ * `tests/schemas.test.ts` holds every schema to its interface at compile
10
+ * time (the same keys, and the interface assignable to what the schema
11
+ * parses), so a field added to one and not the other fails `pnpm typecheck`
12
+ * of this package, which every release runs. Two declarations in one
13
+ * package, one commit apart, one fence between them.
14
+ *
15
+ * Why the schemas are not the owner: `z.infer` cannot carry per-field JSDoc
16
+ * into an editor, and it makes every field mutable or every field readonly,
17
+ * where the entities deliberately mix the two. The interface is the better
18
+ * declaration for a developer; the schema is the better one for a wire.
19
+ *
20
+ * **What a schema promises is what the wire promises ACROSS versions**, and
21
+ * that decides where it is looser than the type. The platform grows its own
22
+ * vocabularies: `via` gained three members in one month, `status` gained
23
+ * `deleting`, `plan` gained `team`. A consumer validating a response holds
24
+ * the schema it shipped with, so an enum there would turn the platform's
25
+ * next word into a validation failure on every older client. Those fields
26
+ * are therefore strings whose DESCRIPTION names today's members (derived
27
+ * from the constant, never typed out), while vocabularies that are not ours
28
+ * to grow (a DNS record type, Stripe's billing interval) are enums. The
29
+ * fence admits this on purpose: the interface must be assignable to the
30
+ * schema's output, not equal to it.
31
+ *
32
+ * Every field is described, and that is fenced too: these descriptions are
33
+ * what an agent reads when a tool publishes the schema, and an undescribed
34
+ * field is a field an agent cannot use. The sentences are the interfaces'
35
+ * own, shortened to what a reader of a result needs.
36
+ *
37
+ * A subpath export (`@shipstatic/types/schemas`), so a consumer that never
38
+ * imports it carries no zod: the browser bundles read `index.ts` alone.
39
+ */
40
+ import { z } from 'zod';
41
+ import { AccountPlan, DeploymentStatus, DeploymentVia, DomainStatus, PUBLIC_DEPLOYMENT_TTL_SECONDS, } from './index.js';
42
+ /** A vocabulary the platform grows, described by today's members and open to tomorrow's. */
43
+ const grown = (members, what) => z.string().describe(`${what} One of: ${Object.values(members).join(', ')}.`);
44
+ const unixSeconds = (what) => z.int().describe(`Unix timestamp (seconds) ${what}.`);
45
+ // =============================================================================
46
+ // LISTS
47
+ // =============================================================================
48
+ /** The half of every list response that is identical on every list. */
49
+ export const ListResponseSchema = z.object({
50
+ cursor: z
51
+ .string()
52
+ .nullable()
53
+ .describe('Opaque cursor for the next page; null on the last page. The whole has-more signal.'),
54
+ });
55
+ // =============================================================================
56
+ // DEPLOYMENTS
57
+ // =============================================================================
58
+ export const DeploymentSchema = z.object({
59
+ deployment: z.string().describe('Deployment hostname, e.g. "happy-cat-abc1234.shipstatic.com".'),
60
+ url: z.url().describe('Full URL to the live deployment.'),
61
+ files: z.int().nonnegative().describe('Number of files in the deployment.'),
62
+ size: z.int().nonnegative().describe('Total deployment size in bytes.'),
63
+ status: grown(DeploymentStatus, 'Deployment lifecycle state; "success" means the site is live.'),
64
+ config: z.boolean().describe('True if the deployment includes a ship.json routing config.'),
65
+ password: z.boolean().describe('True if the deployment is password-protected.'),
66
+ labels: z.array(z.string()).describe('Labels attached to the deployment; empty when none.'),
67
+ via: z
68
+ .string()
69
+ .nullable()
70
+ .describe(`How the deployment was created, as shown in deployment history (today one of: ${Object.values(DeploymentVia).join(', ')}). Null on deployments older than the tag.`),
71
+ created: unixSeconds('when the deployment was created'),
72
+ expires: z
73
+ .int()
74
+ .nullable()
75
+ .describe(`Unix timestamp (seconds) when the deployment expires; null when permanent. Anonymous deployments expire ${PUBLIC_DEPLOYMENT_TTL_SECONDS / 86_400} days after creation unless claimed; an authenticated deployment carries one only when it requested a ttl.`),
76
+ screenshot: z
77
+ .url()
78
+ .describe('Full URL to the deployment screenshot. Captured asynchronously after deploy; the URL is returned immediately but the image may take a few seconds to become available.'),
79
+ });
80
+ export const DeploymentCreateResponseSchema = DeploymentSchema.extend({
81
+ claim: z
82
+ .url()
83
+ .optional()
84
+ .describe('One-time URL that claims this anonymous deployment to a free account, making it permanent. Only present on anonymous deploys; absent when the deploy was made with a connected account.'),
85
+ });
86
+ export const DeploymentListResponseSchema = ListResponseSchema.extend({
87
+ deployments: z.array(DeploymentSchema).describe('The deployments on this page.'),
88
+ });
89
+ export const DeploymentDeleteResponseSchema = z.object({
90
+ deployment: z.string().describe('The deployment hostname that was marked for removal.'),
91
+ status: grown(DeploymentStatus, 'The state the deployment is in while background cleanup runs.'),
92
+ });
93
+ // =============================================================================
94
+ // DOMAINS
95
+ // =============================================================================
96
+ export const DomainSchema = z.object({
97
+ domain: z.string().describe('The domain name, e.g. "www.example.com".'),
98
+ url: z.url().describe('Full URL to the domain.'),
99
+ deployment: z
100
+ .string()
101
+ .nullable()
102
+ .describe('The deployment hostname this domain points to; null when reserved but not yet linked.'),
103
+ status: grown(DomainStatus, 'DNS verification state; "success" means the domain serves the linked deployment.'),
104
+ labels: z.array(z.string()).describe('Labels attached to the domain; empty when none.'),
105
+ created: unixSeconds('when the domain was created'),
106
+ linked: z
107
+ .int()
108
+ .nullable()
109
+ .describe('Unix timestamp (seconds) when a deployment was last linked; null if never linked.'),
110
+ links: z
111
+ .int()
112
+ .nonnegative()
113
+ .describe('How many times a deployment has been linked to this domain.'),
114
+ });
115
+ /** `Domain` plus the SDK's own create-versus-update flag; the wire body is a plain `Domain`. */
116
+ export const DomainSetResultSchema = DomainSchema.extend({
117
+ isCreate: z
118
+ .boolean()
119
+ .describe('True when this call created the domain; false when it updated an existing one.'),
120
+ });
121
+ export const DomainListResponseSchema = ListResponseSchema.extend({
122
+ domains: z.array(DomainSchema).describe('The domains on this page.'),
123
+ });
124
+ export const DomainDeleteResponseSchema = z.object({
125
+ domain: z.string().describe('The domain name that was removed, normalized.'),
126
+ });
127
+ export const DomainVerifyResponseSchema = z.object({
128
+ domain: z
129
+ .string()
130
+ .describe('The domain whose DNS verification was queued, normalized. The check runs asynchronously; the domain status updates once DNS propagates.'),
131
+ });
132
+ export const DnsRecordSchema = z.object({
133
+ type: z
134
+ .enum(['A', 'CNAME'])
135
+ .describe('Record type: A for the apex redirect, CNAME for the hosted subdomain.'),
136
+ name: z.string().describe('The DNS name to configure.'),
137
+ value: z.string().describe('The value to set: an IP for A, a hostname for CNAME.'),
138
+ });
139
+ export const DomainRecordsResponseSchema = z.object({
140
+ domain: z.string().describe('The domain the records are for.'),
141
+ apex: z.string().describe('The apex (registered) domain where DNS records are managed.'),
142
+ records: z
143
+ .array(DnsRecordSchema)
144
+ .describe('The records to configure at the DNS provider, in the order to add them.'),
145
+ });
146
+ export const DnsProviderSchema = z.object({
147
+ name: z.string().nullable().describe('Provider name, e.g. "Cloudflare"; null if unknown.'),
148
+ url: z
149
+ .url()
150
+ .nullable()
151
+ .optional()
152
+ .describe("The provider's DNS dashboard, where the records get added; null or absent when unknown."),
153
+ });
154
+ export const DnsLookupSchema = z.object({
155
+ provider: DnsProviderSchema.optional().describe("The provider serving this domain's DNS; absent when unidentified."),
156
+ });
157
+ export const DomainDnsResponseSchema = z.object({
158
+ domain: z.string().describe('The domain name.'),
159
+ dns: DnsLookupSchema.nullable().describe("What the platform recorded about the domain's DNS provider when the domain was created; null if nothing was recorded."),
160
+ });
161
+ export const DomainShareResponseSchema = z.object({
162
+ domain: z.string().describe('The domain the setup link is for.'),
163
+ url: z
164
+ .url()
165
+ .describe('The shareable DNS setup URL; whoever opens it sees the records to configure, with no API key.'),
166
+ });
167
+ export const DomainValidateResponseSchema = z.object({
168
+ valid: z.boolean().describe('Whether the domain name is valid.'),
169
+ normalized: z.string().nullable().describe('The normalized domain name; null when invalid.'),
170
+ available: z.boolean().nullable().describe('Whether the domain is available; null when invalid.'),
171
+ reason: z.string().nullable().describe('Why the name is unusable, for display; null when valid.'),
172
+ });
173
+ // =============================================================================
174
+ // ACCOUNT
175
+ // =============================================================================
176
+ export const CapsSchema = z.object({
177
+ deployments: z.int().nonnegative().describe('Deployments, every row whatever its status.'),
178
+ platformDomains: z
179
+ .int()
180
+ .nonnegative()
181
+ .describe('Names chosen under the platform\'s own suffix, e.g. "my-app.shipstatic.com".'),
182
+ customDomains: z
183
+ .int()
184
+ .nonnegative()
185
+ .describe('Hostnames the customer owns, paused ones included.'),
186
+ });
187
+ export const ScheduledChangeSchema = z.object({
188
+ plan: grown(AccountPlan, 'The plan the account moves to.'),
189
+ interval: z.enum(['month', 'year']).describe('The billing interval the change applies with.'),
190
+ at: unixSeconds('when the change applies'),
191
+ });
192
+ export const AccountSchema = z.object({
193
+ email: z.string().describe('The account email address.'),
194
+ name: z.string().nullable().describe('Display name; null if not set.'),
195
+ picture: z.url().nullable().describe('Profile picture URL; null if not set.'),
196
+ plan: grown(AccountPlan, 'The tier the account stands at.'),
197
+ suspended: z
198
+ .boolean()
199
+ .describe('True while the operator has suspended the account: reads work, every write is refused.'),
200
+ usage: CapsSchema.describe('What the account currently holds.'),
201
+ caps: CapsSchema.describe('What the account is allowed to hold: the same three keys as usage, so the pair divides.'),
202
+ created: unixSeconds('when the account was created'),
203
+ activated: z
204
+ .int()
205
+ .nullable()
206
+ .describe('Unix timestamp (seconds) of the first deployment; null if not yet activated.'),
207
+ hint: z
208
+ .string()
209
+ .nullable()
210
+ .describe('Last 4 characters of the API key; null when no key exists.'),
211
+ used: z
212
+ .int()
213
+ .nullable()
214
+ .optional()
215
+ .describe("Unix timestamp (seconds) of the API key's last use; null or absent when never used."),
216
+ pastDue: z
217
+ .boolean()
218
+ .describe('True while the subscription is past due and the card is being retried; the plan is unchanged.'),
219
+ billed: z.boolean().describe('Whether a subscription bills this plan.'),
220
+ upgrade: z
221
+ .string()
222
+ .nullable()
223
+ .describe(`The next plan up the ladder, or null when there is none. One of: ${Object.values(AccountPlan).join(', ')}.`),
224
+ interval: z
225
+ .enum(['month', 'year'])
226
+ .nullable()
227
+ .describe("The live subscription's billing interval; null when nothing bills the account."),
228
+ scheduled: ScheduledChangeSchema.nullable().describe('The pending plan change; null when none.'),
229
+ cancelAt: z
230
+ .int()
231
+ .nullable()
232
+ .describe('Unix timestamp (seconds) when the subscription ends; null while it renews.'),
233
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.24.0-beta.2",
3
+ "version": "2.25.0-beta.1",
4
4
  "description": "Shared TypeScript types for the ShipStatic platform.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,17 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
+ },
13
+ "./schemas": {
14
+ "types": "./dist/schemas.d.ts",
15
+ "default": "./dist/schemas.js"
16
+ }
17
+ },
18
+ "typesVersions": {
19
+ "*": {
20
+ "schemas": [
21
+ "./dist/schemas.d.ts"
22
+ ]
12
23
  }
13
24
  },
14
25
  "scripts": {
@@ -51,6 +62,9 @@
51
62
  "engines": {
52
63
  "node": ">=20.19.0"
53
64
  },
65
+ "dependencies": {
66
+ "zod": "^4.4.3"
67
+ },
54
68
  "devDependencies": {
55
69
  "@arethetypeswrong/cli": "^0.18.2",
56
70
  "@biomejs/biome": "2.5.5",
package/src/schemas.ts ADDED
@@ -0,0 +1,300 @@
1
+ /**
2
+ * The wire schemas: what the API's responses look like, as data a consumer
3
+ * can hand to a validator or publish as a tool's `outputSchema`.
4
+ *
5
+ * `index.ts` owns the TYPES. Each interface there carries the platform's
6
+ * reasoning in JSDoc, field by field, and the `readonly`/mutable split a
7
+ * type can express and a schema cannot. This module RESTATES those shapes
8
+ * as zod schemas, and the restatement is fenced rather than trusted:
9
+ * `tests/schemas.test.ts` holds every schema to its interface at compile
10
+ * time (the same keys, and the interface assignable to what the schema
11
+ * parses), so a field added to one and not the other fails `pnpm typecheck`
12
+ * of this package, which every release runs. Two declarations in one
13
+ * package, one commit apart, one fence between them.
14
+ *
15
+ * Why the schemas are not the owner: `z.infer` cannot carry per-field JSDoc
16
+ * into an editor, and it makes every field mutable or every field readonly,
17
+ * where the entities deliberately mix the two. The interface is the better
18
+ * declaration for a developer; the schema is the better one for a wire.
19
+ *
20
+ * **What a schema promises is what the wire promises ACROSS versions**, and
21
+ * that decides where it is looser than the type. The platform grows its own
22
+ * vocabularies: `via` gained three members in one month, `status` gained
23
+ * `deleting`, `plan` gained `team`. A consumer validating a response holds
24
+ * the schema it shipped with, so an enum there would turn the platform's
25
+ * next word into a validation failure on every older client. Those fields
26
+ * are therefore strings whose DESCRIPTION names today's members (derived
27
+ * from the constant, never typed out), while vocabularies that are not ours
28
+ * to grow (a DNS record type, Stripe's billing interval) are enums. The
29
+ * fence admits this on purpose: the interface must be assignable to the
30
+ * schema's output, not equal to it.
31
+ *
32
+ * Every field is described, and that is fenced too: these descriptions are
33
+ * what an agent reads when a tool publishes the schema, and an undescribed
34
+ * field is a field an agent cannot use. The sentences are the interfaces'
35
+ * own, shortened to what a reader of a result needs.
36
+ *
37
+ * A subpath export (`@shipstatic/types/schemas`), so a consumer that never
38
+ * imports it carries no zod: the browser bundles read `index.ts` alone.
39
+ */
40
+
41
+ import { z } from 'zod';
42
+ import {
43
+ AccountPlan,
44
+ DeploymentStatus,
45
+ DeploymentVia,
46
+ DomainStatus,
47
+ PUBLIC_DEPLOYMENT_TTL_SECONDS,
48
+ } from './index.js';
49
+
50
+ /** A vocabulary the platform grows, described by today's members and open to tomorrow's. */
51
+ const grown = (members: Record<string, string>, what: string) =>
52
+ z.string().describe(`${what} One of: ${Object.values(members).join(', ')}.`);
53
+
54
+ const unixSeconds = (what: string) => z.int().describe(`Unix timestamp (seconds) ${what}.`);
55
+
56
+ // =============================================================================
57
+ // LISTS
58
+ // =============================================================================
59
+
60
+ /** The half of every list response that is identical on every list. */
61
+ export const ListResponseSchema = z.object({
62
+ cursor: z
63
+ .string()
64
+ .nullable()
65
+ .describe('Opaque cursor for the next page; null on the last page. The whole has-more signal.'),
66
+ });
67
+
68
+ // =============================================================================
69
+ // DEPLOYMENTS
70
+ // =============================================================================
71
+
72
+ export const DeploymentSchema = z.object({
73
+ deployment: z.string().describe('Deployment hostname, e.g. "happy-cat-abc1234.shipstatic.com".'),
74
+ url: z.url().describe('Full URL to the live deployment.'),
75
+ files: z.int().nonnegative().describe('Number of files in the deployment.'),
76
+ size: z.int().nonnegative().describe('Total deployment size in bytes.'),
77
+ status: grown(DeploymentStatus, 'Deployment lifecycle state; "success" means the site is live.'),
78
+ config: z.boolean().describe('True if the deployment includes a ship.json routing config.'),
79
+ password: z.boolean().describe('True if the deployment is password-protected.'),
80
+ labels: z.array(z.string()).describe('Labels attached to the deployment; empty when none.'),
81
+ via: z
82
+ .string()
83
+ .nullable()
84
+ .describe(
85
+ `How the deployment was created, as shown in deployment history (today one of: ${Object.values(DeploymentVia).join(', ')}). Null on deployments older than the tag.`,
86
+ ),
87
+ created: unixSeconds('when the deployment was created'),
88
+ expires: z
89
+ .int()
90
+ .nullable()
91
+ .describe(
92
+ `Unix timestamp (seconds) when the deployment expires; null when permanent. Anonymous deployments expire ${PUBLIC_DEPLOYMENT_TTL_SECONDS / 86_400} days after creation unless claimed; an authenticated deployment carries one only when it requested a ttl.`,
93
+ ),
94
+ screenshot: z
95
+ .url()
96
+ .describe(
97
+ 'Full URL to the deployment screenshot. Captured asynchronously after deploy; the URL is returned immediately but the image may take a few seconds to become available.',
98
+ ),
99
+ });
100
+
101
+ export const DeploymentCreateResponseSchema = DeploymentSchema.extend({
102
+ claim: z
103
+ .url()
104
+ .optional()
105
+ .describe(
106
+ 'One-time URL that claims this anonymous deployment to a free account, making it permanent. Only present on anonymous deploys; absent when the deploy was made with a connected account.',
107
+ ),
108
+ });
109
+
110
+ export const DeploymentListResponseSchema = ListResponseSchema.extend({
111
+ deployments: z.array(DeploymentSchema).describe('The deployments on this page.'),
112
+ });
113
+
114
+ export const DeploymentDeleteResponseSchema = z.object({
115
+ deployment: z.string().describe('The deployment hostname that was marked for removal.'),
116
+ status: grown(DeploymentStatus, 'The state the deployment is in while background cleanup runs.'),
117
+ });
118
+
119
+ // =============================================================================
120
+ // DOMAINS
121
+ // =============================================================================
122
+
123
+ export const DomainSchema = z.object({
124
+ domain: z.string().describe('The domain name, e.g. "www.example.com".'),
125
+ url: z.url().describe('Full URL to the domain.'),
126
+ deployment: z
127
+ .string()
128
+ .nullable()
129
+ .describe(
130
+ 'The deployment hostname this domain points to; null when reserved but not yet linked.',
131
+ ),
132
+ status: grown(
133
+ DomainStatus,
134
+ 'DNS verification state; "success" means the domain serves the linked deployment.',
135
+ ),
136
+ labels: z.array(z.string()).describe('Labels attached to the domain; empty when none.'),
137
+ created: unixSeconds('when the domain was created'),
138
+ linked: z
139
+ .int()
140
+ .nullable()
141
+ .describe('Unix timestamp (seconds) when a deployment was last linked; null if never linked.'),
142
+ links: z
143
+ .int()
144
+ .nonnegative()
145
+ .describe('How many times a deployment has been linked to this domain.'),
146
+ });
147
+
148
+ /** `Domain` plus the SDK's own create-versus-update flag; the wire body is a plain `Domain`. */
149
+ export const DomainSetResultSchema = DomainSchema.extend({
150
+ isCreate: z
151
+ .boolean()
152
+ .describe('True when this call created the domain; false when it updated an existing one.'),
153
+ });
154
+
155
+ export const DomainListResponseSchema = ListResponseSchema.extend({
156
+ domains: z.array(DomainSchema).describe('The domains on this page.'),
157
+ });
158
+
159
+ export const DomainDeleteResponseSchema = z.object({
160
+ domain: z.string().describe('The domain name that was removed, normalized.'),
161
+ });
162
+
163
+ export const DomainVerifyResponseSchema = z.object({
164
+ domain: z
165
+ .string()
166
+ .describe(
167
+ 'The domain whose DNS verification was queued, normalized. The check runs asynchronously; the domain status updates once DNS propagates.',
168
+ ),
169
+ });
170
+
171
+ export const DnsRecordSchema = z.object({
172
+ type: z
173
+ .enum(['A', 'CNAME'])
174
+ .describe('Record type: A for the apex redirect, CNAME for the hosted subdomain.'),
175
+ name: z.string().describe('The DNS name to configure.'),
176
+ value: z.string().describe('The value to set: an IP for A, a hostname for CNAME.'),
177
+ });
178
+
179
+ export const DomainRecordsResponseSchema = z.object({
180
+ domain: z.string().describe('The domain the records are for.'),
181
+ apex: z.string().describe('The apex (registered) domain where DNS records are managed.'),
182
+ records: z
183
+ .array(DnsRecordSchema)
184
+ .describe('The records to configure at the DNS provider, in the order to add them.'),
185
+ });
186
+
187
+ export const DnsProviderSchema = z.object({
188
+ name: z.string().nullable().describe('Provider name, e.g. "Cloudflare"; null if unknown.'),
189
+ url: z
190
+ .url()
191
+ .nullable()
192
+ .optional()
193
+ .describe(
194
+ "The provider's DNS dashboard, where the records get added; null or absent when unknown.",
195
+ ),
196
+ });
197
+
198
+ export const DnsLookupSchema = z.object({
199
+ provider: DnsProviderSchema.optional().describe(
200
+ "The provider serving this domain's DNS; absent when unidentified.",
201
+ ),
202
+ });
203
+
204
+ export const DomainDnsResponseSchema = z.object({
205
+ domain: z.string().describe('The domain name.'),
206
+ dns: DnsLookupSchema.nullable().describe(
207
+ "What the platform recorded about the domain's DNS provider when the domain was created; null if nothing was recorded.",
208
+ ),
209
+ });
210
+
211
+ export const DomainShareResponseSchema = z.object({
212
+ domain: z.string().describe('The domain the setup link is for.'),
213
+ url: z
214
+ .url()
215
+ .describe(
216
+ 'The shareable DNS setup URL; whoever opens it sees the records to configure, with no API key.',
217
+ ),
218
+ });
219
+
220
+ export const DomainValidateResponseSchema = z.object({
221
+ valid: z.boolean().describe('Whether the domain name is valid.'),
222
+ normalized: z.string().nullable().describe('The normalized domain name; null when invalid.'),
223
+ available: z.boolean().nullable().describe('Whether the domain is available; null when invalid.'),
224
+ reason: z.string().nullable().describe('Why the name is unusable, for display; null when valid.'),
225
+ });
226
+
227
+ // =============================================================================
228
+ // ACCOUNT
229
+ // =============================================================================
230
+
231
+ export const CapsSchema = z.object({
232
+ deployments: z.int().nonnegative().describe('Deployments, every row whatever its status.'),
233
+ platformDomains: z
234
+ .int()
235
+ .nonnegative()
236
+ .describe('Names chosen under the platform\'s own suffix, e.g. "my-app.shipstatic.com".'),
237
+ customDomains: z
238
+ .int()
239
+ .nonnegative()
240
+ .describe('Hostnames the customer owns, paused ones included.'),
241
+ });
242
+
243
+ export const ScheduledChangeSchema = z.object({
244
+ plan: grown(AccountPlan, 'The plan the account moves to.'),
245
+ interval: z.enum(['month', 'year']).describe('The billing interval the change applies with.'),
246
+ at: unixSeconds('when the change applies'),
247
+ });
248
+
249
+ export const AccountSchema = z.object({
250
+ email: z.string().describe('The account email address.'),
251
+ name: z.string().nullable().describe('Display name; null if not set.'),
252
+ picture: z.url().nullable().describe('Profile picture URL; null if not set.'),
253
+ plan: grown(AccountPlan, 'The tier the account stands at.'),
254
+ suspended: z
255
+ .boolean()
256
+ .describe(
257
+ 'True while the operator has suspended the account: reads work, every write is refused.',
258
+ ),
259
+ usage: CapsSchema.describe('What the account currently holds.'),
260
+ caps: CapsSchema.describe(
261
+ 'What the account is allowed to hold: the same three keys as usage, so the pair divides.',
262
+ ),
263
+ created: unixSeconds('when the account was created'),
264
+ activated: z
265
+ .int()
266
+ .nullable()
267
+ .describe('Unix timestamp (seconds) of the first deployment; null if not yet activated.'),
268
+ hint: z
269
+ .string()
270
+ .nullable()
271
+ .describe('Last 4 characters of the API key; null when no key exists.'),
272
+ used: z
273
+ .int()
274
+ .nullable()
275
+ .optional()
276
+ .describe(
277
+ "Unix timestamp (seconds) of the API key's last use; null or absent when never used.",
278
+ ),
279
+ pastDue: z
280
+ .boolean()
281
+ .describe(
282
+ 'True while the subscription is past due and the card is being retried; the plan is unchanged.',
283
+ ),
284
+ billed: z.boolean().describe('Whether a subscription bills this plan.'),
285
+ upgrade: z
286
+ .string()
287
+ .nullable()
288
+ .describe(
289
+ `The next plan up the ladder, or null when there is none. One of: ${Object.values(AccountPlan).join(', ')}.`,
290
+ ),
291
+ interval: z
292
+ .enum(['month', 'year'])
293
+ .nullable()
294
+ .describe("The live subscription's billing interval; null when nothing bills the account."),
295
+ scheduled: ScheduledChangeSchema.nullable().describe('The pending plan change; null when none.'),
296
+ cancelAt: z
297
+ .int()
298
+ .nullable()
299
+ .describe('Unix timestamp (seconds) when the subscription ends; null while it renews.'),
300
+ });