@typeship-ax/mcp 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/AGENTS.md +15 -11
  2. package/README.md +22 -53
  3. package/api.json +9998 -10118
  4. package/api.md +8983 -9120
  5. package/dist/arguments.d.ts +54 -0
  6. package/dist/arguments.d.ts.map +1 -0
  7. package/dist/arguments.js +265 -0
  8. package/dist/core/http.d.ts +162 -19
  9. package/dist/core/http.d.ts.map +1 -1
  10. package/dist/core/http.js +381 -48
  11. package/dist/core/pagination.d.ts +42 -6
  12. package/dist/core/pagination.d.ts.map +1 -1
  13. package/dist/core/pagination.js +111 -17
  14. package/dist/credential-storage.d.ts +10 -3
  15. package/dist/credential-storage.d.ts.map +1 -1
  16. package/dist/credential-storage.js +15 -6
  17. package/dist/dates.d.ts +1 -1
  18. package/dist/dates.js +1 -1
  19. package/dist/errors.d.ts +20 -84
  20. package/dist/errors.d.ts.map +1 -1
  21. package/dist/errors.js +20 -108
  22. package/dist/fields.d.ts +36 -0
  23. package/dist/fields.d.ts.map +1 -0
  24. package/dist/fields.js +187 -0
  25. package/dist/index.d.ts +28 -18
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +35 -25
  28. package/dist/mcp-authorization.d.ts.map +1 -1
  29. package/dist/mcp-authorization.js +34 -10
  30. package/dist/mcp-protocol.d.ts +108 -44
  31. package/dist/mcp-protocol.d.ts.map +1 -1
  32. package/dist/mcp-protocol.js +780 -484
  33. package/dist/mcp.d.ts.map +1 -1
  34. package/dist/mcp.js +129 -29
  35. package/dist/named-credentials.d.ts +19 -0
  36. package/dist/named-credentials.d.ts.map +1 -1
  37. package/dist/named-credentials.js +81 -1
  38. package/dist/oauth-request.d.ts +7 -1
  39. package/dist/oauth-request.d.ts.map +1 -1
  40. package/dist/oauth-request.js +26 -4
  41. package/dist/oauth-session.d.ts +13 -1
  42. package/dist/oauth-session.d.ts.map +1 -1
  43. package/dist/oauth-session.js +34 -18
  44. package/dist/ops.d.ts +58 -5
  45. package/dist/ops.d.ts.map +1 -1
  46. package/dist/ops.js +110 -41
  47. package/dist/resources/api-keys.d.ts +10 -7
  48. package/dist/resources/api-keys.d.ts.map +1 -1
  49. package/dist/resources/api-keys.js +10 -31
  50. package/dist/resources/deliveries.d.ts +89 -5
  51. package/dist/resources/deliveries.d.ts.map +1 -1
  52. package/dist/resources/deliveries.js +96 -19
  53. package/dist/resources/drafts.d.ts +16 -16
  54. package/dist/resources/drafts.d.ts.map +1 -1
  55. package/dist/resources/drafts.js +12 -65
  56. package/dist/resources/files.d.ts +4 -4
  57. package/dist/resources/files.d.ts.map +1 -1
  58. package/dist/resources/files.js +3 -12
  59. package/dist/resources/generations.d.ts +16 -16
  60. package/dist/resources/generations.d.ts.map +1 -1
  61. package/dist/resources/generations.js +23 -47
  62. package/dist/resources/organization.d.ts +4 -4
  63. package/dist/resources/organization.d.ts.map +1 -1
  64. package/dist/resources/organization.js +3 -10
  65. package/dist/resources/{generate.d.ts → packages.d.ts} +16 -16
  66. package/dist/resources/packages.d.ts.map +1 -0
  67. package/dist/resources/{generate.js → packages.js} +13 -29
  68. package/dist/resources/projects.d.ts +50 -50
  69. package/dist/resources/projects.d.ts.map +1 -1
  70. package/dist/resources/projects.js +60 -116
  71. package/dist/resources/releases.d.ts +22 -17
  72. package/dist/resources/releases.d.ts.map +1 -1
  73. package/dist/resources/releases.js +19 -40
  74. package/dist/resources/spec-revisions.d.ts +16 -7
  75. package/dist/resources/spec-revisions.d.ts.map +1 -1
  76. package/dist/resources/spec-revisions.js +7 -29
  77. package/dist/resources/specs.d.ts +7 -7
  78. package/dist/resources/specs.d.ts.map +1 -1
  79. package/dist/resources/specs.js +6 -34
  80. package/dist/resources/targets.d.ts +49 -49
  81. package/dist/resources/targets.d.ts.map +1 -1
  82. package/dist/resources/targets.js +59 -115
  83. package/dist/schemas.d.ts.map +1 -1
  84. package/dist/schemas.js +78 -76
  85. package/dist/search.d.ts +54 -0
  86. package/dist/search.d.ts.map +1 -0
  87. package/dist/search.js +421 -0
  88. package/dist/type-docs.d.ts +61 -0
  89. package/dist/type-docs.d.ts.map +1 -0
  90. package/dist/type-docs.js +174 -0
  91. package/dist/types.d.ts +499 -339
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/types.js +18 -18
  94. package/dist/worker.js +2 -2
  95. package/package.json +5 -2
  96. package/server.json +5 -5
  97. package/src/arguments.ts +254 -0
  98. package/src/core/http.ts +457 -58
  99. package/src/core/pagination.ts +129 -18
  100. package/src/credential-storage.ts +16 -6
  101. package/src/dates.ts +1 -1
  102. package/src/errors.ts +46 -115
  103. package/src/fields.ts +167 -0
  104. package/src/index.ts +45 -28
  105. package/src/mcp-authorization.ts +29 -9
  106. package/src/mcp-protocol.ts +808 -435
  107. package/src/mcp.ts +115 -27
  108. package/src/named-credentials.ts +66 -1
  109. package/src/oauth-request.ts +32 -6
  110. package/src/oauth-session.ts +37 -19
  111. package/src/ops.ts +146 -45
  112. package/src/resources/api-keys.ts +34 -48
  113. package/src/resources/deliveries.ts +213 -32
  114. package/src/resources/drafts.ts +62 -109
  115. package/src/resources/files.ts +19 -20
  116. package/src/resources/generations.ts +61 -79
  117. package/src/resources/organization.ts +11 -16
  118. package/src/resources/{generate.ts → packages.ts} +43 -51
  119. package/src/resources/projects.ts +145 -200
  120. package/src/resources/releases.ts +50 -67
  121. package/src/resources/spec-revisions.ts +40 -49
  122. package/src/resources/specs.ts +39 -59
  123. package/src/resources/targets.ts +144 -194
  124. package/src/schemas.ts +78 -76
  125. package/src/search.ts +434 -0
  126. package/src/type-docs.ts +205 -0
  127. package/src/types.ts +538 -357
  128. package/src/worker.ts +2 -2
  129. package/dist/resources/generate.d.ts.map +0 -1
  130. package/dist/resources/publications.d.ts +0 -47
  131. package/dist/resources/publications.d.ts.map +0 -1
  132. package/dist/resources/publications.js +0 -70
  133. package/src/resources/publications.ts +0 -140
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Auto-pagination. Generated by typeship — https://typeship.dev
2
+ * Auto-pagination. Generated by Typeship — https://typeship.dev
3
3
  *
