@mason-data/cli 0.7.0 → 0.8.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.
Files changed (111) hide show
  1. package/README.md +90 -71
  2. package/dist/src/api/error.js +3 -0
  3. package/dist/src/api/postgres/catalog.js +222 -13
  4. package/dist/src/api/postgres/encode.js +3 -1
  5. package/dist/src/api/postgres/explain.js +3 -2
  6. package/dist/src/api/postgres/protocol.js +39 -23
  7. package/dist/src/api/postgres/server.js +1173 -207
  8. package/dist/src/api/postgres/translate.js +81 -9
  9. package/dist/src/api/request-log.js +18 -5
  10. package/dist/src/cdc/poll.js +5 -5
  11. package/dist/src/cli/background.js +29 -6
  12. package/dist/src/cli/cancel.js +75 -0
  13. package/dist/src/cli/command.js +1 -0
  14. package/dist/src/cli/commands.js +622 -183
  15. package/dist/src/cli/control.js +59 -0
  16. package/dist/src/cli/demo.js +19 -10
  17. package/dist/src/cli/dev.js +351 -111
  18. package/dist/src/cli/doctor.js +20 -10
  19. package/dist/src/cli/guides.js +79 -31
  20. package/dist/src/cli/init.js +119 -58
  21. package/dist/src/cli/machine.js +12 -11
  22. package/dist/src/cli/main.js +3 -3
  23. package/dist/src/cli/mcp.js +29 -20
  24. package/dist/src/cli/metrics.js +79 -0
  25. package/dist/src/cli/pg-tls.js +13 -3
  26. package/dist/src/cli/pg-url.js +4 -0
  27. package/dist/src/cli/pin.js +13 -12
  28. package/dist/src/cli/preflight.js +8 -7
  29. package/dist/src/cli/project.js +43 -4
  30. package/dist/src/cli/readiness.js +38 -4
  31. package/dist/src/cli/registry.js +53 -34
  32. package/dist/src/cli/replication-checks.js +7 -5
  33. package/dist/src/cli/replication-mode.js +105 -26
  34. package/dist/src/cli/request-filter.js +134 -0
  35. package/dist/src/cli/roles.js +584 -0
  36. package/dist/src/cli/run-server.js +483 -41
  37. package/dist/src/cli/runs.js +1 -1
  38. package/dist/src/cli/security.js +10 -8
  39. package/dist/src/cli/serve.js +144 -42
  40. package/dist/src/cli/share-run.js +286 -0
  41. package/dist/src/cli/share.js +286 -0
  42. package/dist/src/cli/source-edit.js +65 -13
  43. package/dist/src/cli/sources.js +287 -38
  44. package/dist/src/cli/supervise.js +12 -1
  45. package/dist/src/cli/sync.js +55 -25
  46. package/dist/src/cli/tunnel.js +200 -0
  47. package/dist/src/cli/ui.js +9 -6
  48. package/dist/src/cli/usage.js +128 -0
  49. package/dist/src/cli/user.js +496 -40
  50. package/dist/src/cli/version.js +9 -7
  51. package/dist/src/demo/seed.js +3 -0
  52. package/dist/src/duck/cancel.js +10 -1
  53. package/dist/src/duck/events.js +134 -0
  54. package/dist/src/duck/executor-worker.js +18 -9
  55. package/dist/src/duck/executor.js +43 -30
  56. package/dist/src/duck/explain.js +56 -46
  57. package/dist/src/duck/extensions.js +160 -0
  58. package/dist/src/duck/functions.js +13 -14
  59. package/dist/src/duck/gaps.js +17 -8
  60. package/dist/src/duck/grants.js +176 -0
  61. package/dist/src/duck/history.js +123 -0
  62. package/dist/src/duck/kept.js +6 -6
  63. package/dist/src/duck/plan.js +102 -25
  64. package/dist/src/duck/publication.js +6 -6
  65. package/dist/src/duck/requests.js +265 -93
  66. package/dist/src/duck/rewrite.js +25 -5
  67. package/dist/src/duck/row-filter.js +649 -0
  68. package/dist/src/duck/store.js +156 -120
  69. package/dist/src/duck/stream.js +10 -5
  70. package/dist/src/duck/sync.js +835 -268
  71. package/dist/src/duck/types.js +6 -5
  72. package/dist/src/duck/usage.js +324 -0
  73. package/dist/src/duck/users.js +64 -15
  74. package/dist/src/duck/verified.js +3 -0
  75. package/dist/src/duck/views.js +2 -3
  76. package/dist/src/lib/database-name.js +62 -0
  77. package/dist/src/lib/dotenv.js +17 -4
  78. package/dist/src/lib/log-buffer.js +68 -0
  79. package/dist/src/lib/pg-numeric.js +5 -2
  80. package/dist/src/lib/project-version.js +23 -23
  81. package/dist/src/lib/table-sync.js +267 -0
  82. package/dist/src/web/app.css +706 -66
  83. package/dist/src/web/app.js +327 -410
  84. package/dist/src/web/charts.js +229 -0
  85. package/dist/src/web/index.html +82 -18
  86. package/dist/src/web/pages/queries.js +192 -0
  87. package/dist/src/web/pages/status.js +446 -0
  88. package/dist/src/web/pages/syncs.js +155 -0
  89. package/dist/src/web/pages/tables.js +434 -0
  90. package/dist/src/web/pages/usage.js +397 -0
  91. package/dist/src/web/pages/users.js +218 -0
  92. package/dist/src/web/shared.js +568 -0
  93. package/dist/src/web/vendor/README.md +25 -0
  94. package/dist/src/web/vendor/geist-1.7.2/Geist-Variable.woff2 +0 -0
  95. package/dist/src/web/vendor/geist-1.7.2/GeistMono-Variable.woff2 +0 -0
  96. package/dist/src/web/vendor/geist-1.7.2/LICENSE.txt +92 -0
  97. package/dist/src/web/vendor/lucide-1.56.0/LICENSE.txt +43 -0
  98. package/dist/src/web/vendor/lucide-1.56.0/chart-column.mjs +29 -0
  99. package/dist/src/web/vendor/lucide-1.56.0/layout-dashboard.mjs +29 -0
  100. package/dist/src/web/vendor/lucide-1.56.0/logs.mjs +34 -0
  101. package/dist/src/web/vendor/lucide-1.56.0/menu.mjs +28 -0
  102. package/dist/src/web/vendor/lucide-1.56.0/square-terminal.mjs +28 -0
  103. package/dist/src/web/vendor/lucide-1.56.0/table.mjs +29 -0
  104. package/dist/src/web/vendor/lucide-1.56.0/users.mjs +29 -0
  105. package/dist/src/web/vendor/lucide-1.56.0/x.mjs +27 -0
  106. package/dist/src/web/vendor/uplot-1.6.32/LICENSE.txt +21 -0
  107. package/dist/src/web/vendor/uplot-1.6.32/uPlot.esm.js +6140 -0
  108. package/dist/src/web/vendor/uplot-1.6.32/uPlot.min.css +1 -0
  109. package/dist/src/web/vendor/zilla-slab-5.3.0/LICENSE.txt +93 -0
  110. package/dist/src/web/vendor/zilla-slab-5.3.0/zilla-slab-latin-700-normal.woff2 +0 -0
  111. package/package.json +10 -3
