@typeship-ax/cli 0.6.0 → 0.9.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.
Files changed (121) hide show
  1. package/AGENTS.md +31 -0
  2. package/README.md +26 -9
  3. package/api.json +6861 -3083
  4. package/api.md +508 -277
  5. package/dist/api-identity.d.ts +40 -0
  6. package/dist/api-identity.d.ts.map +1 -0
  7. package/dist/api-identity.js +128 -0
  8. package/dist/auth-profiles.d.ts +30 -0
  9. package/dist/auth-profiles.d.ts.map +1 -0
  10. package/dist/auth-profiles.js +138 -0
  11. package/dist/cli-agent.d.ts +9 -1
  12. package/dist/cli-agent.d.ts.map +1 -1
  13. package/dist/cli-agent.js +26 -9
  14. package/dist/cli.js +600 -582
  15. package/dist/console-login-check.d.ts +21 -0
  16. package/dist/console-login-check.d.ts.map +1 -0
  17. package/dist/console-login-check.js +107 -0
  18. package/dist/console-login-contract.d.ts +45 -0
  19. package/dist/console-login-contract.d.ts.map +1 -0
  20. package/dist/console-login-contract.js +40 -0
  21. package/dist/core/http.d.ts +21 -92
  22. package/dist/core/http.d.ts.map +1 -1
  23. package/dist/core/http.js +143 -221
  24. package/dist/core/pagination.d.ts.map +1 -1
  25. package/dist/core/pagination.js +6 -34
  26. package/dist/credential-storage.d.ts +24 -0
  27. package/dist/credential-storage.d.ts.map +1 -0
  28. package/dist/credential-storage.js +207 -0
  29. package/dist/dates.d.ts +0 -2
  30. package/dist/dates.d.ts.map +1 -1
  31. package/dist/dates.js +0 -1
  32. package/dist/docs.d.ts +36 -0
  33. package/dist/docs.d.ts.map +1 -0
  34. package/dist/docs.js +258 -0
  35. package/dist/errors.d.ts +42 -34
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/errors.js +30 -20
  38. package/dist/index.d.ts +27 -12
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +40 -14
  41. package/dist/named-credentials.d.ts +21 -0
  42. package/dist/named-credentials.d.ts.map +1 -0
  43. package/dist/named-credentials.js +86 -0
  44. package/dist/oauth-login.d.ts +39 -0
  45. package/dist/oauth-login.d.ts.map +1 -0
  46. package/dist/oauth-login.js +171 -0
  47. package/dist/oauth-request.d.ts +21 -0
  48. package/dist/oauth-request.d.ts.map +1 -0
  49. package/dist/oauth-request.js +119 -0
  50. package/dist/oauth-session.d.ts +106 -0
  51. package/dist/oauth-session.d.ts.map +1 -0
  52. package/dist/oauth-session.js +244 -0
  53. package/dist/ops.d.ts +18 -0
  54. package/dist/ops.d.ts.map +1 -1
  55. package/dist/ops.js +31 -17
  56. package/dist/polling-login.d.ts +57 -0
  57. package/dist/polling-login.d.ts.map +1 -0
  58. package/dist/polling-login.js +204 -0
  59. package/dist/resources/account.d.ts +4 -4
  60. package/dist/resources/account.d.ts.map +1 -1
  61. package/dist/resources/account.js +1 -0
  62. package/dist/resources/api-keys.d.ts +13 -8
  63. package/dist/resources/api-keys.d.ts.map +1 -1
  64. package/dist/resources/api-keys.js +5 -1
  65. package/dist/resources/definition-revisions.d.ts +58 -0
  66. package/dist/resources/definition-revisions.d.ts.map +1 -0
  67. package/dist/resources/definition-revisions.js +114 -0
  68. package/dist/resources/definitions.d.ts +35 -0
  69. package/dist/resources/definitions.d.ts.map +1 -0
  70. package/dist/resources/definitions.js +60 -0
  71. package/dist/resources/generate.d.ts +18 -7
  72. package/dist/resources/generate.d.ts.map +1 -1
  73. package/dist/resources/generate.js +13 -5
  74. package/dist/resources/generations.d.ts +6 -6
  75. package/dist/resources/generations.d.ts.map +1 -1
  76. package/dist/resources/generations.js +3 -1
  77. package/dist/resources/projects.d.ts +111 -35
  78. package/dist/resources/projects.d.ts.map +1 -1
  79. package/dist/resources/projects.js +125 -15
  80. package/dist/resources/targets.d.ts +97 -0
  81. package/dist/resources/targets.d.ts.map +1 -0
  82. package/dist/resources/targets.js +197 -0
  83. package/dist/schemas.d.ts.map +1 -1
  84. package/dist/schemas.js +135 -62
  85. package/dist/types.d.ts +2072 -267
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/types.js +20 -3
  88. package/package.json +2 -1
  89. package/src/api-identity.ts +98 -0
  90. package/src/auth-profiles.ts +114 -0
  91. package/src/cli-agent.ts +28 -11
  92. package/src/cli.ts +528 -560
  93. package/src/console-login-check.ts +88 -0
  94. package/src/console-login-contract.ts +65 -0
  95. package/src/core/http.ts +156 -305
  96. package/src/core/pagination.ts +6 -30
  97. package/src/credential-storage.ts +183 -0
  98. package/src/dates.ts +0 -1
  99. package/src/docs.ts +239 -0
  100. package/src/errors.ts +52 -41
  101. package/src/index.ts +49 -14
  102. package/src/named-credentials.ts +74 -0
  103. package/src/oauth-login.ts +184 -0
  104. package/src/oauth-request.ts +90 -0
  105. package/src/oauth-session.ts +258 -0
  106. package/src/ops.ts +56 -17
  107. package/src/polling-login.ts +165 -0
  108. package/src/resources/account.ts +6 -3
  109. package/src/resources/api-keys.ts +27 -7
  110. package/src/resources/definition-revisions.ts +207 -0
  111. package/src/resources/definitions.ts +122 -0
  112. package/src/resources/generate.ts +29 -6
  113. package/src/resources/generations.ts +9 -4
  114. package/src/resources/projects.ts +274 -41
  115. package/src/resources/targets.ts +378 -0
  116. package/src/schemas.ts +135 -62
  117. package/src/types.ts +2273 -322
  118. package/dist/resources/spec-revisions.d.ts +0 -47
  119. package/dist/resources/spec-revisions.d.ts.map +0 -1
  120. package/dist/resources/spec-revisions.js +0 -90
  121. package/src/resources/spec-revisions.ts +0 -150
