@shipstatic/types 2.26.0-beta.1 → 2.26.0-beta.3
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 +50 -3
- package/dist/index.js +51 -0
- package/dist/schemas.js +9 -3
- package/package.json +1 -8
- package/src/index.ts +73 -3
- package/src/schemas.ts +11 -3
- package/dist/time.d.ts +0 -50
- package/dist/time.js +0 -66
- package/src/time.ts +0 -69
package/dist/index.d.ts
CHANGED
|
@@ -479,13 +479,28 @@ export interface SetupInstructionsResponse {
|
|
|
479
479
|
* one key cannot mean both. See {@link DeploymentDeleteResponse} for the law.
|
|
480
480
|
*/
|
|
481
481
|
export interface DomainValidateResponse {
|
|
482
|
-
/** Whether the domain is
|
|
482
|
+
/** Whether the domain's SHAPE is usable: format, and the caller's own rules. */
|
|
483
483
|
valid: boolean;
|
|
484
484
|
/** Normalized domain name, null when invalid */
|
|
485
485
|
normalized: string | null;
|
|
486
|
-
/**
|
|
486
|
+
/**
|
|
487
|
+
* Whether nobody has registered the name yet; null when invalid.
|
|
488
|
+
*
|
|
489
|
+
* It answers "would creating this be new", not "would a write succeed": `PUT
|
|
490
|
+
* /domains/:domain` is an upsert, which is how a domain is re-pointed, so a
|
|
491
|
+
* caller's OWN domain is unavailable here and writable there. Availability
|
|
492
|
+
* does not depend on the kind of name; a custom domain answered `true`
|
|
493
|
+
* whoever owned it until 2026-09-17.
|
|
494
|
+
*/
|
|
487
495
|
available: boolean | null;
|
|
488
|
-
/**
|
|
496
|
+
/**
|
|
497
|
+
* Why the name is unusable, null when it IS usable — displayed verbatim.
|
|
498
|
+
*
|
|
499
|
+
* A name is unusable when it is invalid OR unavailable, and both carry a
|
|
500
|
+
* reason. This said "null when valid" until 2026-09-17, which was already
|
|
501
|
+
* untrue of the endpoint it described: a registered name is valid, is
|
|
502
|
+
* unusable, and had no reason at all, which is why every client invented one.
|
|
503
|
+
*/
|
|
489
504
|
reason: string | null;
|
|
490
505
|
}
|
|
491
506
|
/**
|
|
@@ -1793,6 +1808,38 @@ export declare const MY_API_KEY_URL = "https://my.shipstatic.com/api-key";
|
|
|
1793
1808
|
* it is.
|
|
1794
1809
|
*/
|
|
1795
1810
|
export declare const PUBLIC_DEPLOYMENT_TTL_SECONDS: number;
|
|
1811
|
+
/**
|
|
1812
|
+
* A span of time in words: `"3 days"`, `"5 hours"`, `"45 minutes"`.
|
|
1813
|
+
*
|
|
1814
|
+
* Minutes under two hours, hours under two days, and days beyond, each rounded
|
|
1815
|
+
* to the nearest. The unit changes exactly where the larger one rounds to two,
|
|
1816
|
+
* so the number never jumps as time passes (119 minutes, then 2 hours), and
|
|
1817
|
+
* the two vaguest things a rounded clock can say, "1 hour" and "1 day", are
|
|
1818
|
+
* never said. Rounding rather than truncating is what lets a deployment made a
|
|
1819
|
+
* second ago read the lifetime it was given: "3 days", not "2 days". Days are
|
|
1820
|
+
* the largest unit because nobody counts a deadline in weeks, and a month has
|
|
1821
|
+
* no fixed length.
|
|
1822
|
+
*
|
|
1823
|
+
* A span that rounds to no minutes at all, zero and negative included, reads
|
|
1824
|
+
* "less than a minute".
|
|
1825
|
+
*/
|
|
1826
|
+
export declare function formatDuration(seconds: number): string;
|
|
1827
|
+
/**
|
|
1828
|
+
* The time left before a deadline, in words (`"3 days"`), or `null` once it
|
|
1829
|
+
* has passed.
|
|
1830
|
+
*
|
|
1831
|
+
* `expires` is unix SECONDS, the wire's own field, taken as it arrives so that
|
|
1832
|
+
* no caller converts it: the deploy card once read it as a date string and
|
|
1833
|
+
* showed the same three days on every deployment for months. `now` is seconds
|
|
1834
|
+
* too, and defaults to the clock.
|
|
1835
|
+
*
|
|
1836
|
+
* A surface that tells a person how long a deployment has left says it with
|
|
1837
|
+
* this, so one deployment reads the same everywhere, whenever it is looked at.
|
|
1838
|
+
* What a surface says around the words is its own ("Expires in", "It stays
|
|
1839
|
+
* live for"), and so is what it says once the deadline has passed, which is
|
|
1840
|
+
* why that case is `null` rather than a sentence.
|
|
1841
|
+
*/
|
|
1842
|
+
export declare function formatTimeRemaining(expires: number, now?: number): string | null;
|
|
1796
1843
|
/**
|
|
1797
1844
|
* Universal deploy input — the union of every shape the SDK accepts.
|
|
1798
1845
|
*
|
package/dist/index.js
CHANGED
|
@@ -1725,6 +1725,57 @@ export const MY_API_KEY_URL = 'https://my.shipstatic.com/api-key';
|
|
|
1725
1725
|
*/
|
|
1726
1726
|
export const PUBLIC_DEPLOYMENT_TTL_SECONDS = 3 * 24 * 60 * 60;
|
|
1727
1727
|
// =============================================================================
|
|
1728
|
+
// TIME REMAINING
|
|
1729
|
+
// =============================================================================
|
|
1730
|
+
/**
|
|
1731
|
+
* A span of time in words: `"3 days"`, `"5 hours"`, `"45 minutes"`.
|
|
1732
|
+
*
|
|
1733
|
+
* Minutes under two hours, hours under two days, and days beyond, each rounded
|
|
1734
|
+
* to the nearest. The unit changes exactly where the larger one rounds to two,
|
|
1735
|
+
* so the number never jumps as time passes (119 minutes, then 2 hours), and
|
|
1736
|
+
* the two vaguest things a rounded clock can say, "1 hour" and "1 day", are
|
|
1737
|
+
* never said. Rounding rather than truncating is what lets a deployment made a
|
|
1738
|
+
* second ago read the lifetime it was given: "3 days", not "2 days". Days are
|
|
1739
|
+
* the largest unit because nobody counts a deadline in weeks, and a month has
|
|
1740
|
+
* no fixed length.
|
|
1741
|
+
*
|
|
1742
|
+
* A span that rounds to no minutes at all, zero and negative included, reads
|
|
1743
|
+
* "less than a minute".
|
|
1744
|
+
*/
|
|
1745
|
+
export function formatDuration(seconds) {
|
|
1746
|
+
const minutes = Math.round(seconds / 60);
|
|
1747
|
+
if (!(minutes >= 1))
|
|
1748
|
+
return 'less than a minute';
|
|
1749
|
+
if (minutes < 120)
|
|
1750
|
+
return countOf(minutes, 'minute');
|
|
1751
|
+
const hours = Math.round(seconds / 3_600);
|
|
1752
|
+
if (hours < 48)
|
|
1753
|
+
return countOf(hours, 'hour');
|
|
1754
|
+
return countOf(Math.round(seconds / 86_400), 'day');
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* The time left before a deadline, in words (`"3 days"`), or `null` once it
|
|
1758
|
+
* has passed.
|
|
1759
|
+
*
|
|
1760
|
+
* `expires` is unix SECONDS, the wire's own field, taken as it arrives so that
|
|
1761
|
+
* no caller converts it: the deploy card once read it as a date string and
|
|
1762
|
+
* showed the same three days on every deployment for months. `now` is seconds
|
|
1763
|
+
* too, and defaults to the clock.
|
|
1764
|
+
*
|
|
1765
|
+
* A surface that tells a person how long a deployment has left says it with
|
|
1766
|
+
* this, so one deployment reads the same everywhere, whenever it is looked at.
|
|
1767
|
+
* What a surface says around the words is its own ("Expires in", "It stays
|
|
1768
|
+
* live for"), and so is what it says once the deadline has passed, which is
|
|
1769
|
+
* why that case is `null` rather than a sentence.
|
|
1770
|
+
*/
|
|
1771
|
+
export function formatTimeRemaining(expires, now = Date.now() / 1_000) {
|
|
1772
|
+
const seconds = expires - now;
|
|
1773
|
+
return seconds > 0 ? formatDuration(seconds) : null;
|
|
1774
|
+
}
|
|
1775
|
+
function countOf(count, unit) {
|
|
1776
|
+
return `${count} ${unit}${count === 1 ? '' : 's'}`;
|
|
1777
|
+
}
|
|
1778
|
+
// =============================================================================
|
|
1728
1779
|
// FILE UPLOAD TYPES
|
|
1729
1780
|
// =============================================================================
|
|
1730
1781
|
/**
|
package/dist/schemas.js
CHANGED
|
@@ -165,10 +165,16 @@ export const DomainShareResponseSchema = z.object({
|
|
|
165
165
|
.describe('The shareable DNS setup URL; whoever opens it sees the records to configure, with no API key.'),
|
|
166
166
|
});
|
|
167
167
|
export const DomainValidateResponseSchema = z.object({
|
|
168
|
-
valid: z.boolean().describe(
|
|
168
|
+
valid: z.boolean().describe("Whether the domain's shape is usable."),
|
|
169
169
|
normalized: z.string().nullable().describe('The normalized domain name; null when invalid.'),
|
|
170
|
-
available: z
|
|
171
|
-
|
|
170
|
+
available: z
|
|
171
|
+
.boolean()
|
|
172
|
+
.nullable()
|
|
173
|
+
.describe('Whether nobody has registered the name yet; null when invalid. Creating it would be new; re-pointing your own domain is a write, not a create.'),
|
|
174
|
+
reason: z
|
|
175
|
+
.string()
|
|
176
|
+
.nullable()
|
|
177
|
+
.describe('Why the name is unusable, for display; null when it is usable.'),
|
|
172
178
|
});
|
|
173
179
|
// =============================================================================
|
|
174
180
|
// ACCOUNT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/types",
|
|
3
|
-
"version": "2.26.0-beta.
|
|
3
|
+
"version": "2.26.0-beta.3",
|
|
4
4
|
"description": "Shared TypeScript types for the ShipStatic platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,19 +13,12 @@
|
|
|
13
13
|
"./schemas": {
|
|
14
14
|
"types": "./dist/schemas.d.ts",
|
|
15
15
|
"default": "./dist/schemas.js"
|
|
16
|
-
},
|
|
17
|
-
"./time": {
|
|
18
|
-
"types": "./dist/time.d.ts",
|
|
19
|
-
"default": "./dist/time.js"
|
|
20
16
|
}
|
|
21
17
|
},
|
|
22
18
|
"typesVersions": {
|
|
23
19
|
"*": {
|
|
24
20
|
"schemas": [
|
|
25
21
|
"./dist/schemas.d.ts"
|
|
26
|
-
],
|
|
27
|
-
"time": [
|
|
28
|
-
"./dist/time.d.ts"
|
|
29
22
|
]
|
|
30
23
|
}
|
|
31
24
|
},
|
package/src/index.ts
CHANGED
|
@@ -538,13 +538,28 @@ export interface SetupInstructionsResponse {
|
|
|
538
538
|
* one key cannot mean both. See {@link DeploymentDeleteResponse} for the law.
|
|
539
539
|
*/
|
|
540
540
|
export interface DomainValidateResponse {
|
|
541
|
-
/** Whether the domain is
|
|
541
|
+
/** Whether the domain's SHAPE is usable: format, and the caller's own rules. */
|
|
542
542
|
valid: boolean;
|
|
543
543
|
/** Normalized domain name, null when invalid */
|
|
544
544
|
normalized: string | null;
|
|
545
|
-
/**
|
|
545
|
+
/**
|
|
546
|
+
* Whether nobody has registered the name yet; null when invalid.
|
|
547
|
+
*
|
|
548
|
+
* It answers "would creating this be new", not "would a write succeed": `PUT
|
|
549
|
+
* /domains/:domain` is an upsert, which is how a domain is re-pointed, so a
|
|
550
|
+
* caller's OWN domain is unavailable here and writable there. Availability
|
|
551
|
+
* does not depend on the kind of name; a custom domain answered `true`
|
|
552
|
+
* whoever owned it until 2026-09-17.
|
|
553
|
+
*/
|
|
546
554
|
available: boolean | null;
|
|
547
|
-
/**
|
|
555
|
+
/**
|
|
556
|
+
* Why the name is unusable, null when it IS usable — displayed verbatim.
|
|
557
|
+
*
|
|
558
|
+
* A name is unusable when it is invalid OR unavailable, and both carry a
|
|
559
|
+
* reason. This said "null when valid" until 2026-09-17, which was already
|
|
560
|
+
* untrue of the endpoint it described: a registered name is valid, is
|
|
561
|
+
* unusable, and had no reason at all, which is why every client invented one.
|
|
562
|
+
*/
|
|
548
563
|
reason: string | null;
|
|
549
564
|
}
|
|
550
565
|
|
|
@@ -2646,6 +2661,61 @@ export const MY_API_KEY_URL = 'https://my.shipstatic.com/api-key';
|
|
|
2646
2661
|
*/
|
|
2647
2662
|
export const PUBLIC_DEPLOYMENT_TTL_SECONDS = 3 * 24 * 60 * 60;
|
|
2648
2663
|
|
|
2664
|
+
// =============================================================================
|
|
2665
|
+
// TIME REMAINING
|
|
2666
|
+
// =============================================================================
|
|
2667
|
+
|
|
2668
|
+
/**
|
|
2669
|
+
* A span of time in words: `"3 days"`, `"5 hours"`, `"45 minutes"`.
|
|
2670
|
+
*
|
|
2671
|
+
* Minutes under two hours, hours under two days, and days beyond, each rounded
|
|
2672
|
+
* to the nearest. The unit changes exactly where the larger one rounds to two,
|
|
2673
|
+
* so the number never jumps as time passes (119 minutes, then 2 hours), and
|
|
2674
|
+
* the two vaguest things a rounded clock can say, "1 hour" and "1 day", are
|
|
2675
|
+
* never said. Rounding rather than truncating is what lets a deployment made a
|
|
2676
|
+
* second ago read the lifetime it was given: "3 days", not "2 days". Days are
|
|
2677
|
+
* the largest unit because nobody counts a deadline in weeks, and a month has
|
|
2678
|
+
* no fixed length.
|
|
2679
|
+
*
|
|
2680
|
+
* A span that rounds to no minutes at all, zero and negative included, reads
|
|
2681
|
+
* "less than a minute".
|
|
2682
|
+
*/
|
|
2683
|
+
export function formatDuration(seconds: number): string {
|
|
2684
|
+
const minutes = Math.round(seconds / 60);
|
|
2685
|
+
if (!(minutes >= 1)) return 'less than a minute';
|
|
2686
|
+
if (minutes < 120) return countOf(minutes, 'minute');
|
|
2687
|
+
const hours = Math.round(seconds / 3_600);
|
|
2688
|
+
if (hours < 48) return countOf(hours, 'hour');
|
|
2689
|
+
return countOf(Math.round(seconds / 86_400), 'day');
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
/**
|
|
2693
|
+
* The time left before a deadline, in words (`"3 days"`), or `null` once it
|
|
2694
|
+
* has passed.
|
|
2695
|
+
*
|
|
2696
|
+
* `expires` is unix SECONDS, the wire's own field, taken as it arrives so that
|
|
2697
|
+
* no caller converts it: the deploy card once read it as a date string and
|
|
2698
|
+
* showed the same three days on every deployment for months. `now` is seconds
|
|
2699
|
+
* too, and defaults to the clock.
|
|
2700
|
+
*
|
|
2701
|
+
* A surface that tells a person how long a deployment has left says it with
|
|
2702
|
+
* this, so one deployment reads the same everywhere, whenever it is looked at.
|
|
2703
|
+
* What a surface says around the words is its own ("Expires in", "It stays
|
|
2704
|
+
* live for"), and so is what it says once the deadline has passed, which is
|
|
2705
|
+
* why that case is `null` rather than a sentence.
|
|
2706
|
+
*/
|
|
2707
|
+
export function formatTimeRemaining(
|
|
2708
|
+
expires: number,
|
|
2709
|
+
now: number = Date.now() / 1_000,
|
|
2710
|
+
): string | null {
|
|
2711
|
+
const seconds = expires - now;
|
|
2712
|
+
return seconds > 0 ? formatDuration(seconds) : null;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
function countOf(count: number, unit: string): string {
|
|
2716
|
+
return `${count} ${unit}${count === 1 ? '' : 's'}`;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2649
2719
|
// =============================================================================
|
|
2650
2720
|
// RESOURCE INTERFACE CONTRACTS
|
|
2651
2721
|
// =============================================================================
|
package/src/schemas.ts
CHANGED
|
@@ -218,10 +218,18 @@ export const DomainShareResponseSchema = z.object({
|
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
export const DomainValidateResponseSchema = z.object({
|
|
221
|
-
valid: z.boolean().describe(
|
|
221
|
+
valid: z.boolean().describe("Whether the domain's shape is usable."),
|
|
222
222
|
normalized: z.string().nullable().describe('The normalized domain name; null when invalid.'),
|
|
223
|
-
available: z
|
|
224
|
-
|
|
223
|
+
available: z
|
|
224
|
+
.boolean()
|
|
225
|
+
.nullable()
|
|
226
|
+
.describe(
|
|
227
|
+
'Whether nobody has registered the name yet; null when invalid. Creating it would be new; re-pointing your own domain is a write, not a create.',
|
|
228
|
+
),
|
|
229
|
+
reason: z
|
|
230
|
+
.string()
|
|
231
|
+
.nullable()
|
|
232
|
+
.describe('Why the name is unusable, for display; null when it is usable.'),
|
|
225
233
|
});
|
|
226
234
|
|
|
227
235
|
// =============================================================================
|
package/dist/time.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How long a deployment has left, in the words a person reads.
|
|
3
|
-
*
|
|
4
|
-
* When this module was written (2026-09-17) one deadline was told four ways:
|
|
5
|
-
* the deploy card counted from now, the CLI and the marketing site from the
|
|
6
|
-
* deployment's creation, the VS Code palette quoted the anonymous tier's fixed
|
|
7
|
-
* lifetime, and the dashboard truncated, so a deployment made a second ago
|
|
8
|
-
* read "2d" there and "3 days" everywhere else. The rule since: a surface that
|
|
9
|
-
* tells a person how long a deployment has left says it with
|
|
10
|
-
* `formatTimeRemaining`, and a duration a person reads about deployments is
|
|
11
|
-
* spelled by `formatDuration`.
|
|
12
|
-
*
|
|
13
|
-
* The WORDS are the surface's own. A card says "Expires in", the marketing
|
|
14
|
-
* site says "It stays live for", and each says something different once the
|
|
15
|
-
* deadline has passed, which is why that case is `null` rather than a
|
|
16
|
-
* sentence. What is owned here is the part that drifted silently: the unit,
|
|
17
|
-
* the rounding, and what the clock is measured from.
|
|
18
|
-
*
|
|
19
|
-
* A subpath export (`@shipstatic/types/time`), for the same reason as
|
|
20
|
-
* `/schemas`: a browser bundle importing only this carries only this. From the
|
|
21
|
-
* main entry it would carry about a kilobyte of unrelated constants that a
|
|
22
|
-
* bundler cannot prove are unused (1 KB gzipped, measured with the deploy
|
|
23
|
-
* card's esbuild on 2026-09-17).
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* A span of time in words: `"3 days"`, `"5 hours"`, `"45 minutes"`.
|
|
27
|
-
*
|
|
28
|
-
* Minutes under two hours, hours under two days, and days beyond, each rounded
|
|
29
|
-
* to the nearest. The unit changes exactly where the larger one rounds to two,
|
|
30
|
-
* so the number never jumps as time passes (119 minutes, then 2 hours), and
|
|
31
|
-
* the two vaguest things a rounded clock can say, "1 hour" and "1 day", are
|
|
32
|
-
* never said. Rounding rather than truncating is what lets a deployment made a
|
|
33
|
-
* second ago read the lifetime it was given: "3 days", not "2 days". Days are
|
|
34
|
-
* the largest unit because nobody counts a deadline in weeks, and a month has
|
|
35
|
-
* no fixed length.
|
|
36
|
-
*
|
|
37
|
-
* A span that rounds to no minutes at all, zero and negative included, reads
|
|
38
|
-
* "less than a minute".
|
|
39
|
-
*/
|
|
40
|
-
export declare function formatDuration(seconds: number): string;
|
|
41
|
-
/**
|
|
42
|
-
* The time left before a deadline, in words (`"3 days"`), or `null` once it
|
|
43
|
-
* has passed.
|
|
44
|
-
*
|
|
45
|
-
* `expires` is unix SECONDS, the wire's own field, taken as it arrives so that
|
|
46
|
-
* no caller converts it: the deploy card once read it as a date string and
|
|
47
|
-
* showed the same three days on every deployment for months. `now` is seconds
|
|
48
|
-
* too, and defaults to the clock.
|
|
49
|
-
*/
|
|
50
|
-
export declare function formatTimeRemaining(expires: number, now?: number): string | null;
|
package/dist/time.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How long a deployment has left, in the words a person reads.
|
|
3
|
-
*
|
|
4
|
-
* When this module was written (2026-09-17) one deadline was told four ways:
|
|
5
|
-
* the deploy card counted from now, the CLI and the marketing site from the
|
|
6
|
-
* deployment's creation, the VS Code palette quoted the anonymous tier's fixed
|
|
7
|
-
* lifetime, and the dashboard truncated, so a deployment made a second ago
|
|
8
|
-
* read "2d" there and "3 days" everywhere else. The rule since: a surface that
|
|
9
|
-
* tells a person how long a deployment has left says it with
|
|
10
|
-
* `formatTimeRemaining`, and a duration a person reads about deployments is
|
|
11
|
-
* spelled by `formatDuration`.
|
|
12
|
-
*
|
|
13
|
-
* The WORDS are the surface's own. A card says "Expires in", the marketing
|
|
14
|
-
* site says "It stays live for", and each says something different once the
|
|
15
|
-
* deadline has passed, which is why that case is `null` rather than a
|
|
16
|
-
* sentence. What is owned here is the part that drifted silently: the unit,
|
|
17
|
-
* the rounding, and what the clock is measured from.
|
|
18
|
-
*
|
|
19
|
-
* A subpath export (`@shipstatic/types/time`), for the same reason as
|
|
20
|
-
* `/schemas`: a browser bundle importing only this carries only this. From the
|
|
21
|
-
* main entry it would carry about a kilobyte of unrelated constants that a
|
|
22
|
-
* bundler cannot prove are unused (1 KB gzipped, measured with the deploy
|
|
23
|
-
* card's esbuild on 2026-09-17).
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* A span of time in words: `"3 days"`, `"5 hours"`, `"45 minutes"`.
|
|
27
|
-
*
|
|
28
|
-
* Minutes under two hours, hours under two days, and days beyond, each rounded
|
|
29
|
-
* to the nearest. The unit changes exactly where the larger one rounds to two,
|
|
30
|
-
* so the number never jumps as time passes (119 minutes, then 2 hours), and
|
|
31
|
-
* the two vaguest things a rounded clock can say, "1 hour" and "1 day", are
|
|
32
|
-
* never said. Rounding rather than truncating is what lets a deployment made a
|
|
33
|
-
* second ago read the lifetime it was given: "3 days", not "2 days". Days are
|
|
34
|
-
* the largest unit because nobody counts a deadline in weeks, and a month has
|
|
35
|
-
* no fixed length.
|
|
36
|
-
*
|
|
37
|
-
* A span that rounds to no minutes at all, zero and negative included, reads
|
|
38
|
-
* "less than a minute".
|
|
39
|
-
*/
|
|
40
|
-
export function formatDuration(seconds) {
|
|
41
|
-
const minutes = Math.round(seconds / 60);
|
|
42
|
-
if (!(minutes >= 1))
|
|
43
|
-
return 'less than a minute';
|
|
44
|
-
if (minutes < 120)
|
|
45
|
-
return countOf(minutes, 'minute');
|
|
46
|
-
const hours = Math.round(seconds / 3_600);
|
|
47
|
-
if (hours < 48)
|
|
48
|
-
return countOf(hours, 'hour');
|
|
49
|
-
return countOf(Math.round(seconds / 86_400), 'day');
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* The time left before a deadline, in words (`"3 days"`), or `null` once it
|
|
53
|
-
* has passed.
|
|
54
|
-
*
|
|
55
|
-
* `expires` is unix SECONDS, the wire's own field, taken as it arrives so that
|
|
56
|
-
* no caller converts it: the deploy card once read it as a date string and
|
|
57
|
-
* showed the same three days on every deployment for months. `now` is seconds
|
|
58
|
-
* too, and defaults to the clock.
|
|
59
|
-
*/
|
|
60
|
-
export function formatTimeRemaining(expires, now = Date.now() / 1_000) {
|
|
61
|
-
const seconds = expires - now;
|
|
62
|
-
return seconds > 0 ? formatDuration(seconds) : null;
|
|
63
|
-
}
|
|
64
|
-
function countOf(count, unit) {
|
|
65
|
-
return `${count} ${unit}${count === 1 ? '' : 's'}`;
|
|
66
|
-
}
|
package/src/time.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How long a deployment has left, in the words a person reads.
|
|
3
|
-
*
|
|
4
|
-
* When this module was written (2026-09-17) one deadline was told four ways:
|
|
5
|
-
* the deploy card counted from now, the CLI and the marketing site from the
|
|
6
|
-
* deployment's creation, the VS Code palette quoted the anonymous tier's fixed
|
|
7
|
-
* lifetime, and the dashboard truncated, so a deployment made a second ago
|
|
8
|
-
* read "2d" there and "3 days" everywhere else. The rule since: a surface that
|
|
9
|
-
* tells a person how long a deployment has left says it with
|
|
10
|
-
* `formatTimeRemaining`, and a duration a person reads about deployments is
|
|
11
|
-
* spelled by `formatDuration`.
|
|
12
|
-
*
|
|
13
|
-
* The WORDS are the surface's own. A card says "Expires in", the marketing
|
|
14
|
-
* site says "It stays live for", and each says something different once the
|
|
15
|
-
* deadline has passed, which is why that case is `null` rather than a
|
|
16
|
-
* sentence. What is owned here is the part that drifted silently: the unit,
|
|
17
|
-
* the rounding, and what the clock is measured from.
|
|
18
|
-
*
|
|
19
|
-
* A subpath export (`@shipstatic/types/time`), for the same reason as
|
|
20
|
-
* `/schemas`: a browser bundle importing only this carries only this. From the
|
|
21
|
-
* main entry it would carry about a kilobyte of unrelated constants that a
|
|
22
|
-
* bundler cannot prove are unused (1 KB gzipped, measured with the deploy
|
|
23
|
-
* card's esbuild on 2026-09-17).
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A span of time in words: `"3 days"`, `"5 hours"`, `"45 minutes"`.
|
|
28
|
-
*
|
|
29
|
-
* Minutes under two hours, hours under two days, and days beyond, each rounded
|
|
30
|
-
* to the nearest. The unit changes exactly where the larger one rounds to two,
|
|
31
|
-
* so the number never jumps as time passes (119 minutes, then 2 hours), and
|
|
32
|
-
* the two vaguest things a rounded clock can say, "1 hour" and "1 day", are
|
|
33
|
-
* never said. Rounding rather than truncating is what lets a deployment made a
|
|
34
|
-
* second ago read the lifetime it was given: "3 days", not "2 days". Days are
|
|
35
|
-
* the largest unit because nobody counts a deadline in weeks, and a month has
|
|
36
|
-
* no fixed length.
|
|
37
|
-
*
|
|
38
|
-
* A span that rounds to no minutes at all, zero and negative included, reads
|
|
39
|
-
* "less than a minute".
|
|
40
|
-
*/
|
|
41
|
-
export function formatDuration(seconds: number): string {
|
|
42
|
-
const minutes = Math.round(seconds / 60);
|
|
43
|
-
if (!(minutes >= 1)) return 'less than a minute';
|
|
44
|
-
if (minutes < 120) return countOf(minutes, 'minute');
|
|
45
|
-
const hours = Math.round(seconds / 3_600);
|
|
46
|
-
if (hours < 48) return countOf(hours, 'hour');
|
|
47
|
-
return countOf(Math.round(seconds / 86_400), 'day');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* The time left before a deadline, in words (`"3 days"`), or `null` once it
|
|
52
|
-
* has passed.
|
|
53
|
-
*
|
|
54
|
-
* `expires` is unix SECONDS, the wire's own field, taken as it arrives so that
|
|
55
|
-
* no caller converts it: the deploy card once read it as a date string and
|
|
56
|
-
* showed the same three days on every deployment for months. `now` is seconds
|
|
57
|
-
* too, and defaults to the clock.
|
|
58
|
-
*/
|
|
59
|
-
export function formatTimeRemaining(
|
|
60
|
-
expires: number,
|
|
61
|
-
now: number = Date.now() / 1_000,
|
|
62
|
-
): string | null {
|
|
63
|
-
const seconds = expires - now;
|
|
64
|
-
return seconds > 0 ? formatDuration(seconds) : null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function countOf(count: number, unit: string): string {
|
|
68
|
-
return `${count} ${unit}${count === 1 ? '' : 's'}`;
|
|
69
|
-
}
|