@@ -1,3 +1,9 @@
1
+ /**
2
+ * THE COMMANDS' LINE TO A RUN: `mason status`, `mason tables`, `mason add` and `mason wait` ask the run's
3
+ * command server (src/cli/run-server.ts) — with the
4
+ * run's token as `Authorization: Bearer` (src/cli/security.ts), read from the project's .mason/config.json (src/cli/runs.ts).
5
+ */
6
+ import * as http from "node:http";
1
7
  /** The run does not answer: a failure with the reason as `why` and mason logs as the way out (`failure` is what a command
2
8
  * hands src/cli/command.ts failed). */
3
9
  export class RunUnreachable extends Error {
@@ -34,3 +40,56 @@ export async function callRun(run, method, route, body, timeoutMs = 30_000) {
34
40
  }
35
41
  return { status: res.status, body: parsed };
36
42
  }
43
+ /**
44
+ * ONE REQUEST HELD OPEN (src/cli/share.ts): the run answers a line of JSON and keeps the answer open, so the end of the
45
+ * connection is the end of what was asked, however this process ends. On node:http, not fetch: fetch gives up on a body
46
+ * silent for five minutes, and here silence is the normal state. A refusal is answered whole, as `callRun` returns it.
47
+ */
48
+ export function holdRun(run, route, body, timeoutMs = 30_000) {
49
+ return new Promise((resolve, reject) => {
50
+ const unreachable = (why) => new RunUnreachable(`Mason for ${run.name} (pid ${run.pid}) does not answer at ${run.control.url}`, why);
51
+ const text = JSON.stringify(body);
52
+ const req = http.request(`${run.control.url}${route}`, { method: "POST", headers: { Authorization: `Bearer ${run.control.token}`, "Content-Type": "application/json", "Content-Length": Buffer.byteLength(text) } }, (res) => {
53
+ let got = "", first = null, said = null;
54
+ let finish = () => undefined;
55
+ const ended = new Promise((r) => { finish = r; });
56
+ res.setEncoding("utf8");
57
+ res.on("data", (chunk) => {
58
+ got += chunk;
59
+ if (res.statusCode !== 200)
60
+ return;
61
+ for (let at = got.indexOf("\n"); at >= 0; at = got.indexOf("\n")) {
62
+ const line = JSON.parse(got.slice(0, at));
63
+ got = got.slice(at + 1);
64
+ if (first === null) {
65
+ first = line;
66
+ clearTimeout(timer);
67
+ resolve({ first, ended, close: () => req.destroy() });
68
+ }
69
+ else if (typeof line.ended === "string")
70
+ said = line.ended;
71
+ }
72
+ });
73
+ res.on("close", () => {
74
+ clearTimeout(timer);
75
+ if (res.statusCode === 200) {
76
+ if (first === null)
77
+ reject(unreachable("it closed the answer before it said anything"));
78
+ finish(said);
79
+ return;
80
+ }
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(got);
84
+ }
85
+ catch {
86
+ parsed = { error: got.trim() || `HTTP ${res.statusCode}` };
87
+ }
88
+ resolve({ status: res.statusCode ?? 0, body: parsed });
89
+ });
90
+ });
91
+ const timer = setTimeout(() => { req.destroy(); reject(unreachable(`no answer in ${timeoutMs / 1000} s`)); }, timeoutMs);
92
+ req.on("error", (e) => { clearTimeout(timer); reject(unreachable(e.code ?? e.message)); });
93
+ req.end(text);
94
+ });
95
+ }
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * - THE PORT LIVES IN THE URL ALONE: `mason init demo` picks one free and writes it into `.env`; `mason start` reads
11
11
  * it back from the resolved URL (`demoPort`), never from a second record that could disagree with it.