package/dist/core/http.js CHANGED
@@ -29,43 +29,21 @@ export class UnexpectedApiError extends ApiError {
29
29
  super("Unexpected HTTP status " + status, status, body, response);
30
30
  }
31
31
  }
32
- /** A 200 response whose GraphQL payload carried errors. */
33
- export class GraphQLRequestError extends Error {
34
- /** The raw errors array from the GraphQL response. */
35
- errors;
32
+ /** A successful response declared JSON but carried a body that could not be parsed. */
33
+ export class ResponseParseError extends Error {
34
+ status;
35
+ body;
36
36
  response;
37
- constructor(errors, response) {
38
- const first = errors[0]?.message;
39
- super(first ?? "GraphQL request returned errors");
40
- this.name = "GraphQLRequestError";
41
- this.errors = errors;
37
+ cause;
38
+ constructor(body, response, cause) {
39
+ super("HTTP " + response.status + " response body was not valid JSON");
40
+ this.name = "ResponseParseError";
41
+ this.status = response.status;
42
+ this.body = body;
42
43
  this.response = response;
44
+ this.cause = cause;
43
45
  }
44
46
  }
45
- /** Serialize a selection object to a GraphQL selection set. Strings pass
46
- * through untouched. */
47
- export function selectionToString(selection) {
48
- if (typeof selection === "string")
49
- return selection;
50
- const parts = [];
51
- for (const [key, value] of Object.entries(selection)) {
52
- if (!value)
53
- continue;
54
- if (key === "on" && typeof value === "object") {
55
- for (const [typeName, sub] of Object.entries(value)) {
56
- if (sub && typeof sub === "object")
57
- parts.push("... on " + typeName + " " + selectionToString(sub));
58
- }
59
- }
60
- else if (value === true) {
61
- parts.push(key);
62
- }
63
- else if (typeof value === "object") {
64
- parts.push(key + " " + selectionToString(value));
65
- }
66
- }
67
- return "{ " + (parts.length > 0 ? parts.join(" ") : "__typename") + " }";
68
- }
69
47
  /** Request or response data did not match the spec's schema (opt-in via the
70
48
  * client's validate option). Never thrown: returned as the error side of
71
49
  * ApiResult, like every other failure. */
@@ -211,7 +189,7 @@ function transportFailureMessage(method, url, cause) {
211
189
  }
212
190
  return method + " " + url + " failed: " + detail;
213
191
  }
214
- /** The request never produced an HTTP response (network failure, timeout, abort). */
192
+ /** The request failed before a complete HTTP response arrived (network failure, timeout, abort, or truncated body). */
215
193
  export class TransportError extends Error {
216
194
  cause;
217
195
  constructor(message, cause) {
@@ -220,6 +198,33 @@ export class TransportError extends Error {
220
198
  this.cause = cause;
221
199
  }
222
200
  }
201
+ /** Resolve one complete alternative in spec order. Missing or incompatible
202
+ * combinations contribute no partial credentials. Anonymous operations send
203
+ * no generated credentials; explicit headers/hooks remain application owned. */
204
+ function selectSecurity(requirements, credentials) {
205
+ for (const requirement of requirements) {
206
+ const names = Object.keys(requirement);
207
+ if (!names.length || names.some((name) => !Object.hasOwn(credentials, name)))
208
+ continue;
209
+ const selected = { headers: Object.create(null), query: Object.create(null) };
210
+ const destinations = new Set();
211
+ let conflict = false;
212
+ for (const name of names) {
213
+ for (const location of ["headers", "query"]) {
214
+ for (const [wire, value] of Object.entries(credentials[name][location] ?? {})) {
215
+ const destination = location + ":" + (location === "headers" ? wire.toLowerCase() : wire);
216
+ if (destinations.has(destination))
217
+ conflict = true;
218
+ destinations.add(destination);
219
+ selected[location][wire] = value;
220
+ }
221
+ }
222
+ }
223
+ if (!conflict)
224
+ return selected;
225
+ }
226
+ return {};
227
+ }
223
228
  const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
224
229
  /** Credential-bearing headers fetch itself drops when a redirect crosses to
225
230
  * another origin. A spec's own API-key headers join them below. */
@@ -269,8 +274,13 @@ export class HttpCore {
269
274
  const policy = { ...this.config.retry, ...req.retry };
270
275
  const maxRetries = req.options?.maxRetries ?? policy.maxRetries ?? this.config.maxRetries;
271
276
  const timeoutMs = req.options?.timeoutMs ?? this.config.timeoutMs;
272
- const retryAllowed = req.idempotent === true || req.method === "GET" || policy.retryNonIdempotent === true;
273
- const retryableStatuses = policy.statuses ? new Set(policy.statuses) : RETRYABLE_STATUSES;
277
+ const retryAllowed = req.idempotent === true ||
278
+ req.method === "GET" ||
279
+ req.idempotencyKey !== undefined ||
280
+ policy.retryNonIdempotent === true;
281
+ const retryableStatuses = policy.statuses
282
+ ? new Set(policy.statuses)
283
+ : RETRYABLE_STATUSES;
274
284
  // One key per logical call, reused on every retry — that's the point
275
285
  // of idempotency keys.
276
286
  const autoIdempotencyKey = req.idempotencyKey !== undefined &&
@@ -299,16 +309,19 @@ export class HttpCore {
299
309
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
300
310
  let response;
301
311
  const attemptStarted = Date.now();
312
+ const emitResponseDebug = (value, body) => this.config.debug?.({
313
+ method: req.method,
314
+ path: req.path,
315
+ status: value.status,
316
+ durationMs: Date.now() - attemptStarted,
317
+ attempt: attempt + 1,
318
+ requestId: requestIdFromBody(body) ??
319
+ value.headers.get("request-id") ??
320
+ value.headers.get("x-request-id") ??
321
+ undefined,
322
+ });
302
323
  try {
303
324
  response = await this.send(req, timeoutMs, attempt, autoIdempotencyKey);
304
- this.config.debug?.({
305
- method: req.method,
306
- path: req.path,
307
- status: response.status,
308
- durationMs: Date.now() - attemptStarted,
309
- attempt: attempt + 1,
310
- requestId: response.headers.get("x-request-id") ?? response.headers.get("request-id") ?? undefined,
311
- });
312
325
  }
313
326
  catch (cause) {
314
327
  lastError = cause;
@@ -328,21 +341,24 @@ export class HttpCore {
328
341
  return { ok: false, error };
329
342
  }
330
343
  if (response.ok) {
331
- if (req.stream) {
332
- return { ok: true, data: sseEvents(response), response: meta(response) };
333
- }
334
344
  let data;
335
345
  try {
336
346
  data = (await parseBody(response, req.method));
337
347
  }
338
348
  catch (cause) {
339
- const error = new TransportError("The response body read was aborted before completing", cause);
349
+ const parseError = cause instanceof ResponseParseError ? cause : undefined;
350
+ emitResponseDebug(response, parseError?.body);
351
+ const error = (parseError ?? new TransportError("The response body read failed before completing", cause));
340
352
  await this.config.onError?.(error, { method: req.method, path: req.path });
341
- return { ok: false, error, response: meta(response) };
353
+ return { ok: false, error, response: parseError?.response ?? meta(response) };
342
354
  }
343
- if (opSchemas?.res && this.config.validate.responses && data !== undefined) {
355
+ emitResponseDebug(response, data);
356
+ const responseMeta = meta(response, data);
357
+ let responseData = data;
358
+ let shouldValidateResponse = responseData !== undefined;
359
+ if (opSchemas?.res && this.config.validate.responses && shouldValidateResponse) {
344
360
  const violations = [];
345
- validateAgainstSchema(data, opSchemas.res, "response", violations, this.config.schemaDefs);
361
+ validateAgainstSchema(responseData, opSchemas.res, "response", violations, this.config.schemaDefs);
346
362
  if (violations.length > 0) {
347
363
  const validationError = new ValidationError("response", violations);
348
364
  if (this.config.validate.mode === "warn") {
@@ -351,28 +367,27 @@ export class HttpCore {
351
367
  else {
352
368
  const error = validationError;
353
369
  await this.config.onError?.(error, { method: req.method, path: req.path });
354
- return { ok: false, error, response: meta(response) };
370
+ return { ok: false, error, response: responseMeta };
355
371
  }
356
372
  }
357
373
  }
358
- if (req.graphqlField) {
359
- const payload = data;
360
- const responseMeta = meta(response);
361
- if (Array.isArray(payload?.errors) && payload.errors.length > 0) {
362
- const gqlError = new GraphQLRequestError(payload.errors, responseMeta);
363
- await this.config.onError?.(gqlError, { method: req.method, path: req.path });
364
- return { ok: false, error: gqlError, response: responseMeta };
365
- }
366
- return { ok: true, data: payload?.data?.[req.graphqlField], response: responseMeta };
367
- }
368
- return { ok: true, data, response: meta(response) };
374
+ return { ok: true, data, response: responseMeta };
369
375
  }
370
376
  // 429 is safe to retry regardless of idempotency; other retryable
371
377
  // statuses only when the verb is idempotent.
372
378
  const retryableStatus = retryableStatuses.has(response.status) &&
373
379
  (retryAllowed || response.status === 429);
374
380
  if (attempt < maxRetries && retryableStatus) {
375
- await sleep(retryAfterMs(response) ?? backoff(attempt, policy));
381
+ const delay = retryAfterMs(response) ?? backoff(attempt, policy);
382
+ let retryBody;
383
+ try {
384
+ retryBody = await parseBody(response, req.method);
385
+ }
386
+ catch {
387
+ retryBody = undefined;
388
+ }
389
+ emitResponseDebug(response, retryBody);
390
+ await sleep(delay);
376
391
  continue;
377
392
  }
378
393
  let body;
@@ -382,7 +397,8 @@ export class HttpCore {
382
397
  catch {
383
398
  body = undefined; // error responses keep their status even if the body read aborts
384
399
  }
385
- const responseMeta = meta(response);
400
+ emitResponseDebug(response, body);
401
+ const responseMeta = meta(response, body);
386
402
  const Ctor = req.errors?.[String(response.status)] ??
387
403
  req.errors?.[String(Math.floor(response.status / 100)) + "XX"] ??
388
404
  req.errors?.["default"];
@@ -398,8 +414,9 @@ export class HttpCore {
398
414
  return { ok: false, error };
399
415
  }
400
416
  async send(req, timeoutMs, attempt, autoIdempotencyKey) {
417
+ const selected = req.security && this.config.credentials ? selectSecurity(req.security, this.config.credentials) : {};
401
418
  const headers = {};
402
- for (const [k, v] of Object.entries(this.config.headers)) {
419
+ for (const [k, v] of Object.entries({ ...this.config.headers, ...selected.headers })) {
403
420
  headers[k] = await resolveAuthValue(v);
404
421
  }
405
422
  if (req.idempotencyKey && autoIdempotencyKey) {
@@ -416,31 +433,19 @@ export class HttpCore {
416
433
  }
417
434
  const context = {
418
435
  method: req.method,
419
- url: await this.buildUrl(req),
436
+ url: await this.buildUrl(req, selected.query),
420
437
  headers,
421
438
  attempt,
422
439
  };
423
440
  await this.config.onRequest?.(context);
424
- // Streaming responses are exempt from the attempt timeout once headers
425
- // arrive (an event stream may stay open far longer than timeoutMs);
426
- // everything else keeps the timeout armed through the body read, so a
427
- // stalled body aborts instead of hanging.
428
441
  const signals = [];
429
- let clearStreamTimeout;
430
- if (req.stream) {
431
- const headersTimeout = new AbortController();
432
- const timer = setTimeout(() => headersTimeout.abort(new DOMException("Timed out waiting for response headers", "TimeoutError")), timeoutMs);
433
- timer.unref?.();
434
- clearStreamTimeout = () => clearTimeout(timer);
435
- signals.push(headersTimeout.signal);
436
- }
437
- else {
442
+ {
438
443
  signals.push(AbortSignal.timeout(timeoutMs));
439
444
  }
440
445
  if (req.options?.signal)
441
446
  signals.push(req.options.signal);
442
447
  try {
443
- const signal = AbortSignal.any(signals);
448
+ const signal = composeSignals(signals);
444
449
  const response = this.manualRedirects
445
450
  ? await this.followRedirects(context, body, signal)
446
451
  : await this.config.fetch(context.url, {
@@ -453,7 +458,6 @@ export class HttpCore {
453
458
  return response;
454
459
  }
455
460
  finally {
456
- clearStreamTimeout?.();
457
461
  }
458
462
  }
459
463
  /**
@@ -514,7 +518,7 @@ export class HttpCore {
514
518
  origin = target.origin;
515
519
  }
516
520
  }
517
- async buildUrl(req) {
521
+ async buildUrl(req, authQuery) {
518
522
  const base = this.config.baseUrl.replace(/\/+$/, "");
519
523
  const url = new URL(base + req.path);
520
524
  for (const [k, v] of Object.entries(req.query ?? {})) {
@@ -529,7 +533,7 @@ export class HttpCore {
529
533
  appendDeep(url.searchParams, k, v);
530
534
  }
531
535
  }
532
- for (const [k, v] of Object.entries(this.config.query)) {
536
+ for (const [k, v] of Object.entries({ ...this.config.query, ...authQuery })) {
533
537
  url.searchParams.append(k, await resolveAuthValue(v));
534
538
  }
535
539
  return url.toString();
@@ -538,64 +542,6 @@ export class HttpCore {
538
542
  async function resolveAuthValue(value) {
539
543
  return typeof value === "function" ? await value() : value;
540
544
  }
541
- /** Parse a text/event-stream body into SseEvents, lazily. */
542
- async function* sseEvents(response) {
543
- if (!response.body)
544
- return;
545
- const reader = response.body.getReader();
546
- const decoder = new TextDecoder();
547
- let buffer = "";
548
- let dataLines = [];
549
- let eventName;
550
- let eventId;
551
- const flush = () => {
552
- if (dataLines.length === 0)
553
- return undefined;
554
- const event = { data: dataLines.join("\n") };
555
- if (eventName !== undefined)
556
- event.event = eventName;
557
- if (eventId !== undefined)
558
- event.id = eventId;
559
- dataLines = [];
560
- eventName = undefined;
561
- return event;
562
- };
563
- try {
564
- while (true) {
565
- const { done, value } = await reader.read();
566
- if (done)
567
- break;
568
- buffer += decoder.decode(value, { stream: true });
569
- let newline;
570
- while ((newline = buffer.indexOf("\n")) !== -1) {
571
- const hasCr = newline > 0 && buffer[newline - 1] === "\r";
572
- const line = buffer.slice(0, hasCr ? newline - 1 : newline);
573
- buffer = buffer.slice(newline + 1);
574
- if (line === "") {
575
- const event = flush();
576
- if (event)
577
- yield event;
578
- }
579
- else if (line.startsWith("data:")) {
580
- dataLines.push(line.slice(5).replace(/^ /, ""));
581
- }
582
- else if (line.startsWith("event:")) {
583
- eventName = line.slice(6).replace(/^ /, "");
584
- }
585
- else if (line.startsWith("id:")) {
586
- eventId = line.slice(3).replace(/^ /, "");
587
- }
588
- // comments (":") and "retry:" are intentionally ignored
589
- }
590
- }
591
- const last = flush();
592
- if (last)
593
- yield last;
594
- }
595
- finally {
596
- reader.releaseLock();
597
- }
598
- }
599
545
  /** Default rendering for debug events (the boolean debug:true sink). */
600
546
  export function formatDebugEvent(name, event) {
601
547
  return name + " " + event.method + " " + event.path +
@@ -612,54 +558,6 @@ export function bearerAuth(token) {
612
558
  }
613
559
  return "Bearer " + token;
614
560
  }
615
- /**
616
- * OAuth2 client-credentials token source: fetches from the token URL,
617
- * caches until expiry (60s early refresh), and shares one in-flight
618
- * request across concurrent callers. Returned function plugs in as an
619
- * Authorization AuthValue, resolved before every attempt.
620
- */
621
- export function oauthClientCredentials(config) {
622
- let token;
623
- let expiresAt = 0;
624
- let inflight;
625
- const fetchImpl = config.fetchImpl ?? fetch;
626
- async function fetchToken() {
627
- const params = new URLSearchParams({ grant_type: "client_credentials" });
628
- if (config.scopes !== undefined && config.scopes.length > 0) {
629
- params.set("scope", config.scopes.join(" "));
630
- }
631
- for (const [key, value] of Object.entries(config.tokenParams ?? {})) {
632
- params.set(key, value);
633
- }
634
- const headers = {
635
- "Content-Type": "application/x-www-form-urlencoded",
636
- Accept: "application/json",
637
- };
638
- if (config.authMethod === "basic") {
639
- headers["Authorization"] = "Basic " + toBase64(config.clientId + ":" + config.clientSecret);
640
- }
641
- else {
642
- params.set("client_id", config.clientId);
643
- params.set("client_secret", config.clientSecret);
644
- }
645
- const response = await fetchImpl(config.tokenUrl, { method: "POST", headers, body: params.toString() });
646
- const body = (await response.json().catch(() => null));
647
- if (!response.ok || typeof body?.access_token !== "string") {
648
- throw new TransportError("OAuth token request failed (HTTP " + response.status + (body?.error ? ": " + body.error : "") + ")", body);
649
- }
650
- token = body.access_token;
651
- expiresAt = body.expires_in !== undefined
652
- ? Date.now() + body.expires_in * 1000 - 60_000
653
- : Number.MAX_SAFE_INTEGER;
654
- return token;
655
- }
656
- return async () => {
657
- if (token !== undefined && Date.now() < expiresAt)
658
- return "Bearer " + token;
659
- inflight ??= fetchToken().finally(() => { inflight = undefined; });
660
- return "Bearer " + (await inflight);
661
- };
662
- }
663
561
  /**
664
562
  * Bracket-style deep encoding shared by query strings and form bodies:
665
563
  * { created: { gte: 5 } } -> created[gte]=5, { items: [{ id: "x" }] } ->
@@ -686,37 +584,28 @@ function serializeBody(req) {
686
584
  switch (req.bodyKind ?? "json") {
687
585
  case "json":
688
586
  return { body: JSON.stringify(req.body), contentType: "application/json" };
689
- case "form": {
690
- const params = new URLSearchParams();
691
- for (const [k, v] of Object.entries(req.body)) {
692
- appendDeep(params, k, v);
693
- }
694
- // URLSearchParams sets its own content type with the charset suffix.
695
- return { body: params };
696
- }
697
- case "multipart": {
698
- const form = new FormData();
699
- for (const [k, v] of Object.entries(req.body)) {
700
- if (v === undefined || v === null)
701
- continue;
702
- form.append(k, v instanceof Blob ? v : typeof v === "object" ? JSON.stringify(v) : String(v));
703
- }
704
- // Let fetch set the boundary header.
705
- return { body: form };
706
- }
707
- case "text":
708
- return { body: String(req.body), contentType: "text/plain" };
709
- case "binary":
710
- return { body: req.body };
711
587
  }
588
+ throw new Error("Unsupported request body kind: " + String(req.bodyKind));
712
589
  }
713
590
  async function parseBody(response, method) {
714
591
  if (method === "HEAD" || response.status === 204 || response.status === 205)
715
592
  return undefined;
716
- const contentType = response.headers.get("content-type") ?? "";
593
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
594
+ const mediaType = contentType.split(";", 1)[0].trim();
595
+ const slash = mediaType.indexOf("/");
596
+ const subtype = slash === -1 ? "" : mediaType.slice(slash + 1);
717
597
  try {
718
- if (contentType.includes("json"))
719
- return await response.json();
598
+ if (subtype === "json" || subtype.endsWith("+json")) {
599
+ const body = await response.text();
600
+ if (body.length === 0)
601
+ return undefined;
602
+ try {
603
+ return JSON.parse(body);
604
+ }
605
+ catch (cause) {
606
+ throw new ResponseParseError(body, meta(response, body), cause);
607
+ }
608
+ }
720
609
  if (contentType.startsWith("text/"))
721
610
  return await response.text();
722
611
  if (response.body === null)
@@ -724,22 +613,55 @@ async function parseBody(response, method) {
724
613
  return await response.blob();
725
614
  }
726
615
  catch (cause) {
727
- // A timed-out or aborted body read is a transport failure, not an
728
- // empty body — surface it instead of faking success.
729
- if (cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError"))
616
+ if (cause instanceof ResponseParseError)
730
617
  throw cause;
731
- return undefined;
618
+ // A timed-out, aborted, or prematurely terminated body is a transport
619
+ // failure, not an empty body — surface it instead of faking success.
620
+ throw cause;
732
621
  }
733
622
  }
734
- function meta(response) {
623
+ function requestIdFromBody(body) {
624
+ if (!body || typeof body !== "object" || Array.isArray(body))
625
+ return undefined;
626
+ const value = body.request_id ?? body.requestId;
627
+ return typeof value === "string" && value.length > 0 ? value : undefined;
628
+ }
629
+ function meta(response, body) {
735
630
  return {
736
631
  status: response.status,
737
632
  headers: response.headers,
738
- requestId: response.headers.get("x-request-id") ??
633
+ ...(body !== undefined ? { rawBody: body } : {}),
634
+ requestId: requestIdFromBody(body) ??
739
635
  response.headers.get("request-id") ??
636
+ response.headers.get("x-request-id") ??
740
637
  undefined,
741
638
  };
742
639
  }
640
+ /** AbortSignal.any reached Node in 18.17. Keep the package's Node 18 floor
641
+ * honest for earlier 18.x releases and for web runtimes without it. */
642
+ function composeSignals(signals) {
643
+ const nativeAny = AbortSignal.any;
644
+ if (nativeAny)
645
+ return nativeAny.call(AbortSignal, signals);
646
+ const controller = new AbortController();
647
+ const listeners = new Map();
648
+ const abortFrom = (signal) => {
649
+ for (const [item, listener] of listeners)
650
+ item.removeEventListener("abort", listener);
651
+ listeners.clear();
652
+ controller.abort(signal.reason);
653
+ };
654
+ for (const signal of signals) {
655
+ if (signal.aborted) {
656
+ abortFrom(signal);
657
+ break;
658
+ }
659
+ const listener = () => abortFrom(signal);
660
+ listeners.set(signal, listener);
661
+ signal.addEventListener("abort", listener, { once: true });
662
+ }
663
+ return controller.signal;
664
+ }
743
665
  function retryAfterMs(response) {
744
666
  const header = response.headers.get("retry-after");
745
667
  if (!header)
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/core/pagination.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/core/pagination.ts"],"names":[],"mappings":"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"}
@@ -37,30 +37,12 @@ export class Page {
37
37
  const next = getPath(this.body, config.nextCursorField);
38
38
  if (next === undefined || next === null || next === "")
39
39
  return null;
40
- return { ...params, [config.cursorParam]: next };
41
- }
42
- case "cursorFromLastId": {
43
- if (items.length === 0)
44
- return null;
45
- const last = items[items.length - 1];
46
- const id = last?.[config.idField ?? "id"];
47
- if (id === undefined || id === null)
48
- return null;
49
- return { ...params, [config.cursorParam]: id };
50
- }
51
- case "page": {
52
- if (!this.looksLikeMore(items))
40
+ if (next === params[config.cursorParam])
53
41
  return null;
54
- const current = Number(params[config.pageParam] ?? 1);
55
- return { ...params, [config.pageParam]: current + 1 };
56
- }
57
- case "offset": {
58
- if (!this.looksLikeMore(items))
59
- return null;
60
- const current = Number(params[config.offsetParam] ?? 0);
61
- return { ...params, [config.offsetParam]: current + items.length };
42
+ return { ...params, [config.cursorParam]: next };
62
43
  }
63
44
  }
45
+ return null;
64
46
  }
65
47
  /** Fetch the next page, or null when this is the last one. Throws the typed error on failure. */
66
48
  async getNextPage() {
@@ -116,17 +98,9 @@ export class PagePromise {
116
98
  }
117
99
  }
118
100
  export function paginate(core, req, config) {
119
- const isGraphql = req.graphqlField !== undefined;
101
+ const isGraphql = false;
120
102
  const fetchPage = async (params) => {
121
- const nextReq = isGraphql
122
- ? {
123
- ...req,
124
- body: {
125
- ...req.body,
126
- variables: { ...(req.body?.variables ?? {}), ...params },
127
- },
128
- }
129
- : { ...req, query: params };
103
+ let nextReq = { ...req, query: params };
130
104
  const result = await core.request(nextReq);
131
105
  if (!result.ok)
132
106
  return result;
@@ -134,9 +108,7 @@ export function paginate(core, req, config) {
134
108
  return { ok: true, data: page, response: result.response };
135
109
  };
136
110
  const initial = {};
137
- const seed = isGraphql
138
- ? (req.body?.variables ?? {})
139
- : (req.query ?? {});
111
+ let seed = req.query ?? {};
140
112
  for (const [k, v] of Object.entries(seed)) {
141
113
  if (v !== undefined)
142
114
  initial[k] = v;
@@ -0,0 +1,24 @@
1
+ import { FileCredentialStore, type CredentialCodec } from "./oauth-session.js";
2
+ export interface NativeCommandResult {
3
+ status: number | null;
4
+ stdout: string;
5
+ stderr: string;
6
+ error?: unknown;
7
+ }
8
+ export type NativeCommand = (executable: string, args: string[], input?: string) => NativeCommandResult;
9
+ export interface CredentialKeyStore {
10
+ readonly name: string;
11
+ read(): Buffer | null;
12
+ write(key: Buffer): void;
13
+ /** Used only when explicitly removing this wrapping key, not ordinary logout. */
14
+ remove(): void;
15
+ }
16
+ /** A small wrapping key avoids OS item-size limits; session files are encrypted
17
+ * separately so refresh rotation can retain the atomic file/lock transaction. */
18
+ export declare function nativeCredentialKeyStore(path: string, platform?: NodeJS.Platform, run?: NativeCommand, environmentName?: string): CredentialKeyStore;
19
+ export declare function encryptedCredentialCodec(path: string, keyStore: CredentialKeyStore): CredentialCodec;
20
+ /** OS protection is the default. Plaintext storage is an explicit, separate
21
+ * store for environments where the owner accepts that tradeoff. Never migrate
22
+ * or fall back silently when an OS service is unavailable. */
23
+ export declare function createCredentialStore(directory: string, mode?: string, environmentName?: string): FileCredentialStore;
24
+ //# sourceMappingURL=credential-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-storage.d.ts","sourceRoot":"","sources":["../src/credential-storage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAA0B,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEvG,MAAM,WAAW,mBAAmB;IAAG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,mBAAmB,CAAC;AACxG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iFAAiF;IACjF,MAAM,IAAI,IAAI,CAAC;CAChB;AAkBD;iFACiF;AACjF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAAE,GAAG,GAAE,aAA6B,EAAE,eAAe,SAAqB,GAAG,kBAAkB,CAsGjM;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,eAAe,CAkCpG;AAED;;8DAE8D;AAC9D,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAAa,EAAE,eAAe,SAAqB,GAAG,mBAAmB,CAKvI"}