@shipstatic/types 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,7 +68,7 @@ try { response = await fetch(url); }
68
68
  catch (cause) { throw ShipError.fromFetchError(cause, 'Get account'); }
69
69
  ```
70
70
 
71
- `fromHttpResponse` trusts the body's `error` field when it's a known server-producible `ErrorType` — so a server's `ShipError.validation(...)` round-trips back to `ErrorType.Validation` on the client. For non-API responses (CDN errors, intermediaries) or malformed bodies it falls back to status-derived (401 → `Authentication`, 429 → `RateLimit`, else → `Api`). Body's `message` and `details` are preserved best-effort.
71
+ `fromHttpResponse` trusts the body's `error` field when it's a known server-producible `ErrorType` — so a server's `ShipError.validation(...)` round-trips back to `ErrorType.Validation` on the client. For non-API responses (CDN errors, intermediaries) or malformed bodies it falls back to status-derived (401 → `Authentication`, 403 → `Forbidden`, 429 → `RateLimit`, else → `Api`). Client-only types (`Network`, `Cancelled`, `File`, `Config`) are filtered out of the trusted set. Body's `message` and `details` are preserved best-effort.
72
72
 
73
73
  `fromFetchError` routes by the thrown cause: an existing `ShipError` is returned unchanged, `AbortError` becomes `Cancelled`, a fetch `TypeError` becomes `Network`, anything else becomes `Api` (with no HTTP status — the request never reached the server).
74
74
 
@@ -159,6 +159,15 @@ import {
159
159
  } from '@shipstatic/types';
160
160
  ```
161
161
 
162
+ ### Password Utilities
163
+
164
+ ```typescript
165
+ import {
166
+ PASSWORD_CONSTRAINTS, // { MIN_LENGTH: 6, MAX_LENGTH: 128 }
167
+ validatePassword, // (value: unknown) => string | undefined
168
+ } from '@shipstatic/types';
169
+ ```
170
+
162
171
  ### Constants
163
172
 
