@shipstatic/types 2.5.0-beta.17 → 2.5.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -668,7 +668,8 @@ export declare class ShipError extends Error {
668
668
  * Routing:
669
669
  * - Already a `ShipError` → returned as-is (caller's intent preserved)
670
670
  * - `AbortError` → `ShipError.cancelled(...)`
671
- * - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
671
+ * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
672
+ * for what each runtime offers as evidence
672
673
  * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
673
674
  * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
674
675
  *
@@ -1093,6 +1094,21 @@ export interface StaticFile {
1093
1094
  }
1094
1095
  /** Default API URL if not otherwise configured. */
1095
1096
  export declare const DEFAULT_API = "https://api.shipstatic.com";
1097
+ /**
1098
+ * How long an anonymous deployment lives before it expires.
1099
+ *
1100
+ * The lifetime of the public tier, and one fact with several readers. The API
1101
+ * stamps a deployment's `expires` from it and gives a claim code exactly the
1102
+ * same window — a live site with a dead claim link is a coherence bug, so the
1103
+ * two are one constant rather than two that agree. Both MCP transports quote
1104
+ * the duration in prose an agent reads, and derive it from here rather than
1105
+ * writing it out, which they did in eight places until this export existed.
1106
+ *
1107
+ * Seconds, spelled in the name: this platform has both second- and
1108
+ * millisecond-valued durations, and the pair is only safe when each says which
1109
+ * it is.
1110
+ */
1111
+ export declare const PUBLIC_DEPLOYMENT_TTL_SECONDS: number;
1096
1112
  /**
1097
1113
  * Universal deploy input — the union of every shape the SDK accepts.
1098
1114
  *
@@ -1144,8 +1160,8 @@ export interface DeploymentUploadOptions {
1144
1160
  *
1145
1161
  * **Agents are the audience.** A human notices a duplicate; an automated
1146
1162
  * retry does not. Pick a key that identifies the ATTEMPT — a run id, a
1147
- * commit sha, a uuid minted before the first try — never one that varies
1148
- * per attempt, which would defeat the point.
1163
+ * commit sha, a uuid minted before the first try — never one minted fresh
1164
+ * on each retry, which would defeat the point.
1149
1165
  *
1150
1166
  * The replay is per-caller, and it stores successes only: a failed deploy
1151
1167
  * retries fresh under the same key.
package/dist/index.js CHANGED
@@ -237,6 +237,42 @@ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter((t
237
237
  * contract, and truncating a long validation message would be the bug.
238
238
  */
239
239
  const MAX_FOREIGN_MESSAGE_LENGTH = 200;
240
+ /**
241
+ * Did the runtime say the exchange never completed?
242
+ *
243
+ * WHATWG has `fetch` reject with a **TypeError** on network error, and undici,
244
+ * Chromium and Firefox comply. Bun does not: it rejects with a plain `Error`
245
+ * carrying a system `code` string. Captured 2026-08-05 (the capture script is
246
+ * in `tests/errors.test.ts`, "runtime failure shapes"):
247
+ *
248
+ * | failure | Node 22 / undici | Bun 1.3.14 |
249
+ * |---------------|---------------------------|----------------------------------------------|
250
+ * | refused | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
251
+ * | DNS failure | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
252
+ * | reset | `TypeError: fetch failed` | `Error` `code: 'ECONNRESET'` |
253
+ * | TLS rejected | `TypeError: fetch failed` | `Error` `code: 'UNKNOWN_CERTIFICATE_…ERROR'` |
254
+ *
255
+ * So the test is the **evidence, not a list of dialect strings**: a string
256
+ * `code` is a runtime naming a transport-level failure. An allowlist of codes
257
+ * was written first and rejected — the TLS row alone would mean enumerating
258
+ * BoringSSL's certificate table, and a code nobody guessed is precisely the bug
259
+ * this closes. Two kinds of error are deliberately NOT caught: ordinary JS
260
+ * faults carry no `code` at all, and a `DOMException`'s is a **number**, so
261
+ * aborts and timeouts fall through to their own arms.
262
+ *
263
+ * The accepted trade: a caller's `TokenProvider` that throws a coded error
264
+ * (`ENOENT` from a keychain read) is typed `Network` rather than `Api`. Both
265
+ * are wrong for it, `Network` is the cheaper wrong — it says "nothing was
266
+ * exchanged", which is true, where `Api` claims a server answered.
267
+ */
268
+ function isTransportFailure(cause) {
269
+ if (typeof cause.code === 'string')
270
+ return true;
271
+ // Spec runtimes put no code on the rejection itself. The message test is what
272
+ // keeps fetch's ARGUMENT errors out — `Failed to parse URL from …` is a
273
+ // caller's config mistake, not a transport failure.
274
+ return cause instanceof TypeError && cause.message.includes('fetch');
275
+ }
240
276
  /**
241
277
  * Simple unified error class for both API and SDK
242
278
  */
@@ -362,7 +398,8 @@ export class ShipError extends Error {
362
398
  * Routing:
363
399
  * - Already a `ShipError` → returned as-is (caller's intent preserved)
364
400
  * - `AbortError` → `ShipError.cancelled(...)`
365
- * - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
401
+ * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
402
+ * for what each runtime offers as evidence
366
403
  * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
367
404
  * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
368
405
  *
@@ -378,7 +415,7 @@ export class ShipError extends Error {
378
415
  if (cause.name === 'AbortError') {
379
416
  return ShipError.cancelled(`${op} was cancelled`);
380
417
  }
381
- if (cause instanceof TypeError && cause.message.includes('fetch')) {
418
+ if (isTransportFailure(cause)) {
382
419
  return ShipError.network(`${op} failed: ${cause.message}`, { cause });
383
420
  }
384
421
  return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
@@ -1002,6 +1039,21 @@ export function isDeployment(input) {
1002
1039
  // =============================================================================
1003
1040
  /** Default API URL if not otherwise configured. */
1004
1041
  export const DEFAULT_API = 'https://api.shipstatic.com';
1042
+ /**
1043
+ * How long an anonymous deployment lives before it expires.
1044
+ *
1045
+ * The lifetime of the public tier, and one fact with several readers. The API
1046
+ * stamps a deployment's `expires` from it and gives a claim code exactly the
1047
+ * same window — a live site with a dead claim link is a coherence bug, so the
1048
+ * two are one constant rather than two that agree. Both MCP transports quote
1049
+ * the duration in prose an agent reads, and derive it from here rather than
1050
+ * writing it out, which they did in eight places until this export existed.
1051
+ *
1052
+ * Seconds, spelled in the name: this platform has both second- and
1053
+ * millisecond-valued durations, and the pair is only safe when each says which
1054
+ * it is.
1055
+ */
1056
+ export const PUBLIC_DEPLOYMENT_TTL_SECONDS = 3 * 24 * 60 * 60;
1005
1057
  // =============================================================================
1006
1058
  // FILE UPLOAD TYPES
1007
1059
  // =============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.5.0-beta.17",
3
+ "version": "2.5.0-beta.19",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -754,6 +754,42 @@ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
754
754
  */
755
755
  const MAX_FOREIGN_MESSAGE_LENGTH = 200;
756
756
 
757
+ /**
758
+ * Did the runtime say the exchange never completed?
759
+ *
760
+ * WHATWG has `fetch` reject with a **TypeError** on network error, and undici,
761
+ * Chromium and Firefox comply. Bun does not: it rejects with a plain `Error`
762
+ * carrying a system `code` string. Captured 2026-08-05 (the capture script is
763
+ * in `tests/errors.test.ts`, "runtime failure shapes"):
764
+ *
765
+ * | failure | Node 22 / undici | Bun 1.3.14 |
766
+ * |---------------|---------------------------|----------------------------------------------|
767
+ * | refused | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
768
+ * | DNS failure | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
769
+ * | reset | `TypeError: fetch failed` | `Error` `code: 'ECONNRESET'` |
770
+ * | TLS rejected | `TypeError: fetch failed` | `Error` `code: 'UNKNOWN_CERTIFICATE_…ERROR'` |
771
+ *
772
+ * So the test is the **evidence, not a list of dialect strings**: a string
773
+ * `code` is a runtime naming a transport-level failure. An allowlist of codes
774
+ * was written first and rejected — the TLS row alone would mean enumerating
775
+ * BoringSSL's certificate table, and a code nobody guessed is precisely the bug
776
+ * this closes. Two kinds of error are deliberately NOT caught: ordinary JS
777
+ * faults carry no `code` at all, and a `DOMException`'s is a **number**, so
778
+ * aborts and timeouts fall through to their own arms.
779
+ *
780
+ * The accepted trade: a caller's `TokenProvider` that throws a coded error
781
+ * (`ENOENT` from a keychain read) is typed `Network` rather than `Api`. Both
782
+ * are wrong for it, `Network` is the cheaper wrong — it says "nothing was
783
+ * exchanged", which is true, where `Api` claims a server answered.
784
+ */
785
+ function isTransportFailure(cause: Error): boolean {
786
+ if (typeof (cause as { code?: unknown }).code === 'string') return true;
787
+ // Spec runtimes put no code on the rejection itself. The message test is what
788
+ // keeps fetch's ARGUMENT errors out — `Failed to parse URL from …` is a
789
+ // caller's config mistake, not a transport failure.
790
+ return cause instanceof TypeError && cause.message.includes('fetch');
791
+ }
792
+
757
793
  /**
758
794
  * Standard error response format used everywhere
759
795
  */
@@ -900,7 +936,8 @@ export class ShipError extends Error {
900
936
  * Routing:
901
937
  * - Already a `ShipError` → returned as-is (caller's intent preserved)
902
938
  * - `AbortError` → `ShipError.cancelled(...)`
903
- * - `TypeError` whose message mentions "fetch" → `ShipError.network(...)`
939
+ * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
940
+ * for what each runtime offers as evidence
904
941
  * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
905
942
  * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
906
943
  *
@@ -917,7 +954,7 @@ export class ShipError extends Error {
917
954
  if (cause.name === 'AbortError') {
918
955
  return ShipError.cancelled(`${op} was cancelled`);
919
956
  }
920
- if (cause instanceof TypeError && cause.message.includes('fetch')) {
957
+ if (isTransportFailure(cause)) {
921
958
  return ShipError.network(`${op} failed: ${cause.message}`, { cause });
922
959
  }
923
960
  return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
@@ -1735,6 +1772,22 @@ export interface StaticFile {
1735
1772
  /** Default API URL if not otherwise configured. */
1736
1773
  export const DEFAULT_API = 'https://api.shipstatic.com';
1737
1774
 
1775
+ /**
1776
+ * How long an anonymous deployment lives before it expires.
1777
+ *
1778
+ * The lifetime of the public tier, and one fact with several readers. The API
1779
+ * stamps a deployment's `expires` from it and gives a claim code exactly the
1780
+ * same window — a live site with a dead claim link is a coherence bug, so the
1781
+ * two are one constant rather than two that agree. Both MCP transports quote
1782
+ * the duration in prose an agent reads, and derive it from here rather than
1783
+ * writing it out, which they did in eight places until this export existed.
1784
+ *
1785
+ * Seconds, spelled in the name: this platform has both second- and
1786
+ * millisecond-valued durations, and the pair is only safe when each says which
1787
+ * it is.
1788
+ */
1789
+ export const PUBLIC_DEPLOYMENT_TTL_SECONDS = 3 * 24 * 60 * 60;
1790
+
1738
1791
  // =============================================================================
1739
1792
  // RESOURCE INTERFACE CONTRACTS
1740
1793
  // =============================================================================
@@ -1791,8 +1844,8 @@ export interface DeploymentUploadOptions {
1791
1844
  *
1792
1845
  * **Agents are the audience.** A human notices a duplicate; an automated
1793
1846
  * retry does not. Pick a key that identifies the ATTEMPT — a run id, a
1794
- * commit sha, a uuid minted before the first try — never one that varies
1795
- * per attempt, which would defeat the point.
1847
+ * commit sha, a uuid minted before the first try — never one minted fresh
1848
+ * on each retry, which would defeat the point.
1796
1849
  *
1797
1850
  * The replay is per-caller, and it stores successes only: a failed deploy
1798
1851
  * retries fresh under the same key.