4
4
  * List operations return a PagePromise: `await` it for one page, or
5
5
  * `for await` it to stream every item across every page.
@@ -7,13 +7,15 @@
7
7
 
8
8
  import {
9
9
  HttpCore,
10
+ SdkError,
10
11
  type CoreRequest,
11
12
  type ResponseMeta,
12
13
  } from "./http.js";
13
14
 
14
15
  export interface PageConfig {
15
- style: "cursor" | "cursorFromLastId" | "page" | "offset";
16
- /** Response field holding the item array ("data", "items", …). */
16
+ style: "cursor" | "cursorFromLastId" | "page" | "offset" | "nextUrl" | "link";
17
+ /** Response field holding the item array ("data", "items", …); "" when
18
+ * the body is the array itself. */
17
19
  itemsField: string;
18
20
  cursorParam?: string;
19
21
  /** Dot-path into the response body ("next_cursor", "meta.next_cursor"). */
@@ -24,16 +26,41 @@ export interface PageConfig {
24
26
  pageParam?: string;
25
27
  offsetParam?: string;
26
28
  limitParam?: string;
29
+ /** nextUrl: dot-path of the next page's URL ("next_page_uri", "next"). */
30
+ nextUrlField?: string;
31
+ /** page: the first page number when it is not 1. */
32
+ firstPage?: number;
33
+ /** Dot-path of the total item count: iteration stops once it is reached. */
34
+ totalField?: string;
35
+ /** Dot-path of the total page count: page iteration stops at the last page. */
36
+ totalPagesField?: string;
37
+ /** Each item is this field of the listed element (a Relay edge's `node`). */
38
+ itemPath?: string;
39
+ /** Page size sent when the caller gives none (GraphQL `first`). */
40
+ defaultLimit?: number;
41
+ /** Relay backward paging, used when the caller passes `backward.limitParam` (`last`). */
42
+ backward?: { limitParam: string; cursorParam: string; cursorField: string; hasMoreField: string };
43
+ }
44
+
45
+ /** A list response the pagination rules cannot read. */
46
+ export class PaginationError extends SdkError {
47
+ constructor(message: string, response?: ResponseMeta) {
48
+ super(message, "pagination_error", response?.status ?? null, response?.rawBody, response?.requestId);
49
+ }
27
50
  }
28
51
 
29
52
  type FetchPage<Item, E> = (
30
53
  query: Record<string, unknown>,
54
+ url?: string,
55
+ before?: number,
31
56
  ) => Promise<Page<Item, E>>;
32
57
 
33
58
  export class Page<Item, E = unknown> {
34
59
  private readonly fetchPage: FetchPage<Item, E>;
35
60
  private readonly config: PageConfig;
36
61
  private readonly params: Record<string, unknown>;
62
+ /** Items on the pages before this one, for the total-count stop. */
63
+ private readonly before: number;
37
64
  readonly body: unknown;
38
65
  readonly response: ResponseMeta;
39
66
 
@@ -43,48 +70,74 @@ export class Page<Item, E = unknown> {
43
70
  params: Record<string, unknown>,
44
71
  body: unknown,
45
72
  response: ResponseMeta,
73
+ before = 0,
46
74
  ) {
47
75
  this.fetchPage = fetchPage;
48
76
  this.config = config;
49
77
  this.params = params;
50
78
  this.body = body;
51
79
  this.response = response;
80
+ this.before = before;
52
81
  }
53
82
 
54
83
  get items(): Item[] {
55
- const value = getPath(this.body, this.config.itemsField);
56
- return Array.isArray(value) ? (value as Item[]) : [];
84
+ const value = this.config.itemsField === "" ? this.body : getPath(this.body, this.config.itemsField);
85
+ if (!Array.isArray(value)) return [];
86
+ const path = this.config.itemPath;
87
+ return path === undefined ? (value as Item[]) : value.map((entry) => (entry as Record<string, unknown> | null)?.[path] as Item);
88
+ }
89
+
90
+ /** True when the caller asked for backward Relay pages (`last`). */
91
+ private get backward(): PageConfig["backward"] | undefined {
92
+ const backward = this.config.backward;
93
+ return backward && this.params[backward.limitParam] !== undefined ? backward : undefined;
57
94
  }
58
95
 
59
96
  hasNextPage(): boolean {
60
97
  return this.nextPageParams() !== null;
61
98
  }
62
99
 
100
+ /** The next page's URL, for styles that follow one (a response field or
101
+ * the Link header); null on the last page and for the other styles. */
102
+ nextPageUrl(): string | null {
103
+ if (this.finished()) return null;
104
+ switch (this.config.style) {
105
+ }
106
+ return null;
107
+ }
108
+
63
109
  nextPageParams(): Record<string, unknown> | null {
64
110
  const { config, params } = this;
65
111
  const items = this.items;
66
-
67
- if (config.hasMoreField !== undefined) {
68
- const hasMore = getPath(this.body, config.hasMoreField);
69
- if (hasMore === false) return null;
70
- }
112
+ if (this.finished()) return null;
113
+ const backward = this.backward;
71
114
 
72
115
  switch (config.style) {
73
116
  case "cursor": {
74
- const next = getPath(this.body, config.nextCursorField!);
117
+ const cursorParam = backward ? backward.cursorParam : config.cursorParam!;
118
+ const next = getPath(this.body, backward ? backward.cursorField : config.nextCursorField!);
75
119
  if (next === undefined || next === null || next === "") return null;
76
- if (next === params[config.cursorParam!]) return null;
77
- return { ...params, [config.cursorParam!]: next };
120
+ if (next === params[cursorParam]) return null;
121
+ return { ...params, [cursorParam]: next };
78
122
  }
79
123
  }
80
- return null;
124
+ // URL styles: the next request is the URL itself. Pass page_url as the
125
+ // pageUrl request option to fetch that page.
126
+ const url = this.nextPageUrl();
127
+ if (url === null) return null;
128
+ // The URL is the whole request: its query can hold parameters the
129
+ // operation does not declare (GitHub's after), so it is passed as is.
130
+ return { page_url: url };
81
131
  }
82
132
 
83
133
  /** Fetch the next page, or null when this is the last one. Throws the typed error on failure. */
84
134
  async getNextPage(): Promise<Page<Item, E> | null> {
135
+ const url = this.nextPageUrl();
136
+ if (url !== null) return this.fetchPage({}, url, this.before + this.items.length);
137
+ if (this.config.style === "nextUrl" || this.config.style === "link") return null;
85
138
  const next = this.nextPageParams();
86
139
  if (next === null) return null;
87
- return this.fetchPage(next);
140
+ return this.fetchPage(next, undefined, this.before + this.items.length);
88
141
  }
89
142
 
90
143
  /** Iterate every item on this page and all following pages. */
@@ -96,6 +149,36 @@ export class Page<Item, E = unknown> {
96
149
  }
97
150
  }
98
151
 
152
+ /** Signals every style shares: has_more false, or the total reached. */
153
+ private finished(): boolean {
154
+ // Backward Relay pages end on hasPreviousPage, forward ones on hasNextPage.
155
+ const hasMoreField = this.backward?.hasMoreField ?? this.config.hasMoreField;
156
+ if (hasMoreField !== undefined && getPath(this.body, hasMoreField) === false) return true;
157
+ if (this.config.totalField !== undefined) {
158
+ const total = getPath(this.body, this.config.totalField);
159
+ if (typeof total === "number" && this.itemsThrough() >= total) return true;
160
+ }
161
+ return false;
162
+ }
163
+
164
+ /** Items up to the end of this page: counted along a walk, or implied by
165
+ * the page's position when it was fetched on its own (an MCP nextPage). */
166
+ private itemsThrough(): number {
167
+ let before = this.before;
168
+ const { config, params } = this;
169
+ if (before === 0 && config.style === "offset" && config.offsetParam !== undefined) {
170
+ const offset = Number(params[config.offsetParam]);
171
+ if (Number.isFinite(offset) && offset > 0) before = offset;
172
+ }
173
+ if (before === 0 && config.style === "page" && config.pageParam !== undefined && config.limitParam !== undefined) {
174
+ const current = Number(params[config.pageParam]);
175
+ const limit = Number(params[config.limitParam]);
176
+ const first = config.firstPage ?? 1;
177
+ if (Number.isFinite(current) && Number.isFinite(limit) && limit > 0 && current > first) before = (current - first) * limit;
178
+ }
179
+ return before + this.items.length;
180
+ }
181
+
99
182
  private looksLikeMore(items: Item[]): boolean {
100
183
  if (items.length === 0) return false;
101
184
  if (this.config.limitParam !== undefined) {
@@ -137,20 +220,48 @@ export function paginate<Item, E>(
137
220
  ): PagePromise<Item, E> {
138
221
  const isGraphql = false
139
222
  ;
140
- const fetchPage: FetchPage<Item, E> = async (params) => {
223
+ const fetchPage: FetchPage<Item, E> = async (params, url, before = 0) => {
141
224
  let nextReq: CoreRequest = { ...req, query: params };
225
+ if (url !== undefined) {
226
+ // A next-page URL is followed verbatim, but only on the API's own
227
+ // origin: the request carries the client's credentials.
228
+ const base = new URL(core.config.baseUrl);
229
+ const target = new URL(url, base);
230
+ if (target.origin !== base.origin) {
231
+ throw new PaginationError("The next page is on another origin (" + target.origin + "); refusing to send credentials there. Fetch it yourself if you trust it.");
232
+ }
233
+ nextReq = { ...req, query: undefined, url: target.toString() };
234
+ }
142
235
  const result = await core.request<unknown, E>(nextReq);
143
236
  if (!result.ok) throw result.error instanceof Error ? result.error : new Error(String(result.error));
144
- return new Page<Item, E>(fetchPage, config, params, result.data, result.response);
237
+ // A page without its item array is a contract break, not an empty page:
238
+ // iterating it would silently end the walk. A null array is empty.
239
+ const items = config.itemsField === "" ? result.data : getPath(result.data, config.itemsField);
240
+ if (!Array.isArray(items) && !(items === null && config.itemsField !== "")) {
241
+ throw new PaginationError(
242
+ (config.itemsField === "" ? "The list response is not an array" : "The list response has no " + JSON.stringify(config.itemsField) + " array")
243
+ + ". Check the API response against the spec, or configure this operation's pagination.",
244
+ result.response,
245
+ );
246
+ }
247
+ return new Page<Item, E>(fetchPage, config, params, result.data, result.response, before);
145
248
  };
146
249
  const initial: Record<string, unknown> = {};
147
250
  let seed = req.query ?? {};
148
251
  for (const [k, v] of Object.entries(seed)) {
149
252
  if (v !== undefined) initial[k] = v;
150
253
  }
151
- return new PagePromise<Item, E>(fetchPage(initial));
254
+ // Relay servers reject a connection query with neither `first` nor `last`.
255
+ const backwardLimit = config.backward?.limitParam;
256
+ if (config.defaultLimit !== undefined && config.limitParam !== undefined && initial[config.limitParam] === undefined
257
+ && (backwardLimit === undefined || initial[backwardLimit] === undefined)) {
258
+ initial[config.limitParam] = config.defaultLimit;
259
+ }
260
+ const pageUrl = req.options?.pageUrl;
261
+ return new PagePromise<Item, E>(pageUrl !== undefined ? fetchPage({}, pageUrl) : fetchPage(initial));
152
262
  }
153
263
 
264
+
154
265
  function getPath(body: unknown, path: string): unknown {
155
266
  let node: unknown = body;
156
267
  for (const key of path.split(".")) {
@@ -30,12 +30,22 @@ function parseKey(value: string): Buffer {
30
30
  return key;
31
31
  }
32
32
 
33
+ /** The OS item's service name: the command that owns it, then a digest of
34
+ * the session file, so each profile has its own key. Limited to a fixed
35
+ * alphabet because the macOS write passes it through security's command
36
+ * stream. */
37
+ export function credentialKeyService(owner: string, path: string): string {
38
+ const name = owner.replace(/[^A-Za-z0-9._-]/g, "-") || "cli";
39
+ return name + ".credentials." + createHash("sha256").update(resolve(path)).digest("hex");
40
+ }
41
+
33
42
  /** A small wrapping key avoids OS item-size limits; session files are encrypted
34
- * separately so refresh rotation can retain the atomic file/lock transaction. */
35
- export function nativeCredentialKeyStore(path: string, platform: NodeJS.Platform = process.platform, run: NativeCommand = nativeCommand, environmentName = "CREDENTIAL_STORE"): CredentialKeyStore {
43
+ * separately so refresh rotation can retain the atomic file/lock transaction.
44
+ * `owner` is the command name; the CLI and its local MCP server pass the same
45
+ * one so they share the key. */
46
+ export function nativeCredentialKeyStore(path: string, owner: string, platform: NodeJS.Platform = process.platform, run: NativeCommand = nativeCommand, environmentName = "CREDENTIAL_STORE"): CredentialKeyStore {
36
47
  const failed = (name: string): never => unavailable(name, environmentName);
37
- const identity = createHash("sha256").update(resolve(path)).digest("hex");
38
- const service = "typeship.credentials." + identity;
48
+ const service = credentialKeyService(owner, path);
39
49
  const account = "session-key";
40
50
  if (platform === "darwin") {
41
51
  const name = "macOS Keychain";
@@ -175,9 +185,9 @@ export function encryptedCredentialCodec(path: string, keyStore: CredentialKeySt
175
185
  /** OS protection is the default. Plaintext storage is an explicit, separate
176
186
  * store for environments where the owner accepts that tradeoff. Never migrate
177
187
  * or fall back silently when an OS service is unavailable. */
178
- export function createCredentialStore(directory: string, mode: string = "os", environmentName = "CREDENTIAL_STORE"): FileCredentialStore {
188
+ export function createCredentialStore(directory: string, owner: string, mode: string = "os", environmentName = "CREDENTIAL_STORE"): FileCredentialStore {
179
189
  if (mode === "file") return new FileCredentialStore(join(directory, "credentials.json"));
180
190
  if (mode !== "os") throw new CredentialStorageError(environmentName + " must be os or file.");
181
191
  const path = join(directory, "credentials.enc");
182
- return new FileCredentialStore(path, 40_000, encryptedCredentialCodec(path, nativeCredentialKeyStore(path, process.platform, nativeCommand, environmentName)));
192
+ return new FileCredentialStore(path, 40_000, encryptedCredentialCodec(path, nativeCredentialKeyStore(path, owner, process.platform, nativeCommand, environmentName)));
183
193
  }
package/src/dates.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relative dates for date / date-time arguments. Generated by typeship — https://typeship.dev
2
+ * Relative dates for date / date-time arguments. Generated by Typeship — https://typeship.dev
3
3
  *
4
4
  * Agents rarely know "now" and often do know "a week ago", so date-shaped
5
5
  * arguments of the CLI and the MCP server accept, besides an absolute
package/src/errors.ts CHANGED
@@ -1,138 +1,69 @@
1
- // typeship — typed error classes.
2
- // Generated by typeship — https://typeship.dev
1
+ // Typeship — typed error classes.
2
+ // Generated by Typeship — https://typeship.dev
3
3
 
4
- import { ApiError, type ResponseMeta } from "./core/http.js";
4
+ import { ApiError, RateLimitError, ServerError, type ResponseMeta } from "./core/http.js";
5
5
  import type { ErrorModel, ErrorModelRead } from "./types.js";
6
6
 
7
- export { ApiError, SdkError, ResponseParseError, TransportError, UnexpectedApiError, ValidationError, type Violation } from "./core/http.js";
7
+ export {
8
+ ApiError,
9
+ SdkError,
10
+ PayloadError,
11
+ ResponseParseError,
12
+ TransportError,
13
+ UnexpectedApiError,
14
+ ValidationError,
15
+ BadRequestError,
16
+ ConflictError,
17
+ ForbiddenError,
18
+ NotFoundError,
19
+ NotModifiedError,
20
+ RateLimitError,
21
+ ServerError,
22
+ UnauthorizedError,
23
+ UnprocessableEntityError,
24
+ type RateLimitInfo,
25
+ type Violation,
26
+ } from "./core/http.js";
8
27
 
9
- /**
10
- * The request body, Spec source, target selection, or package name is invalid.
11
- * Raised for HTTP 400 responses.
12
- */
13
- export class BadRequestError extends ApiError<400, ErrorModelRead> {
14
- constructor(body: ErrorModelRead, response: ResponseMeta) {
15
- super("The request body, Spec source, target selection, or package name is invalid.", 400, body, response);
16
- }
17
- }
18
-
19
- /**
20
- * Missing, invalid, expired, or revoked credentials.
21
- * Raised for HTTP 401 responses.
22
- */
23
- export class UnauthorizedError extends ApiError<401, ErrorModelRead> {
24
- constructor(body: ErrorModelRead, response: ResponseMeta) {
25
- super("Missing, invalid, expired, or revoked credentials.", 401, body, response);
26
- }
27
- }
28
-
29
- /**
30
- * The credentials are valid but cannot act on the requested organization.
31
- * Raised for HTTP 403 responses.
32
- */
33
- export class ForbiddenError extends ApiError<403, ErrorModelRead> {
28
+ /** Raised for HTTP 402 responses. */
29
+ export class PaymentRequiredError extends ApiError<402, ErrorModelRead> {
34
30
  constructor(body: ErrorModelRead, response: ResponseMeta) {
35
- super("The credentials are valid but cannot act on the requested organization.", 403, body, response);
31
+ super("HTTP 402", 402, body, response);
36
32
  }
37
33
  }
38
34
 
39
- /**
40
- * The key identifies changed intent.
41
- * Raised for HTTP 409 responses.
42
- */
43
- export class ConflictError extends ApiError<409, ErrorModelRead> {
44
- constructor(body: ErrorModelRead, response: ResponseMeta) {
45
- super("The key identifies changed intent.", 409, body, response);
46
- }
47
- }
35
+ /** @deprecated Use RateLimitError, raised for every 429 response. */
36
+ export const RateLimitedError = RateLimitError;
37
+ /** @deprecated Use RateLimitError, raised for every 429 response. */
38
+ export type RateLimitedError = RateLimitError<ErrorModelRead>;
48
39
 
49
- /**
50
- * Spec exceeds the 10MB limit.
51
- * Raised for HTTP 413 responses.
52
- */
53
- export class PayloadTooLargeError extends ApiError<413, ErrorModelRead> {
54
- constructor(body: ErrorModelRead, response: ResponseMeta) {
55
- super("Spec exceeds the 10MB limit.", 413, body, response);
56
- }
57
- }
40
+ /** @deprecated Use ServerError, raised for every 5xx response. */
41
+ export const InternalServerError = ServerError;
42
+ /** @deprecated Use ServerError, raised for every 5xx response. */
43
+ export type InternalServerError = ServerError<ErrorModelRead>;
58
44
 
59
- /**
60
- * The Spec could not be resolved or understood.
61
- * Raised for HTTP 422 responses.
62
- */
63
- export class UnprocessableEntityError extends ApiError<422, ErrorModelRead> {
45
+ /** Raised for HTTP 412 responses. */
46
+ export class PreconditionFailedError extends ApiError<412, ErrorModelRead> {
64
47
  constructor(body: ErrorModelRead, response: ResponseMeta) {
65
- super("The Spec could not be resolved or understood.", 422, body, response);
48
+ super("HTTP 412", 412, body, response);
66
49
  }
67
50
  }
68
51
 
69
- /**
70
- * Too many requests, or an identical write is still in progress. Wait for Retry-After before
71
- * retrying.
72
- * Raised for HTTP 429 responses.
73
- */
74
- export class RateLimitedError extends ApiError<429, ErrorModelRead> {
75
- constructor(body: ErrorModelRead, response: ResponseMeta) {
76
- super("Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.", 429, body, response);
77
- }
78
- }
52
+ /** @deprecated Use ServerError, raised for every 5xx response. */
53
+ export const BadGatewayError = ServerError;
54
+ /** @deprecated Use ServerError, raised for every 5xx response. */
55
+ export type BadGatewayError = ServerError<ErrorModelRead>;
79
56
 
80
- /**
81
- * An unexpected error prevented the request from completing.
82
- * Raised for HTTP 500 responses.
83
- */
84
- export class InternalServerError extends ApiError<500, ErrorModelRead> {
57
+ /** Raised for HTTP 413 responses. */
58
+ export class PayloadTooLargeError extends ApiError<413, ErrorModelRead> {
85
59
  constructor(body: ErrorModelRead, response: ResponseMeta) {
86
- super("An unexpected error prevented the request from completing.", 500, body, response);
60
+ super("HTTP 413", 413, body, response);
87
61
  }
88
62
  }
89
63
 
90
- /**
91
- * Unexpected error.
92
- * Raised for "default" responses.
93
- */
64
+ /** Raised for "default" responses. */
94
65
  export class ApiResponseError extends ApiError<number, ErrorModelRead> {
95
66
  constructor(body: ErrorModelRead, response: ResponseMeta) {
96
- super("Unexpected error.", response.status, body, response);
97
- }
98
- }
99
-
100
- /**
101
- * No such resource in this organization.
102
- * Raised for HTTP 404 responses.
103
- */
104
- export class NotFoundError extends ApiError<404, ErrorModelRead> {
105
- constructor(body: ErrorModelRead, response: ResponseMeta) {
106
- super("No such resource in this organization.", 404, body, response);
107
- }
108
- }
109
-
110
- /**
111
- * The plan does not include another project or the requested target configuration.
112
- * Raised for HTTP 402 responses.
113
- */
114
- export class PaymentRequiredError extends ApiError<402, ErrorModelRead> {
115
- constructor(body: ErrorModelRead, response: ResponseMeta) {
116
- super("The plan does not include another project or the requested target configuration.", 402, body, response);
117
- }
118
- }
119
-
120
- /**
121
- * The resource changed since the ETag supplied in If-Match. No write was applied.
122
- * Raised for HTTP 412 responses.
123
- */
124
- export class PreconditionFailedError extends ApiError<412, ErrorModelRead> {
125
- constructor(body: ErrorModelRead, response: ResponseMeta) {
126
- super("The resource changed since the ETag supplied in If-Match. No write was applied.", 412, body, response);
127
- }
128
- }
129
-
130
- /**
131
- * Dependent work failed while completing the request.
132
- * Raised for HTTP 502 responses.
133
- */
134
- export class BadGatewayError extends ApiError<502, ErrorModelRead> {
135
- constructor(body: ErrorModelRead, response: ResponseMeta) {
136
- super("Dependent work failed while completing the request.", 502, body, response);
67
+ super("HTTP " + response.status, response.status, body, response);
137
68
  }
138
69
  }
package/src/fields.ts ADDED
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Result projection shared by the generated CLI (--fields) and MCP server
3
+ * (the fields argument). Generated by Typeship — https://typeship.dev
4
+ */
5
+
6
+ /** Keep only `paths` of a value: arrays item by item, objects by dotted
7
+ * path, including paths through arrays (`items.id` keeps each item's id);
8
+ * scalars untouched. */
9
+ export function projectFields(value: unknown, paths: string[][] | null): unknown {
10
+ if (paths === null) return value;
11
+ if (Array.isArray(value)) return value.map((v) => projectFields(v, paths));
12
+ if (value === null || typeof value !== "object") return value;
13
+ const groups = new Map<string, string[][]>();
14
+ for (const [key, ...rest] of paths) {
15
+ if (key === undefined) continue;
16
+ const group = groups.get(key);
17
+ if (group) group.push(rest); else groups.set(key, [rest]);
18
+ }
19
+ const out: Record<string, unknown> = {};
20
+ for (const [key, rests] of groups) {
21
+ const child = (value as Record<string, unknown>)[key];
22
+ if (child === undefined) continue;
23
+ if (rests.some((rest) => rest.length === 0)) out[key] = child;
24
+ else if (child !== null && typeof child === "object") out[key] = projectFields(child, rests);
25
+ }
26
+ return out;
27
+ }
28
+
29
+ /** A requested path that selected nothing, with the keys that exist where
30
+ * it stopped matching. */
31
+ export interface UnmatchedField {
32
+ path: string;
33
+ /** Keys present at the level where the path failed, first 40. */
34
+ available: string[];
35
+ /** The path segment that is not a key there, or null when the path runs
36
+ * into a scalar or the result is not an object. */
37
+ missing: string | null;
38
+ }
39
+
40
+ /**
41
+ * The paths that match nothing in any item of `value`. A path matches when
42
+ * at least one item has it; reaching null or an empty array counts as a
43
+ * match, because the path may be right and the values merely empty. An
44
+ * empty result matches everything.
45
+ *
46
+ * `schema` is the JSON Schema of `value` (the operation's declared
47
+ * response, or its item schema for a list). A key the schema declares may
48
+ * be absent from every item, as optional fields are: the path matches, and
49
+ * projects to nothing. What the schema cannot vouch for (no schema, an
50
+ * object without properties, a key it does not list) still has to be in
51
+ * the data.
52
+ */
53
+ export function unmatchedFields(value: unknown, paths: string[][], schema?: unknown): UnmatchedField[] {
54
+ const out: UnmatchedField[] = [];
55
+ for (const path of paths) {
56
+ const miss = { depth: -1, keys: new Set<string>(), segment: null as string | null };
57
+ if (!pathMatches(value, path, 0, miss, schema)) {
58
+ out.push({ path: path.join("."), available: [...miss.keys].slice(0, 40), missing: miss.segment });
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ type Miss = { depth: number; keys: Set<string>; segment: string | null };
65
+
66
+ function pathMatches(value: unknown, path: string[], index: number, miss: Miss, schema: unknown): boolean {
67
+ if (value === null || value === undefined) return true;
68
+ if (Array.isArray(value)) {
69
+ if (value.length === 0) return true;
70
+ const items = arrayItems(schema);
71
+ let any = false;
72
+ for (const item of value) if (pathMatches(item, path, index, miss, items)) any = true;
73
+ return any;
74
+ }
75
+ if (index === path.length) return true;
76
+ if (typeof value !== "object") {
77
+ if (miss.depth < index) { miss.depth = index; miss.keys = new Set(); miss.segment = null; }
78
+ return false;
79
+ }
80
+ const key = path[index]!;
81
+ const record = value as Record<string, unknown>;
82
+ if (!Object.hasOwn(record, key)) {
83
+ const declared = declaredProperty(schema, key);
84
+ if (declared !== undefined) return declaredPath(declared, path, index + 1, miss);
85
+ if (miss.depth < index) { miss.depth = index; miss.keys = new Set(); miss.segment = key; }
86
+ if (miss.depth === index) for (const k of Object.keys(record)) miss.keys.add(k);
87
+ return false;
88
+ }
89
+ return pathMatches(record[key], path, index + 1, miss, declaredProperty(schema, key));
90
+ }
91
+
92
+ /** The rest of a path below a declared key no item has: nothing in the data
93
+ * can contradict it, so it matches unless the schema lists that level's
94
+ * keys and the next segment is not one of them. */
95
+ function declaredPath(schema: unknown, path: string[], index: number, miss: Miss): boolean {
96
+ if (index === path.length) return true;
97
+ const key = path[index]!;
98
+ const declared = declaredProperty(schema, key);
99
+ if (declared !== undefined) return declaredPath(declared, path, index + 1, miss);
100
+ const listed = listedProperties(schema);
101
+ if (listed === null) return true;
102
+ if (miss.depth < index) { miss.depth = index; miss.keys = new Set(); miss.segment = key; }
103
+ if (miss.depth === index) for (const k of listed) miss.keys.add(k);
104
+ return false;
105
+ }
106
+
107
+ /** A schema with its allOf, anyOf and oneOf branches flattened, arrays
108
+ * seen through to their items. */
109
+ function variants(schema: unknown, out: Record<string, unknown>[] = []): Record<string, unknown>[] {
110
+ if (schema === null || typeof schema !== "object" || Array.isArray(schema)) return out;
111
+ const node = schema as Record<string, unknown>;
112
+ out.push(node);
113
+ for (const key of ["allOf", "anyOf", "oneOf"]) {
114
+ const branches = node[key];
115
+ if (Array.isArray(branches)) for (const branch of branches) variants(branch, out);
116
+ }
117
+ return out;
118
+ }
119
+
120
+ function arrayItems(schema: unknown): unknown {
121
+ const items = variants(schema).map((node) => node.items).filter((item) => item !== undefined && item !== null);
122
+ return items.length === 0 ? undefined : items.length === 1 ? items[0] : { anyOf: items };
123
+ }
124
+
125
+ /** The schema of `key` when some branch declares it, else undefined. */
126
+ function declaredProperty(schema: unknown, key: string): unknown {
127
+ const found = variants(schema).flatMap((node) => {
128
+ const properties = node.properties as Record<string, unknown> | undefined;
129
+ return properties && typeof properties === "object" && Object.hasOwn(properties, key) ? [properties[key] ?? {}] : [];
130
+ });
131
+ return found.length === 0 ? undefined : found.length === 1 ? found[0] : { anyOf: found };
132
+ }
133
+
134
+ /** Every key the schema declares, or null when it cannot say which keys
135
+ * exist: no schema, an object without properties, or one that admits more. */
136
+ function listedProperties(schema: unknown): string[] | null {
137
+ const nodes = variants(schema);
138
+ const keys: string[] = [];
139
+ let closed = false;
140
+ for (const node of nodes) {
141
+ const properties = node.properties as Record<string, unknown> | undefined;
142
+ if (properties && typeof properties === "object") {
143
+ closed = true;
144
+ keys.push(...Object.keys(properties));
145
+ }
146
+ if (node.additionalProperties !== undefined && node.additionalProperties !== false) return null;
147
+ if (node.patternProperties !== undefined) return null;
148
+ // Tool schemas keep the first eight variants of a union; more may exist.
149
+ if (Array.isArray(node.anyOf) && node.anyOf.length >= 8) return null;
150
+ }
151
+ // A branch that is an object without listed properties is open.
152
+ if (nodes.some((node) => node.properties === undefined && (node.type === "object" || (Array.isArray(node.type) && node.type.includes("object"))))) return null;
153
+ return closed ? [...new Set(keys)] : null;
154
+ }
155
+
156
+ /** The error message: the call went through, and for each unmatched path,
157
+ * what exists instead. */
158
+ export function unmatchedFieldsMessage(unmatched: UnmatchedField[], perItem: boolean): string {
159
+ return "The API call succeeded, but " + unmatched.map((u, index) => {
160
+ const where = perItem ? " in any item" : " in the result";
161
+ const why = u.missing !== null
162
+ ? " (\"" + u.missing + "\" is not a key there)"
163
+ : " (it runs into a value that is not an object)";
164
+ const keys = u.available.length > 0 ? " Available keys: " + u.available.join(", ") + "." : "";
165
+ return (index === 0 ? "the" : "The") + " field path \"" + u.path + "\" matched nothing" + where + why + "." + keys;
166
+ }).join(" ");
167
+ }