12
- * - POLL ONLY: the demo database has no change log (no WAL sender), so `--replication` is refused before anything
13
- * starts (`demoChangesRefusal`).
12
+ * - POLL ONLY: the demo database has no change log (no WAL sender), so a table whose `sync:` is `replication` is
13
+ * refused before anything starts (`demoChangesRefusal`).
14
14
  * - A WRITER: every MASON_DEMO_CHANGE_MS (`demoSettings`) the run makes one round of changes (`demoChanges`), so the
15
15
  * copy is seen keeping current.
16
16
  *
@@ -19,7 +19,7 @@
19
19
  */
20
20
  import * as path from "node:path";
21
21
  import { parseSource, isLocalHost } from "./preflight.js";
22
- import { changesOf } from "./replication-mode.js";
22
+ import { entryName, replicatedEntries } from "./sources.js";
23
23
  import { heldText, portHolder } from "./ports.js";
24
24
  import { DEMO_DIR, STATE_DIR } from "./project.js";
25
25
  import { DEMO_DATABASE, DEMO_USER, demoUrl } from "../demo/seed.js";
@@ -30,7 +30,7 @@ export const DEMO_PORT = 5434;
30
30
  /** The demo database's tables (the seed's own list), the ones `mason init demo` adds to the sources file and the one it
31
31
  * leaves for the person to add, and its URL (src/demo/seed.ts: no PGlite is loaded by it, so `mason init demo` and
32
32
  * `demoPort` never load the database). */