164
173
  ```typescript
package/dist/index.d.ts CHANGED
@@ -692,7 +692,8 @@ export interface DeploymentUploadOptions {
692
692
  * Optional password that protects this deployment.
693
693
  *
694
694
  * Length: {@link PASSWORD_CONSTRAINTS.MIN_LENGTH} to
695
- * {@link PASSWORD_CONSTRAINTS.MAX_LENGTH} characters; whitespace is
695
+ * {@link PASSWORD_CONSTRAINTS.MAX_LENGTH} characters. Leading and trailing
696
+ * whitespace is trimmed before validation; internal whitespace is
696
697
  * significant. Visitors are prompted to enter the password before they can
697
698
  * view the deployment — including on any custom domains pointing at it.
698
699
  * To remove protection, redeploy without a password.
@@ -1012,3 +1013,25 @@ export declare const PASSWORD_CONSTRAINTS: {
1012
1013
  /** Maximum password length in characters */
1013
1014
  readonly MAX_LENGTH: 128;
1014
1015
  };
1016
+ /**
1017
+ * Validate an optional deployment password and return it normalized.
1018
+ *
1019
+ * Absent (`undefined` / `null`) → returns `undefined`. Present → trim
1020
+ * leading/trailing whitespace, then validate against `PASSWORD_CONSTRAINTS`
1021
+ * length bounds (internal whitespace is significant and counts toward
1022
+ * length). Throws `ShipError.validation` on breach; returns the trimmed
1023
+ * value.
1024
+ *
1025
+ * The trim is canonical: at upload, the API hashes the trimmed value; at
1026
+ * unlock, the router trims submissions before hashing. Submission and storage
1027
+ * agree byte-for-byte. Length validation runs on the trimmed value because
1028
+ * that's the user's actual intent — and it disarms a class of invisible
1029
+ * foot-guns (trailing newlines from copy/paste, mobile auto-spacing,
1030
+ * password-manager artifacts).
1031
+ *
1032
+ * Single source of truth shared by SDK (client-side validation, return
1033
+ * ignored) and API (server-side enforcement, return threaded into config).
1034
+ * Length is part of the wire-format contract; strength rules, if added later,
1035
+ * stay server-side. See `CLAUDE.md` "Validation: format vs policy".
1036
+ */
1037
+ export declare function validatePassword(value: unknown): string | undefined;
package/dist/index.js CHANGED
@@ -661,3 +661,37 @@ export const PASSWORD_CONSTRAINTS = {
661
661
  /** Maximum password length in characters */
662
662
  MAX_LENGTH: 128,
663
663
  };
664
+ /**
665
+ * Validate an optional deployment password and return it normalized.
666
+ *
667
+ * Absent (`undefined` / `null`) → returns `undefined`. Present → trim
668
+ * leading/trailing whitespace, then validate against `PASSWORD_CONSTRAINTS`
669
+ * length bounds (internal whitespace is significant and counts toward
670
+ * length). Throws `ShipError.validation` on breach; returns the trimmed
671
+ * value.
672
+ *
673
+ * The trim is canonical: at upload, the API hashes the trimmed value; at
674
+ * unlock, the router trims submissions before hashing. Submission and storage
675
+ * agree byte-for-byte. Length validation runs on the trimmed value because
676
+ * that's the user's actual intent — and it disarms a class of invisible
677
+ * foot-guns (trailing newlines from copy/paste, mobile auto-spacing,
678
+ * password-manager artifacts).
679
+ *
680
+ * Single source of truth shared by SDK (client-side validation, return
681
+ * ignored) and API (server-side enforcement, return threaded into config).
682
+ * Length is part of the wire-format contract; strength rules, if added later,
683
+ * stay server-side. See `CLAUDE.md` "Validation: format vs policy".
684
+ */
685
+ export function validatePassword(value) {
686
+ if (value === undefined || value === null)
687
+ return undefined;
688
+ if (typeof value !== 'string') {
689
+ throw ShipError.validation('Password must be a string');
690
+ }
691
+ const trimmed = value.trim();
692
+ if (trimmed.length < PASSWORD_CONSTRAINTS.MIN_LENGTH ||
693
+ trimmed.length > PASSWORD_CONSTRAINTS.MAX_LENGTH) {
694
+ throw ShipError.validation(`Password must be between ${PASSWORD_CONSTRAINTS.MIN_LENGTH} and ${PASSWORD_CONSTRAINTS.MAX_LENGTH} characters`);
695
+ }
696
+ return trimmed;
697
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
4
4
  "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -1065,7 +1065,8 @@ export interface DeploymentUploadOptions {
1065
1065
  * Optional password that protects this deployment.
1066
1066
  *
1067
1067
  * Length: {@link PASSWORD_CONSTRAINTS.MIN_LENGTH} to
1068
- * {@link PASSWORD_CONSTRAINTS.MAX_LENGTH} characters; whitespace is
1068
+ * {@link PASSWORD_CONSTRAINTS.MAX_LENGTH} characters. Leading and trailing
1069
+ * whitespace is trimmed before validation; internal whitespace is
1069
1070
  * significant. Visitors are prompted to enter the password before they can
1070
1071
  * view the deployment — including on any custom domains pointing at it.
1071
1072
  * To remove protection, redeploy without a password.
@@ -1524,4 +1525,42 @@ export const PASSWORD_CONSTRAINTS = {
1524
1525
  MIN_LENGTH: 6,
1525
1526
  /** Maximum password length in characters */
1526
1527
  MAX_LENGTH: 128,
1527
- } as const;
1528
+ } as const;
1529
+
1530
+ /**
1531
+ * Validate an optional deployment password and return it normalized.
1532
+ *
1533
+ * Absent (`undefined` / `null`) → returns `undefined`. Present → trim
1534
+ * leading/trailing whitespace, then validate against `PASSWORD_CONSTRAINTS`
1535
+ * length bounds (internal whitespace is significant and counts toward
1536
+ * length). Throws `ShipError.validation` on breach; returns the trimmed
1537
+ * value.
1538
+ *
1539
+ * The trim is canonical: at upload, the API hashes the trimmed value; at
1540
+ * unlock, the router trims submissions before hashing. Submission and storage
1541
+ * agree byte-for-byte. Length validation runs on the trimmed value because
1542
+ * that's the user's actual intent — and it disarms a class of invisible
1543
+ * foot-guns (trailing newlines from copy/paste, mobile auto-spacing,
1544
+ * password-manager artifacts).
1545
+ *
1546
+ * Single source of truth shared by SDK (client-side validation, return
1547
+ * ignored) and API (server-side enforcement, return threaded into config).
1548
+ * Length is part of the wire-format contract; strength rules, if added later,
1549
+ * stay server-side. See `CLAUDE.md` "Validation: format vs policy".
1550
+ */
1551
+ export function validatePassword(value: unknown): string | undefined {
1552
+ if (value === undefined || value === null) return undefined;
1553
+ if (typeof value !== 'string') {
1554
+ throw ShipError.validation('Password must be a string');
1555
+ }
1556
+ const trimmed = value.trim();
1557
+ if (
1558
+ trimmed.length < PASSWORD_CONSTRAINTS.MIN_LENGTH ||
1559
+ trimmed.length > PASSWORD_CONSTRAINTS.MAX_LENGTH
1560
+ ) {
1561
+ throw ShipError.validation(
1562
+ `Password must be between ${PASSWORD_CONSTRAINTS.MIN_LENGTH} and ${PASSWORD_CONSTRAINTS.MAX_LENGTH} characters`,
1563
+ );
1564
+ }
1565
+ return trimmed;
1566
+ }