@mason-data/cli 0.0.0-stage → 0.7.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.
- package/LICENSE +202 -0
- package/README.md +268 -2
- package/dist/src/api/error.js +18 -0
- package/dist/src/api/postgres/catalog-worker.js +92 -0
- package/dist/src/api/postgres/catalog.js +477 -0
- package/dist/src/api/postgres/encode.js +541 -0
- package/dist/src/api/postgres/explain.js +143 -0
- package/dist/src/api/postgres/listener.js +60 -0
- package/dist/src/api/postgres/parser-worker.js +58 -0
- package/dist/src/api/postgres/parser.js +121 -0
- package/dist/src/api/postgres/plain.js +33 -0
- package/dist/src/api/postgres/protocol.js +404 -0
- package/dist/src/api/postgres/schema.js +54 -0
- package/dist/src/api/postgres/server.js +1771 -0
- package/dist/src/api/postgres/translate.js +248 -0
- package/dist/src/api/request-log.js +50 -0
- package/dist/src/cdc/pg-wire.js +588 -0
- package/dist/src/cdc/pgoutput.js +107 -0
- package/dist/src/cdc/poll.js +67 -0
- package/dist/src/cdc/source.js +65 -0
- package/dist/src/cli/background.js +198 -0
- package/dist/src/cli/command.js +173 -0
- package/dist/src/cli/commands.js +1277 -0
- package/dist/src/cli/control.js +36 -0
- package/dist/src/cli/demo.js +149 -0
- package/dist/src/cli/dev.js +669 -0
- package/dist/src/cli/doctor.js +260 -0
- package/dist/src/cli/guides.js +158 -0
- package/dist/src/cli/init.js +366 -0
- package/dist/src/cli/machine.js +162 -0
- package/dist/src/cli/main.js +83 -0
- package/dist/src/cli/mcp.js +726 -0
- package/dist/src/cli/pg-tls.js +100 -0
- package/dist/src/cli/pg-url.js +52 -0
- package/dist/src/cli/pin.js +36 -0
- package/dist/src/cli/ports.js +118 -0
- package/dist/src/cli/preflight.js +347 -0
- package/dist/src/cli/project.js +129 -0
- package/dist/src/cli/readiness.js +76 -0
- package/dist/src/cli/registry.js +220 -0
- package/dist/src/cli/replication-checks.js +120 -0
- package/dist/src/cli/replication-mode.js +148 -0
- package/dist/src/cli/run-server.js +311 -0
- package/dist/src/cli/runs.js +120 -0
- package/dist/src/cli/security.js +86 -0
- package/dist/src/cli/self-signed-cert.js +64 -0
- package/dist/src/cli/serve.js +301 -0
- package/dist/src/cli/source-edit.js +116 -0
- package/dist/src/cli/sources.js +564 -0
- package/dist/src/cli/state.js +58 -0
- package/dist/src/cli/style.js +187 -0
- package/dist/src/cli/supervise.js +248 -0
- package/dist/src/cli/sync.js +94 -0
- package/dist/src/cli/ui.js +107 -0
- package/dist/src/cli/user.js +229 -0
- package/dist/src/cli/version.js +23 -0
- package/dist/src/demo/seed.js +237 -0
- package/dist/src/demo/source.js +655 -0
- package/dist/src/duck/analyze.js +113 -0
- package/dist/src/duck/cancel.js +49 -0
- package/dist/src/duck/distinct-key.js +228 -0
- package/dist/src/duck/executor-worker.js +268 -0
- package/dist/src/duck/executor.js +567 -0
- package/dist/src/duck/explain.js +238 -0
- package/dist/src/duck/functions.js +819 -0
- package/dist/src/duck/gaps.js +94 -0
- package/dist/src/duck/jsonb-order.js +175 -0
- package/dist/src/duck/jsonb.js +188 -0
- package/dist/src/duck/kept-route.js +299 -0
- package/dist/src/duck/kept.js +964 -0
- package/dist/src/duck/late.js +221 -0
- package/dist/src/duck/libc-order.js +190 -0
- package/dist/src/duck/pg-eval-worker.js +34 -0
- package/dist/src/duck/pg-eval.js +59 -0
- package/dist/src/duck/pgtree.js +378 -0
- package/dist/src/duck/plan.js +259 -0
- package/dist/src/duck/publication.js +207 -0
- package/dist/src/duck/requests.js +209 -0
- package/dist/src/duck/resolved.js +248 -0
- package/dist/src/duck/rewrite.js +5101 -0
- package/dist/src/duck/store.js +907 -0
- package/dist/src/duck/stream.js +518 -0
- package/dist/src/duck/sync.js +2929 -0
- package/dist/src/duck/to-char.js +482 -0
- package/dist/src/duck/token.js +31 -0
- package/dist/src/duck/types.js +427 -0
- package/dist/src/duck/users.js +231 -0
- package/dist/src/duck/verified.js +1001 -0
- package/dist/src/duck/views.js +150 -0
- package/dist/src/lib/database-url.js +74 -0
- package/dist/src/lib/did-you-mean.js +43 -0
- package/dist/src/lib/dotenv.js +72 -0
- package/dist/src/lib/exit-when-written.js +20 -0
- package/dist/src/lib/float-equal.js +69 -0
- package/dist/src/lib/http-json.js +22 -0
- package/dist/src/lib/log.js +161 -0
- package/dist/src/lib/pg-numeric.js +489 -0
- package/dist/src/lib/port.js +22 -0
- package/dist/src/lib/project-file.js +49 -0
- package/dist/src/lib/project-files.js +43 -0
- package/dist/src/lib/project-version.js +76 -0
- package/dist/src/lib/runnable.js +14 -0
- package/dist/src/lib/shell-word.js +4 -0
- package/dist/src/lib/timezone.js +237 -0
- package/dist/src/lib/zone-names.js +114 -0
- package/dist/src/web/app.css +76 -0
- package/dist/src/web/app.js +491 -0
- package/dist/src/web/index.html +44 -0
- package/package.json +22 -4
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE VERSION A PROJECT IS WRITTEN FOR (the journey from a laptop to production). A project's
|
|
3
|
+
* `mason.yml`, beside its `sources/` and committed with it, names the Mason version the project was
|
|
4
|
+
* written and checked with:
|
|
5
|
+
*
|
|
6
|
+
* version: 0.5.1
|
|
7
|
+
*
|
|
8
|
+
* Before 1.0 a minor version may change the project's format with no compatibility (CLAUDE.md "No backwards
|
|
9
|
+
* compatibility before v1.0"), so a project written for 0.5 and run by 0.6 would fail on whatever key moved, in words
|
|
10
|
+
* that never say the versions differ. The pin turns that into one refusal that does: the image of the ClickHouse stack
|
|
11
|
+
* this was cut from refused to start on a project of another minor version, or of a newer patch than itself
|
|
12
|
+
* (`imagePinProblem` below), and `mason init` / `mason start` refuse a CLI that is (src/cli/pin.ts). The way out is
|
|
13
|
+
* always the same pair: run the version the project names, or read the release notes and move the pin.
|
|
14
|
+
*
|
|
15
|
+
* A build that is no release (`dev` from a checkout, `main-<sha>` from main) checks nothing: it has no version to hold
|
|
16
|
+
* a project to. A project with no `mason.yml` (made by an older `mason init`) is unpinned and runs; `mason start` writes one.
|
|
17
|
+
*/
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { PROJECT_FILE, parseVersion, readProjectSettings } from "./project-file.js";
|
|
20
|
+
export { parseVersion };
|
|
21
|
+
export const PIN_FILE = PROJECT_FILE;
|
|
22
|
+
export const versionText = (v) => `${v.major}.${v.minor}.${v.patch}`;
|
|
23
|
+
/** The line a project's mason.yml is: what `mason init` writes. */
|
|
24
|
+
export function pinFileText(version) {
|
|
25
|
+
return "# The Mason version this project is written for. The mason image and the mason command refuse another\n" +
|
|
26
|
+
"# minor version (before 1.0 a minor version may change the project's format): to upgrade, read that release's\n" +
|
|
27
|
+
"# notes, change what they say, then change this line.\n" +
|
|
28
|
+
`version: ${version}\n`;
|
|
29
|
+
}
|
|
30
|
+
/** The project's pin: its version, null when it has no mason.yml, or the problem with the file (src/lib/project-file.ts
|
|
31
|
+
* reads it whole: a key it does not know is a problem here too). */
|
|
32
|
+
export function readPin(project) {
|
|
33
|
+
const read = readProjectSettings(project);
|
|
34
|
+
return "problem" in read ? read : { pin: read.pin, file: read.file };
|
|
35
|
+
}
|
|
36
|
+
/** Same release line: before 1.0 the minor version, from 1.0 the major. */
|
|
37
|
+
export function sameLine(a, b) {
|
|
38
|
+
return a.major === b.major && (a.major > 0 || a.minor === b.minor);
|
|
39
|
+
}
|
|
40
|
+
const older = (a, b) => a.major !== b.major ? a.major < b.major : a.minor !== b.minor ? a.minor < b.minor : a.patch < b.patch;
|
|
41
|
+
/**
|
|
42
|
+
* Why `running` (what reads the project: the image, or the CLI) may not run a project pinned to `pin`, or null.
|
|
43
|
+
* Allowed: the same release line, at the pin's patch or later. `what` names the reader in the message ("the mason
|
|
44
|
+
* image", "this mason command"); `run` says how to run the pinned version instead.
|
|
45
|
+
*/
|
|
46
|
+
export function pinProblem(pin, running, file, what, run) {
|
|
47
|
+
if (sameLine(pin, running) && !older(running, pin))
|
|
48
|
+
return null;
|
|
49
|
+
const p = versionText(pin), r = versionText(running);
|
|
50
|
+
if (sameLine(pin, running)) {
|
|
51
|
+
return `${file} pins Mason ${p}, and ${what} is ${r}, older than it: the project may use a fix or a key ${r} does not have. ${run}`;
|
|
52
|
+
}
|
|
53
|
+
const line = pin.major > 0 ? `${pin.major}.x` : `${pin.major}.${pin.minor}.x`;
|
|
54
|
+
return older(running, pin)
|
|
55
|
+
? `${file} pins Mason ${p}, and ${what} is ${r}: the project is written for a newer Mason. ${run}`
|
|
56
|
+
: `${file} pins Mason ${p}, and ${what} is ${r}, a newer release line than ${line}, which may read the project differently. ` +
|
|
57
|
+
`${run} Or upgrade the project: read the release notes of every version after ${p} up to ${r} (their "Before you upgrade"), ` +
|
|
58
|
+
`change what they say, then set version: ${r} in ${path.basename(file)}.`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The image's check, as the ClickHouse stack's service ran it at its start: the project's pin against the image's
|
|
62
|
+
* MASON_VERSION, or null.
|
|
63
|
+
* An image that is no release (`dev`, `main-<sha>`) holds no project to a version.
|
|
64
|
+
*/
|
|
65
|
+
export function imagePinProblem(project, imageVersion) {
|
|
66
|
+
const running = parseVersion(imageVersion ?? "");
|
|
67
|
+
if (running === null)
|
|
68
|
+
return null;
|
|
69
|
+
const read = readPin(project);
|
|
70
|
+
if ("problem" in read)
|
|
71
|
+
return read.problem;
|
|
72
|
+
if (read.pin === null)
|
|
73
|
+
return null;
|
|
74
|
+
const p = versionText(read.pin);
|
|
75
|
+
return pinProblem(read.pin, running, read.file, "this mason image", `Run the image the project names (ghcr.io/embeddable-hq/mason-data:${p}).`);
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A SCRIPT'S RUNNABLE FILE. This repo runs its TypeScript directly (Node's type stripping, no build), so
|
|
3
|
+
* every script the service starts, and every template the compiler imports, is named by its `.ts` path. The
|
|
4
|
+
* `@mason-data/cli` package cannot ship it that way — Node refuses to strip types under node_modules — so it ships the
|
|
5
|
+
* same sources compiled to `.js` beside each other (cli/build.ts). This picks whichever is there: the `.ts` in a
|
|
6
|
+
* checkout or the image, the `.js` in the package. Dependency-free.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
export function runnable(file) {
|
|
10
|
+
if (!file.endsWith(".ts") || fs.existsSync(file))
|
|
11
|
+
return file;
|
|
12
|
+
const js = `${file.slice(0, -3)}.js`;
|
|
13
|
+
return fs.existsSync(js) ? js : file;
|
|
14
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TIME ZONES, as a request names them (`timezone: "Asia/Kolkata"`, issue #435: "today" or "per day" has to mean the
|
|
3
|
+
* customer's day). An IANA name, checked against the zone names this product accepts, exactly as spelled
|
|
4
|
+
* (src/lib/zone-names.ts) and the zones this runtime can do arithmetic in (`Intl`), and the calendar arithmetic a
|
|
5
|
+
* range and a bucket need in it: the UTC instant of a local wall-clock time, and the local parts of an instant.
|
|
6
|
+
*
|
|
7
|
+
* Pure, and free of any dependency: the API's grammar, the fill and the SDK-side generator use it. The zone a
|
|
8
|
+
* ClickHouse function takes is the same name, spliced only after `isZone` accepted it (the name is from a closed
|
|
9
|
+
* set, so it cannot carry SQL).
|
|
10
|
+
*/
|
|
11
|
+
import { ZONE_NAMES } from "./zone-names.js";
|
|
12
|
+
/** The name a request writes for UTC: no zone at all, as every bucket and range was before zones. */
|
|
13
|
+
export const UTC = "UTC";
|
|
14
|
+
const NAME_RE = /^[A-Za-z][A-Za-z0-9_+\-]*(\/[A-Za-z0-9_+\-]+){0,2}$/;
|
|
15
|
+
const known = new Map();
|
|
16
|
+
/** Whether `z` is a zone of ZONE_NAMES as written AND this runtime can compute in: `Europe/London`, `Asia/Kolkata`,
|
|
17
|
+
* `UTC` — never `asia/kolkata` or `utc` (ZONE_NAMES is case-sensitive, `Intl` is not). */
|
|
18
|
+
export function isZone(z) {
|
|
19
|
+
if (typeof z !== "string" || !NAME_RE.test(z) || !ZONE_NAMES.has(z))
|
|
20
|
+
return false;
|
|
21
|
+
let ok = known.get(z);
|
|
22
|
+
if (ok === undefined) {
|
|
23
|
+
try {
|
|
24
|
+
new Intl.DateTimeFormat("en-US", { timeZone: z });
|
|
25
|
+
ok = true;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
// the one way Intl says "no such zone": a RangeError; anything else is not a verdict on the name
|
|
29
|
+
if (!(err instanceof RangeError))
|
|
30
|
+
throw err;
|
|
31
|
+
ok = false;
|
|
32
|
+
}
|
|
33
|
+
known.set(z, ok);
|
|
34
|
+
}
|
|
35
|
+
return ok;
|
|
36
|
+
}
|
|
37
|
+
let lowered;
|
|
38
|
+
/** The zone as ZONE_NAMES spells it, from a name in any case (`utc`, `europe/london`: the review of #443 met a 400
|
|
39
|
+
* `unknown_timezone` for "utc"); null for a name no spelling of which is a zone. */
|
|
40
|
+
export function canonicalZone(z) {
|
|
41
|
+
if (typeof z !== "string")
|
|
42
|
+
return null;
|
|
43
|
+
const name = z;
|
|
44
|
+
const exact = isZone(name);
|
|
45
|
+
if (exact)
|
|
46
|
+
return name;
|
|
47
|
+
lowered ??= new Map([...ZONE_NAMES].map((n) => [n.toLowerCase(), n]));
|
|
48
|
+
const c = lowered.get(name.toLowerCase());
|
|
49
|
+
return c !== undefined && isZone(c) ? c : null;
|
|
50
|
+
}
|
|
51
|
+
/** UTC and its aliases (compared in any case): a zone that changes nothing. One list, read by isUtc and by the SQL that tests the session zone at execution. */
|
|
52
|
+
export const UTC_NAMES = ["UTC", "Etc/UTC", "Etc/GMT", "GMT", "Etc/Universal", "Universal", "Zulu", "Etc/Zulu"];
|
|
53
|
+
export const isUtc = (z) => UTC_NAMES.some((n) => n.toLowerCase() === z.toLowerCase());
|
|
54
|
+
/**
|
|
55
|
+
* THE ZONE OF A POSTGRES `AT TIME ZONE '<name>'`, as ClickHouse spells it, where Postgres and ClickHouse read the name
|
|
56
|
+
* alike (Cube's zoned bucket): an Area/Location name (`Europe/London`, `America/Sao_Paulo`, in any case) or
|
|
57
|
+
* a UTC alias. Postgres reads a name as a zone ABBREVIATION first (`CET` is a fixed +01 there, tzdata's `CET` keeps a
|
|
58
|
+
* summer time; `EST`, `MST`, `EET`, `MET` likewise) and a POSIX string (`+02`, `<-03>+3`) with its own sign rule, so a
|
|
59
|
+
* name of neither form is refused, never read as tzdata reads it. A name nobody knows is Postgres's own 22023.
|
|
60
|
+
* The rewrite and the functions of the copy (src/duck/rewrite.ts, src/duck/functions.ts) read it here.
|
|
61
|
+
*/
|
|
62
|
+
export function atTimeZoneOf(name) {
|
|
63
|
+
const zone = canonicalZone(name);
|
|
64
|
+
const examples = "Europe/London, America/Sao_Paulo, UTC";
|
|
65
|
+
// Postgres's abbreviations for UTC itself (offset 0, no summer time): UTC, whatever tzdata calls them
|
|
66
|
+
if (/^(UT|Z|ZULU)$/i.test(name))
|
|
67
|
+
return { zone: UTC };
|
|
68
|
+
if (isUtc(name) && zone !== null)
|
|
69
|
+
return { zone };
|
|
70
|
+
if (name.includes("/") && zone !== null)
|
|
71
|
+
return { zone };
|
|
72
|
+
if (zone !== null || /^[A-Za-z]{3,5}$/.test(name) || /^[<+\-0-9]/.test(name) || /^[A-Za-z]{3,}[+-]?\d/.test(name)) {
|
|
73
|
+
return { problem: `time zone "${name}": Postgres reads a name without a "/" as an abbreviation or a POSIX zone first, with a fixed offset, where the IANA zone of that name may move; write an IANA name (${examples})`, sqlstate: "0A000" };
|
|
74
|
+
}
|
|
75
|
+
return { problem: `time zone "${name}" not recognized: write an IANA name (${examples})`, sqlstate: "22023" };
|
|
76
|
+
}
|
|
77
|
+
/** A few zones every app serves, for a message that lists choices. */
|
|
78
|
+
export const COMMON_ZONES = ["UTC", "America/New_York", "America/Chicago", "America/Los_Angeles", "Europe/London", "Europe/Berlin", "Asia/Kolkata", "Asia/Singapore", "Asia/Tokyo", "Australia/Sydney", "Australia/Adelaide"];
|
|
79
|
+
const formatters = new Map();
|
|
80
|
+
function formatter(z) {
|
|
81
|
+
let f = formatters.get(z);
|
|
82
|
+
if (!f) {
|
|
83
|
+
f = new Intl.DateTimeFormat("en-US", { timeZone: z, hourCycle: "h23", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
84
|
+
formatters.set(z, f);
|
|
85
|
+
}
|
|
86
|
+
return f;
|
|
87
|
+
}
|
|
88
|
+
export function localParts(ms, z) {
|
|
89
|
+
const parts = {};
|
|
90
|
+
for (const p of formatter(z).formatToParts(new Date(ms)))
|
|
91
|
+
if (p.type !== "literal")
|
|
92
|
+
parts[p.type] = Number(p.value);
|
|
93
|
+
const { year, month, day, hour, minute, second } = parts;
|
|
94
|
+
const weekday = ((new Date(Date.UTC(year, month - 1, day)).getUTCDay() + 6) % 7) + 1;
|
|
95
|
+
return { year: year, month: month, day: day, hour: hour, minute: minute, second: second, weekday };
|
|
96
|
+
}
|
|
97
|
+
/** The zone's offset from UTC at the instant `ms`, in SECONDS: a historic offset has them (Monrovia was −0:44:30 until
|
|
98
|
+
* 1972, Amsterdam +0:19:32 until 1937), Intl keeps them and so does Postgres, so a reading of a time keeps them too. */
|
|
99
|
+
export function offsetSeconds(ms, z) {
|
|
100
|
+
const p = localParts(ms, z);
|
|
101
|
+
const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
102
|
+
return Math.round((asUtc - Math.floor(ms / 1000) * 1000) / 1000);
|
|
103
|
+
}
|
|
104
|
+
/** The zone's offset from UTC at the instant `ms`, in whole minutes (Kolkata +330, New York -240 in summer): what the
|
|
105
|
+
* hour arithmetic of a range and a bucket reads, which is only asked in a zone a whole number of hours from UTC. */
|
|
106
|
+
export function offsetMinutes(ms, z) {
|
|
107
|
+
return Math.round(offsetSeconds(ms, z) / 60);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The instants a wall clock (`wall`: its fields written as if UTC) could mean in `z`: `candidates`, one per offset the
|
|
111
|
+
* zone has around the time (a zone changes at most once in three days), and `valid`, those of them at which the zone's
|
|
112
|
+
* offset puts the clock there — one for an ordinary time, two in the hour a clock falls back over, none in the hour it
|
|
113
|
+
* springs forward over.
|
|
114
|
+
*/
|
|
115
|
+
function readings(z, wall) {
|
|
116
|
+
const offsets = new Set([offsetSeconds(wall - 129_600_000, z), offsetSeconds(wall, z), offsetSeconds(wall + 129_600_000, z)]);
|
|
117
|
+
const candidates = [...offsets].map((o) => wall - o * 1000);
|
|
118
|
+
return { valid: candidates.filter((t) => offsetSeconds(t, z) * 1000 === wall - t), candidates };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The UTC instant of a local wall-clock TIME in zone `z`: the one reading of a time with no offset, as Postgres reads
|
|
122
|
+
* `timestamp '…' AT TIME ZONE z` (an app's SQL is Postgres SQL, the Postgres API is held to Postgres, and the oracle
|
|
123
|
+
* resolves every window bound there), on every path alike — the Postgres API's literals, and in the ClickHouse stack
|
|
124
|
+
* this was cut from its HTTP API's ranges, buckets, fill and compare — to the second of a historic offset
|
|
125
|
+
* (`offsetSeconds`: 1970-01-01 00:00 in Monrovia is 00:44:30Z, as Postgres has it). A time that does not exist there
|
|
126
|
+
* (the hour a clock springs forward over) is moved forward by the gap (02:30 on a 02:00 -> 03:00 night is 03:30); a time that happens twice (the hour it falls back over)
|
|
127
|
+
* is the LATER of the two, the offset in force after the change (2026-11-01 01:30 in New York is 06:30Z, 01:30 EST).
|
|
128
|
+
* The doubled hour was once read at the offset in force at the wall clock taken as UTC: the earlier instant
|
|
129
|
+
* west of UTC (05:30Z) and the later east of it, so the HTTP API and Postgres disagreed on the same body for every zone
|
|
130
|
+
* west of UTC. ClickHouse's own `toDateTime64(text, 3, z)` reads the doubled hour as the EARLIER instant and moves a
|
|
131
|
+
* gap BACK, so no path hands it a zone-less literal in a zone: every bound is resolved here and sent as an instant.
|
|
132
|
+
* A DATE is not a time: its first instant is `dayStart`. Pinned against Postgres by the dashboard tests of the
|
|
133
|
+
* ClickHouse stack this was cut from.
|
|
134
|
+
* `ms` is the rest of the minute (seconds and fraction), which never changes which reading applies.
|
|
135
|
+
*/
|
|
136
|
+
export function utcOf(z, year, month, day, hour = 0, minute = 0, ms = 0) {
|
|
137
|
+
// AS POSTGRES READS IT (`DetermineTimeZoneOffset`, verified on Postgres 18.6), so the Postgres API reads a
|
|
138
|
+
// wall-clock time with this one function too (it once had a twin, `postgresUtcOf`, written when this one
|
|
139
|
+
// met a doubled time on whichever pass came first)
|
|
140
|
+
const { valid, candidates } = readings(z, Date.UTC(year, month - 1, day, hour, minute) + ms);
|
|
141
|
+
// twice: the later; never: the offset before the gap, which is also the later reading
|
|
142
|
+
return Math.max(...(valid.length > 0 ? valid : candidates));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The first instant of the local DATE in `z`: what a date bound (`{"from": "2026-11-01"}`), `today`, the edges of a
|
|
146
|
+
* day, a week and a month, and a `.day` bucket's start mean. Its midnight; the EARLIER one where a clock falls back
|
|
147
|
+
* over midnight (Havana, the Azores: the local date begins at the first 00:00, and a `.day` bucket — ClickHouse's
|
|
148
|
+
* `toDate(col, z)`, Postgres's `(col AT TIME ZONE z)::date` — holds every row from it, so a range starting an hour
|
|
149
|
+
* later would miss rows its own bucket holds; Postgres's `date::timestamptz` cast reads midnight as a wall-clock TIME,
|
|
150
|
+
* the later one, which is why this is not `utcOf`); and the first instant after a midnight a clock skips (Santiago,
|
|
151
|
+
* 2026-09-06: 01:00). Decided in the review of #495; that stack's oracle said it in Postgres's words.
|
|
152
|
+
*/
|
|
153
|
+
export function dayStart(z, year, month, day) {
|
|
154
|
+
const { valid, candidates } = readings(z, Date.UTC(year, month - 1, day));
|
|
155
|
+
return valid.length > 0 ? Math.min(...valid) : Math.max(...candidates);
|
|
156
|
+
}
|
|
157
|
+
const wholeHours = new Map();
|
|
158
|
+
/**
|
|
159
|
+
* Whether every offset `z` has had since 1970 and will have over the next two years is a whole number of hours, so a
|
|
160
|
+
* UTC hour is a local hour there: what lets an hour stored in UTC be re-bucketed in the zone (Kolkata, +5:30, and
|
|
161
|
+
* Adelaide, +9:30, are not). Since 1970, not a recent window: an `all_time` query reaches every stored hour, and
|
|
162
|
+
* Caracas was −4:30 until 2016 (review of #443). Daily, so no half-hour season is stepped over; once per zone.
|
|
163
|
+
*/
|
|
164
|
+
export function isWholeHourZone(z, now = Date.now()) {
|
|
165
|
+
let ok = wholeHours.get(z);
|
|
166
|
+
if (ok === undefined) {
|
|
167
|
+
ok = true;
|
|
168
|
+
for (let t = 0; t < now + 2 * 365 * 86_400_000; t += 86_400_000) {
|
|
169
|
+
if (offsetMinutes(t, z) % 60 !== 0) {
|
|
170
|
+
ok = false;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
wholeHours.set(z, ok);
|
|
175
|
+
}
|
|
176
|
+
return ok;
|
|
177
|
+
}
|
|
178
|
+
/** Why no time stored in UTC can be re-bucketed in `z`, or null when hours can: the finest grain anything stores is an
|
|
179
|
+
* hour, so a zone that is not a whole number of hours from UTC is answered from the table's own rows, never from storage.
|
|
180
|
+
* ONE sentence for the compiler's classification, its coverage and `mason status` (the hands-on review of #451). */
|
|
181
|
+
export function hourFoldProblem(z) {
|
|
182
|
+
return isWholeHourZone(z) ? null : `${z} is not a whole number of hours from UTC, so a stored UTC hour is not one of its hours`;
|
|
183
|
+
}
|
|
184
|
+
const WEEK = 7 * 86_400_000;
|
|
185
|
+
/** The years a change is looked for in: the times a DateTime64 holds (1900 to 2299), as every range and row is. */
|
|
186
|
+
const FIRST_YEAR = 1900, LAST_YEAR = 2299;
|
|
187
|
+
const changesByYear = new Map();
|
|
188
|
+
/** A zone's changes in one UTC year: its offset read once a week, and a week it moved in bisected to the second.
|
|
189
|
+
* Once per zone and year. A change undone within the same week is not seen: the tz database has no such week. */
|
|
190
|
+
function changesInYear(z, year) {
|
|
191
|
+
const key = `${z}\u0000${year}`;
|
|
192
|
+
let out = changesByYear.get(key);
|
|
193
|
+
if (out !== undefined)
|
|
194
|
+
return out;
|
|
195
|
+
out = [];
|
|
196
|
+
const end = Date.UTC(year + 1, 0, 1);
|
|
197
|
+
let t = Date.UTC(year, 0, 1), off = offsetMinutes(t, z);
|
|
198
|
+
while (t < end) {
|
|
199
|
+
const u = Math.min(t + WEEK, end);
|
|
200
|
+
if (offsetMinutes(u, z) === off) {
|
|
201
|
+
t = u;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let lo = t, hi = u;
|
|
205
|
+
while (hi - lo > 1000) {
|
|
206
|
+
const mid = lo + Math.floor((hi - lo) / 2000) * 1000;
|
|
207
|
+
if (offsetMinutes(mid, z) === off)
|
|
208
|
+
lo = mid;
|
|
209
|
+
else
|
|
210
|
+
hi = mid;
|
|
211
|
+
}
|
|
212
|
+
const after = offsetMinutes(hi, z);
|
|
213
|
+
out.push({ at: hi, before: off, after });
|
|
214
|
+
t = hi;
|
|
215
|
+
off = after;
|
|
216
|
+
}
|
|
217
|
+
changesByYear.set(key, out);
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
/** The changes of `z`'s offset after `from` and before `to`, in order (none for UTC; none outside 1900 to 2299). */
|
|
221
|
+
export function offsetChanges(z, from, to) {
|
|
222
|
+
if (isUtc(z) || !(to > from))
|
|
223
|
+
return [];
|
|
224
|
+
const out = [];
|
|
225
|
+
const y0 = Math.max(FIRST_YEAR, new Date(Math.max(from, Date.UTC(FIRST_YEAR, 0, 1))).getUTCFullYear());
|
|
226
|
+
const y1 = Math.min(LAST_YEAR, new Date(Math.min(to, Date.UTC(LAST_YEAR, 11, 31))).getUTCFullYear());
|
|
227
|
+
for (let y = y0; y <= y1; y++)
|
|
228
|
+
for (const c of changesInYear(z, y))
|
|
229
|
+
if (c.at > from && c.at < to)
|
|
230
|
+
out.push(c);
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
234
|
+
/** A local date as the API writes a Date: `2026-09-01`. */
|
|
235
|
+
export const dateText = (y, m, d) => `${pad(y, 4)}-${pad(m)}-${pad(d)}`;
|
|
236
|
+
/** An instant as ClickHouse's ISO output writes a DateTime: `2026-09-01T10:30:00Z`. */
|
|
237
|
+
export const instantText = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE IANA ZONE NAMES THIS PRODUCT ACCEPTS — in `AT TIME ZONE '<name>'`, `SET TIME ZONE '<name>'` and mason.yml's
|
|
3
|
+
* `timezones` — exactly as spelled: a name is case-sensitive here (`asia/kolkata` and `utc` are not in it;
|
|
4
|
+
* `canonicalZone`, src/lib/timezone.ts, finds the spelling a name in another case stands for).
|
|
5
|
+
*
|
|
6
|
+
* The list was taken from ClickHouse 26.8's `system.time_zones` in 2026-10, when this product was cut from a stack that
|
|
7
|
+
* ran on ClickHouse, and it is kept because `Intl` alone is no check: ICU knows zones its tzdata does not
|
|
8
|
+
* (`SystemV/AST4`), so `Intl` said yes to names the engine underneath then refused, and the caller met a 502 for what
|
|
9
|
+
* is a 400 (review of #443). Nothing holds it to a server any more: a zone tzdata adds is added here by hand.
|
|
10
|
+
*/
|
|
11
|
+
export const ZONE_NAMES = new Set([
|
|
12
|
+
"Africa/Abidjan", "Africa/Accra", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Asmara", "Africa/Asmera",
|
|
13
|
+
"Africa/Bamako", "Africa/Bangui", "Africa/Banjul", "Africa/Bissau", "Africa/Blantyre", "Africa/Brazzaville",
|
|
14
|
+
"Africa/Bujumbura", "Africa/Cairo", "Africa/Casablanca", "Africa/Ceuta", "Africa/Conakry", "Africa/Dakar",
|
|
15
|
+
"Africa/Dar_es_Salaam", "Africa/Djibouti", "Africa/Douala", "Africa/El_Aaiun", "Africa/Freetown", "Africa/Gaborone",
|
|
16
|
+
"Africa/Harare", "Africa/Johannesburg", "Africa/Juba", "Africa/Kampala", "Africa/Khartoum", "Africa/Kigali",
|
|
17
|
+
"Africa/Kinshasa", "Africa/Lagos", "Africa/Libreville", "Africa/Lome", "Africa/Luanda", "Africa/Lubumbashi",
|
|
18
|
+
"Africa/Lusaka", "Africa/Malabo", "Africa/Maputo", "Africa/Maseru", "Africa/Mbabane", "Africa/Mogadishu",
|
|
19
|
+
"Africa/Monrovia", "Africa/Nairobi", "Africa/Ndjamena", "Africa/Niamey", "Africa/Nouakchott", "Africa/Ouagadougou",
|
|
20
|
+
"Africa/Porto-Novo", "Africa/Sao_Tome", "Africa/Timbuktu", "Africa/Tripoli", "Africa/Tunis", "Africa/Windhoek",
|
|
21
|
+
"America/Adak", "America/Anchorage", "America/Anguilla", "America/Antigua", "America/Araguaina",
|
|
22
|
+
"America/Argentina/Buenos_Aires", "America/Argentina/Catamarca", "America/Argentina/ComodRivadavia",
|
|
23
|
+
"America/Argentina/Cordoba", "America/Argentina/Jujuy", "America/Argentina/La_Rioja", "America/Argentina/Mendoza",
|
|
24
|
+
"America/Argentina/Rio_Gallegos", "America/Argentina/Salta", "America/Argentina/San_Juan",
|
|
25
|
+
"America/Argentina/San_Luis", "America/Argentina/Tucuman", "America/Argentina/Ushuaia", "America/Aruba",
|
|
26
|
+
"America/Asuncion", "America/Atikokan", "America/Atka", "America/Bahia", "America/Bahia_Banderas",
|
|
27
|
+
"America/Barbados", "America/Belem", "America/Belize", "America/Blanc-Sablon", "America/Boa_Vista",
|
|
28
|
+
"America/Bogota", "America/Boise", "America/Buenos_Aires", "America/Cambridge_Bay", "America/Campo_Grande",
|
|
29
|
+
"America/Cancun", "America/Caracas", "America/Catamarca", "America/Cayenne", "America/Cayman", "America/Chicago",
|
|
30
|
+
"America/Chihuahua", "America/Ciudad_Juarez", "America/Coral_Harbour", "America/Cordoba", "America/Costa_Rica",
|
|
31
|
+
"America/Coyhaique", "America/Creston", "America/Cuiaba", "America/Curacao", "America/Danmarkshavn",
|
|
32
|
+
"America/Dawson", "America/Dawson_Creek", "America/Denver", "America/Detroit", "America/Dominica",
|
|
33
|
+
"America/Edmonton", "America/Eirunepe", "America/El_Salvador", "America/Ensenada", "America/Fort_Nelson",
|
|
34
|
+
"America/Fort_Wayne", "America/Fortaleza", "America/Glace_Bay", "America/Godthab", "America/Goose_Bay",
|
|
35
|
+
"America/Grand_Turk", "America/Grenada", "America/Guadeloupe", "America/Guatemala", "America/Guayaquil",
|
|
36
|
+
"America/Guyana", "America/Halifax", "America/Havana", "America/Hermosillo", "America/Indiana/Indianapolis",
|
|
37
|
+
"America/Indiana/Knox", "America/Indiana/Marengo", "America/Indiana/Petersburg", "America/Indiana/Tell_City",
|
|
38
|
+
"America/Indiana/Vevay", "America/Indiana/Vincennes", "America/Indiana/Winamac", "America/Indianapolis",
|
|
39
|
+
"America/Inuvik", "America/Iqaluit", "America/Jamaica", "America/Jujuy", "America/Juneau",
|
|
40
|
+
"America/Kentucky/Louisville", "America/Kentucky/Monticello", "America/Knox_IN", "America/Kralendijk",
|
|
41
|
+
"America/La_Paz", "America/Lima", "America/Los_Angeles", "America/Louisville", "America/Lower_Princes",
|
|
42
|
+
"America/Maceio", "America/Managua", "America/Manaus", "America/Marigot", "America/Martinique", "America/Matamoros",
|
|
43
|
+
"America/Mazatlan", "America/Mendoza", "America/Menominee", "America/Merida", "America/Metlakatla",
|
|
44
|
+
"America/Mexico_City", "America/Miquelon", "America/Moncton", "America/Monterrey", "America/Montevideo",
|
|
45
|
+
"America/Montreal", "America/Montserrat", "America/Nassau", "America/New_York", "America/Nipigon", "America/Nome",
|
|
46
|
+
"America/Noronha", "America/North_Dakota/Beulah", "America/North_Dakota/Center", "America/North_Dakota/New_Salem",
|
|
47
|
+
"America/Nuuk", "America/Ojinaga", "America/Panama", "America/Pangnirtung", "America/Paramaribo", "America/Phoenix",
|
|
48
|
+
"America/Port-au-Prince", "America/Port_of_Spain", "America/Porto_Acre", "America/Porto_Velho",
|
|
49
|
+
"America/Puerto_Rico", "America/Punta_Arenas", "America/Rainy_River", "America/Rankin_Inlet", "America/Recife",
|
|
50
|
+
"America/Regina", "America/Resolute", "America/Rio_Branco", "America/Rosario", "America/Santa_Isabel",
|
|
51
|
+
"America/Santarem", "America/Santiago", "America/Santo_Domingo", "America/Sao_Paulo", "America/Scoresbysund",
|
|
52
|
+
"America/Shiprock", "America/Sitka", "America/St_Barthelemy", "America/St_Johns", "America/St_Kitts",
|
|
53
|
+
"America/St_Lucia", "America/St_Thomas", "America/St_Vincent", "America/Swift_Current", "America/Tegucigalpa",
|
|
54
|
+
"America/Thule", "America/Thunder_Bay", "America/Tijuana", "America/Toronto", "America/Tortola",
|
|
55
|
+
"America/Vancouver", "America/Virgin", "America/Whitehorse", "America/Winnipeg", "America/Yakutat",
|
|
56
|
+
"America/Yellowknife", "Antarctica/Casey", "Antarctica/Davis", "Antarctica/DumontDUrville", "Antarctica/Macquarie",
|
|
57
|
+
"Antarctica/Mawson", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", "Antarctica/South_Pole",
|
|
58
|
+
"Antarctica/Syowa", "Antarctica/Troll", "Antarctica/Vostok", "Arctic/Longyearbyen", "Asia/Aden", "Asia/Almaty",
|
|
59
|
+
"Asia/Amman", "Asia/Anadyr", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Ashgabat", "Asia/Ashkhabad", "Asia/Atyrau",
|
|
60
|
+
"Asia/Baghdad", "Asia/Bahrain", "Asia/Baku", "Asia/Bangkok", "Asia/Barnaul", "Asia/Beirut", "Asia/Bishkek",
|
|
61
|
+
"Asia/Brunei", "Asia/Calcutta", "Asia/Chita", "Asia/Choibalsan", "Asia/Chongqing", "Asia/Chungking", "Asia/Colombo",
|
|
62
|
+
"Asia/Dacca", "Asia/Damascus", "Asia/Dhaka", "Asia/Dili", "Asia/Dubai", "Asia/Dushanbe", "Asia/Famagusta",
|
|
63
|
+
"Asia/Gaza", "Asia/Harbin", "Asia/Hebron", "Asia/Ho_Chi_Minh", "Asia/Hong_Kong", "Asia/Hovd", "Asia/Irkutsk",
|
|
64
|
+
"Asia/Istanbul", "Asia/Jakarta", "Asia/Jayapura", "Asia/Jerusalem", "Asia/Kabul", "Asia/Kamchatka", "Asia/Karachi",
|
|
65
|
+
"Asia/Kashgar", "Asia/Kathmandu", "Asia/Katmandu", "Asia/Khandyga", "Asia/Kolkata", "Asia/Krasnoyarsk",
|
|
66
|
+
"Asia/Kuala_Lumpur", "Asia/Kuching", "Asia/Kuwait", "Asia/Macao", "Asia/Macau", "Asia/Magadan", "Asia/Makassar",
|
|
67
|
+
"Asia/Manila", "Asia/Muscat", "Asia/Nicosia", "Asia/Novokuznetsk", "Asia/Novosibirsk", "Asia/Omsk", "Asia/Oral",
|
|
68
|
+
"Asia/Phnom_Penh", "Asia/Pontianak", "Asia/Pyongyang", "Asia/Qatar", "Asia/Qostanay", "Asia/Qyzylorda",
|
|
69
|
+
"Asia/Rangoon", "Asia/Riyadh", "Asia/Saigon", "Asia/Sakhalin", "Asia/Samarkand", "Asia/Seoul", "Asia/Shanghai",
|
|
70
|
+
"Asia/Singapore", "Asia/Srednekolymsk", "Asia/Taipei", "Asia/Tashkent", "Asia/Tbilisi", "Asia/Tehran",
|
|
71
|
+
"Asia/Tel_Aviv", "Asia/Thimbu", "Asia/Thimphu", "Asia/Tokyo", "Asia/Tomsk", "Asia/Ujung_Pandang",
|
|
72
|
+
"Asia/Ulaanbaatar", "Asia/Ulan_Bator", "Asia/Urumqi", "Asia/Ust-Nera", "Asia/Vientiane", "Asia/Vladivostok",
|
|
73
|
+
"Asia/Yakutsk", "Asia/Yangon", "Asia/Yekaterinburg", "Asia/Yerevan", "Atlantic/Azores", "Atlantic/Bermuda",
|
|
74
|
+
"Atlantic/Canary", "Atlantic/Cape_Verde", "Atlantic/Faeroe", "Atlantic/Faroe", "Atlantic/Jan_Mayen",
|
|
75
|
+
"Atlantic/Madeira", "Atlantic/Reykjavik", "Atlantic/South_Georgia", "Atlantic/St_Helena", "Atlantic/Stanley",
|
|
76
|
+
"Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken_Hill", "Australia/Canberra",
|
|
77
|
+
"Australia/Currie", "Australia/Darwin", "Australia/Eucla", "Australia/Hobart", "Australia/LHI",
|
|
78
|
+
"Australia/Lindeman", "Australia/Lord_Howe", "Australia/Melbourne", "Australia/NSW", "Australia/North",
|
|
79
|
+
"Australia/Perth", "Australia/Queensland", "Australia/South", "Australia/Sydney", "Australia/Tasmania",
|
|
80
|
+
"Australia/Victoria", "Australia/West", "Australia/Yancowinna", "Brazil/Acre", "Brazil/DeNoronha", "Brazil/East",
|
|
81
|
+
"Brazil/West", "CET", "CST6CDT", "Canada/Atlantic", "Canada/Central", "Canada/Eastern", "Canada/Mountain",
|
|
82
|
+
"Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "Chile/Continental",
|
|
83
|
+
"Chile/EasterIsland", "Cuba", "EET", "EST", "EST5EDT", "Egypt", "Eire", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1",
|
|
84
|
+
"Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", "Etc/GMT+5", "Etc/GMT+6",
|
|
85
|
+
"Etc/GMT+7", "Etc/GMT+8", "Etc/GMT+9", "Etc/GMT-0", "Etc/GMT-1", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12",
|
|
86
|
+
"Etc/GMT-13", "Etc/GMT-14", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7",
|
|
87
|
+
"Etc/GMT-8", "Etc/GMT-9", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu",
|
|
88
|
+
"Europe/Amsterdam", "Europe/Andorra", "Europe/Astrakhan", "Europe/Athens", "Europe/Belfast", "Europe/Belgrade",
|
|
89
|
+
"Europe/Berlin", "Europe/Bratislava", "Europe/Brussels", "Europe/Bucharest", "Europe/Budapest", "Europe/Busingen",
|
|
90
|
+
"Europe/Chisinau", "Europe/Copenhagen", "Europe/Dublin", "Europe/Gibraltar", "Europe/Guernsey", "Europe/Helsinki",
|
|
91
|
+
"Europe/Isle_of_Man", "Europe/Istanbul", "Europe/Jersey", "Europe/Kaliningrad", "Europe/Kiev", "Europe/Kirov",
|
|
92
|
+
"Europe/Kyiv", "Europe/Lisbon", "Europe/Ljubljana", "Europe/London", "Europe/Luxembourg", "Europe/Madrid",
|
|
93
|
+
"Europe/Malta", "Europe/Mariehamn", "Europe/Minsk", "Europe/Monaco", "Europe/Moscow", "Europe/Nicosia",
|
|
94
|
+
"Europe/Oslo", "Europe/Paris", "Europe/Podgorica", "Europe/Prague", "Europe/Riga", "Europe/Rome", "Europe/Samara",
|
|
95
|
+
"Europe/San_Marino", "Europe/Sarajevo", "Europe/Saratov", "Europe/Simferopol", "Europe/Skopje", "Europe/Sofia",
|
|
96
|
+
"Europe/Stockholm", "Europe/Tallinn", "Europe/Tirane", "Europe/Tiraspol", "Europe/Ulyanovsk", "Europe/Uzhgorod",
|
|
97
|
+
"Europe/Vaduz", "Europe/Vatican", "Europe/Vienna", "Europe/Vilnius", "Europe/Volgograd", "Europe/Warsaw",
|
|
98
|
+
"Europe/Zagreb", "Europe/Zaporozhye", "Europe/Zurich", "Factory", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0",
|
|
99
|
+
"Greenwich", "HST", "Hongkong", "Iceland", "Indian/Antananarivo", "Indian/Chagos", "Indian/Christmas",
|
|
100
|
+
"Indian/Cocos", "Indian/Comoro", "Indian/Kerguelen", "Indian/Mahe", "Indian/Maldives", "Indian/Mauritius",
|
|
101
|
+
"Indian/Mayotte", "Indian/Reunion", "Iran", "Israel", "Jamaica", "Japan", "Kwajalein", "Libya", "MET", "MST",
|
|
102
|
+
"MST7MDT", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General", "NZ", "NZ-CHAT", "Navajo", "PRC", "PST8PDT",
|
|
103
|
+
"Pacific/Apia", "Pacific/Auckland", "Pacific/Bougainville", "Pacific/Chatham", "Pacific/Chuuk", "Pacific/Easter",
|
|
104
|
+
"Pacific/Efate", "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Fiji", "Pacific/Funafuti", "Pacific/Galapagos",
|
|
105
|
+
"Pacific/Gambier", "Pacific/Guadalcanal", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Kanton",
|
|
106
|
+
"Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", "Pacific/Majuro", "Pacific/Marquesas",
|
|
107
|
+
"Pacific/Midway", "Pacific/Nauru", "Pacific/Niue", "Pacific/Norfolk", "Pacific/Noumea", "Pacific/Pago_Pago",
|
|
108
|
+
"Pacific/Palau", "Pacific/Pitcairn", "Pacific/Pohnpei", "Pacific/Ponape", "Pacific/Port_Moresby",
|
|
109
|
+
"Pacific/Rarotonga", "Pacific/Saipan", "Pacific/Samoa", "Pacific/Tahiti", "Pacific/Tarawa", "Pacific/Tongatapu",
|
|
110
|
+
"Pacific/Truk", "Pacific/Wake", "Pacific/Wallis", "Pacific/Yap", "Poland", "Portugal", "ROC", "ROK", "Singapore",
|
|
111
|
+
"Turkey", "UCT", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern",
|
|
112
|
+
"US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa", "UTC", "Universal",
|
|
113
|
+
"W-SU", "WET", "Zulu",
|
|
114
|
+
]);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* The web page of mason start (src/web/app.js). The CLI's palette (src/cli/style.ts): bold for a heading, the type
|
|
2
|
+
colour for what you can type, dim for a description, green / red / yellow for a state only. No font is fetched:
|
|
3
|
+
the system's, under the server's default-src 'self'. */
|
|
4
|
+
:root {
|
|
5
|
+
--bg: #fbfbfa; --fg: #1d1d1b; --dim: #6b6b66; --line: #e3e2de; --panel: #ffffff;
|
|
6
|
+
--code: #0b6e8a; --ok: #1f7a3a; --bad: #b42318; --warn: #8a5a00; --bar: #0b6e8a;
|
|
7
|
+
color-scheme: light dark;
|
|
8
|
+
}
|
|
9
|
+
@media (prefers-color-scheme: dark) {
|
|
10
|
+
:root { --bg: #161615; --fg: #e8e7e3; --dim: #9a9993; --line: #2e2e2b; --panel: #1e1e1c;
|
|
11
|
+
--code: #5cc3df; --ok: #5fcf80; --bad: #ff7b6e; --warn: #e9b64a; --bar: #5cc3df; }
|
|
12
|
+
}
|
|
13
|
+
* { box-sizing: border-box; }
|
|
14
|
+
body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
15
|
+
header, main, #poll { max-width: 1040px; margin: 0 auto; padding: 0 16px; }
|
|
16
|
+
header { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px; padding-top: 24px; }
|
|
17
|
+
h1 { font-size: 22px; margin: 0; }
|
|
18
|
+
.where { margin: 0; color: var(--dim); }
|
|
19
|
+
.updated { margin: 0 0 0 auto; color: var(--dim); font-size: 13px; }
|
|
20
|
+
section { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 4px 20px 16px; margin: 16px 0; }
|
|
21
|
+
h2 { font-size: 17px; margin: 14px 0 10px; }
|
|
22
|
+
h3 { font-size: 15px; margin: 18px 0 6px; }
|
|
23
|
+
code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }
|
|
24
|
+
code { color: var(--code); overflow-wrap: anywhere; }
|
|
25
|
+
pre { white-space: pre-wrap; margin: 6px 0 0; color: var(--dim); }
|
|
26
|
+
.dim, .lead { color: var(--dim); }
|
|
27
|
+
.facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 0; }
|
|
28
|
+
.facts dt { color: var(--dim); }
|
|
29
|
+
.facts dd { margin: 0; overflow-wrap: anywhere; }
|
|
30
|
+
table { width: 100%; border-collapse: collapse; }
|
|
31
|
+
th, td { text-align: left; vertical-align: top; padding: 6px 8px 6px 0; border-top: 1px solid var(--line); font-weight: normal; }
|
|
32
|
+
thead th { border-top: 0; color: var(--dim); font-size: 13px; }
|
|
33
|
+
td.bar { white-space: nowrap; width: 1%; }
|
|
34
|
+
tbody th code { white-space: nowrap; overflow-wrap: normal; }
|
|
35
|
+
progress { width: 120px; height: 8px; vertical-align: middle; accent-color: var(--bar); }
|
|
36
|
+
.pct { display: inline-block; min-width: 3.2em; text-align: right; font-variant-numeric: tabular-nums; }
|
|
37
|
+
.t-done progress { accent-color: var(--ok); }
|
|
38
|
+
.t-failed progress, .t-failed .what { accent-color: var(--bad); color: var(--bad); }
|
|
39
|
+
.t-unsynced progress, .t-unsynced .what { accent-color: var(--warn); color: var(--warn); }
|
|
40
|
+
.count { margin: 10px 0 0; }
|
|
41
|
+
.readiness { font-weight: 600; margin: 14px 0 4px; }
|
|
42
|
+
.readiness.ok { color: var(--ok); }
|
|
43
|
+
.readiness.bad { color: var(--bad); }
|
|
44
|
+
.bullets { margin: 0; padding-left: 20px; }
|
|
45
|
+
.warn { color: var(--warn); font-size: 13px; }
|
|
46
|
+
td.act { text-align: right; white-space: nowrap; }
|
|
47
|
+
.confirm { white-space: normal; }
|
|
48
|
+
button { font: inherit; font-size: 14px; padding: 4px 12px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); color: var(--fg); cursor: pointer; }
|
|
49
|
+
button:hover { border-color: var(--dim); }
|
|
50
|
+
button.danger { border-color: var(--bad); color: var(--bad); }
|
|
51
|
+
form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 12px 0; }
|
|
52
|
+
input { font: inherit; padding: 4px 8px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); color: var(--fg); min-width: 14em; }
|
|
53
|
+
.failure { border-left: 3px solid var(--bad); padding: 8px 12px; margin: 12px 0; background: var(--panel); }
|
|
54
|
+
.failure .message { margin: 0; font-weight: 600; }
|
|
55
|
+
.failure .mark { color: var(--bad); }
|
|
56
|
+
.failure dl { display: grid; grid-template-columns: max-content 1fr; gap: 2px 12px; margin: 6px 0 0; }
|
|
57
|
+
.failure dt { color: var(--dim); }
|
|
58
|
+
.failure dd { margin: 0; white-space: pre-line; overflow-wrap: anywhere; }
|
|
59
|
+
.next-head { margin: 8px 0 2px; font-weight: 600; }
|
|
60
|
+
.next { list-style: none; margin: 0; padding: 0; }
|
|
61
|
+
.next li span { color: var(--dim); margin-left: 8px; }
|
|
62
|
+
.added { border: 1px solid var(--ok); border-radius: 8px; padding: 10px 14px; margin: 12px 0; }
|
|
63
|
+
.added .headline { font-weight: 600; margin: 0 0 8px; }
|
|
64
|
+
.added .tip { color: var(--dim); }
|
|
65
|
+
#poll .failure { border: 1px solid var(--bad); border-left-width: 3px; border-radius: 4px; margin-top: 16px; }
|
|
66
|
+
table.catalog td.num { white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
|
|
67
|
+
.c-added .state { color: var(--ok); }
|
|
68
|
+
.c-cannot .state { color: var(--warn); font-size: 13px; }
|
|
69
|
+
tr.under > td { border-top: 0; padding-top: 0; }
|
|
70
|
+
form.pick { display: block; margin: 4px 0 8px; }
|
|
71
|
+
form.pick fieldset { border: 1px solid var(--line); border-radius: 6px; margin: 0 0 8px; padding: 6px 10px; }
|
|
72
|
+
form.pick legend { color: var(--dim); font-size: 13px; padding: 0 4px; }
|
|
73
|
+
label.tick { display: inline-flex; align-items: baseline; gap: 4px; margin: 2px 14px 2px 0; }
|
|
74
|
+
.done { border: 1px solid var(--ok); border-radius: 8px; padding: 8px 12px; margin: 4px 0 8px; }
|
|
75
|
+
.done .headline { font-weight: 600; margin: 0 0 4px; overflow-wrap: anywhere; }
|
|
76
|
+
.done .note, .done .tip { color: var(--dim); margin: 2px 0 6px; }
|