33
- export { DEMO_ADDED_TABLES, DEMO_TABLES, DEMO_TABLE_TO_ADD, demoUrl } from "../demo/seed.js";
33
+ export { DEMO_ADDED_TABLES, DEMO_SYNC, DEMO_TABLES, DEMO_TABLE_TO_ADD, demoUrl } from "../demo/seed.js";
34
34
  /** How often the run changes the demo database by default (MASON_DEMO_CHANGE_MS). */
35
35
  export const DEMO_CHANGE_MS = 5000;
36
36
  const DEMO_CHANGE_MIN_MS = 100;
@@ -69,14 +69,23 @@ export function demoPort(url, from) {
69
69
  return { problem: `${from} was changed: the demo database is ${demoUrl(s.port)}; ${wayOut}` };
70
70
  return { port: s.port };
71
71
  }
72
- /** The refusal of logical replication for a demo project (the flag, or a mode the config was edited to): before
73
- * anything starts, with the way out; null for any other project, or a demo one polled. */
74
- export function demoChangesRefusal(cfg, flag) {
75
- if (cfg.demo !== true || changesOf(flag, cfg.changes) !== "replication")
72
+ /** The refusal of logical replication for a demo project (a table whose sync: is replication in its sources file,
73
+ * `shown`, or the mode an older Mason recorded in the config, edited to it): before anything starts, with the way out;
74
+ * null for any other project, or a demo one polled. */
75
+ export function demoChangesRefusal(cfg, file = null, shown = "") {
76
+ if (cfg.demo !== true)
76
77
  return null;
77
- return { message: "the demo database is read by polling only: --replication does not apply to it", code: "refused",
78
+ const streamed = file === null ? [] : replicatedEntries(file).map((e) => entryName(e.table));
79
+ if (streamed.length) {
80
+ return { message: `the demo database is read by polling only: sync: replication does not apply to it (${streamed.join(", ")})`, code: "refused",
81
+ why: "the demo database has no change log for logical replication to read; Mason polls it with SELECTs",
82
+ fix: `write sync: poll 5s for ${streamed.length === 1 ? "it" : "them"} in ${shown}, then mason start`, next: [["mason start", "the demo database and Mason, polled"]] };
83
+ }
84
+ if (cfg.changes !== "replication")
85
+ return null;
86
+ return { message: `the demo database is read by polling only: "changes": "replication" in ${STATE_DIR}/config.json does not apply to it`, code: "refused",
78
87
  why: "the demo database has no change log for logical replication to read; mason start polls it with SELECTs, every few seconds",
79
- fix: flag === "replication" ? "mason start, without --replication" : `remove "changes": "replication" from ${STATE_DIR}/config.json, then mason start`,
88
+ fix: `remove "changes": "replication" from ${STATE_DIR}/config.json, then mason start`,
80
89
  next: [["mason start", "the demo database and Mason, polled"]] };
81
90
  }
82
91
  /** The step's done line: where the demo database listens, and whether it was seeded now or kept from the last run. */