@mason-data/cli 0.8.0 → 0.9.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 (109) hide show
  1. package/README.md +13 -4
  2. package/dist/src/api/postgres/catalog-worker.js +49 -2
  3. package/dist/src/api/postgres/catalog.js +166 -32
  4. package/dist/src/api/postgres/listener.js +11 -3
  5. package/dist/src/api/postgres/proxy.js +283 -0
  6. package/dist/src/api/postgres/schema.js +6 -0
  7. package/dist/src/api/postgres/server.js +505 -99
  8. package/dist/src/api/postgres/translate.js +47 -2
  9. package/dist/src/cli/background.js +18 -19
  10. package/dist/src/cli/bridge-stdin.js +101 -0
  11. package/dist/src/cli/command.js +169 -11
  12. package/dist/src/cli/commands.js +207 -51
  13. package/dist/src/cli/credentials.js +182 -0
  14. package/dist/src/cli/demo.js +2 -2
  15. package/dist/src/cli/deploy.js +574 -0
  16. package/dist/src/cli/dev.js +148 -27
  17. package/dist/src/cli/doctor.js +116 -33
  18. package/dist/src/cli/generated-files.js +143 -0
  19. package/dist/src/cli/guides.js +97 -6
  20. package/dist/src/cli/hosted.js +850 -0
  21. package/dist/src/cli/init.js +47 -11
  22. package/dist/src/cli/login.js +289 -0
  23. package/dist/src/cli/machine.js +1 -1
  24. package/dist/src/cli/main.js +8 -2
  25. package/dist/src/cli/mason.cjs +17 -0
  26. package/dist/src/cli/mcp.js +2 -2
  27. package/dist/src/cli/output-gate.js +176 -0
  28. package/dist/src/cli/page-share.js +96 -0
  29. package/dist/src/cli/pg-tls.js +1 -1
  30. package/dist/src/cli/pin.js +6 -2
  31. package/dist/src/cli/pool-size.cjs +85 -0
  32. package/dist/src/cli/ports.js +2 -0
  33. package/dist/src/cli/preflight.js +130 -39
  34. package/dist/src/cli/project.js +99 -0
  35. package/dist/src/cli/readiness.js +42 -6
  36. package/dist/src/cli/registry.js +12 -4
  37. package/dist/src/cli/replica-hosted.js +665 -0
  38. package/dist/src/cli/replica.js +76 -0
  39. package/dist/src/cli/roles.js +2 -1
  40. package/dist/src/cli/run-server.js +154 -7
  41. package/dist/src/cli/security.js +57 -10
  42. package/dist/src/cli/serve.js +63 -7
  43. package/dist/src/cli/share-run.js +51 -8
  44. package/dist/src/cli/share.js +3 -3
  45. package/dist/src/cli/source-edit.js +10 -4
  46. package/dist/src/cli/source-role.js +344 -0
  47. package/dist/src/cli/sources.js +30 -20
  48. package/dist/src/cli/style.js +2 -1
  49. package/dist/src/cli/supervise.js +15 -23
  50. package/dist/src/cli/token-hosted.js +337 -0
  51. package/dist/src/cli/ui-ssh.js +460 -0
  52. package/dist/src/cli/ui.js +115 -11
  53. package/dist/src/cli/user-hosted.js +299 -0
  54. package/dist/src/cli/user.js +126 -74
  55. package/dist/src/demo/seed.js +26 -0
  56. package/dist/src/duck/analyze.js +36 -20
  57. package/dist/src/duck/collation.js +266 -0
  58. package/dist/src/duck/constant-key.js +94 -0
  59. package/dist/src/duck/distinct-key.js +283 -26
  60. package/dist/src/duck/events.js +39 -8
  61. package/dist/src/duck/executor-worker.js +2 -2
  62. package/dist/src/duck/executor.js +6 -5
  63. package/dist/src/duck/explain.js +4 -0
  64. package/dist/src/duck/functions.js +448 -49
  65. package/dist/src/duck/gaps.js +34 -16
  66. package/dist/src/duck/generated-plan.js +1070 -0
  67. package/dist/src/duck/generated.js +906 -0
  68. package/dist/src/duck/grants.js +1 -1
  69. package/dist/src/duck/held-types.js +64 -0
  70. package/dist/src/duck/history.js +4 -0
  71. package/dist/src/duck/jsonb-order.js +9 -12
  72. package/dist/src/duck/kept-route.js +6 -5
  73. package/dist/src/duck/kept.js +9 -7
  74. package/dist/src/duck/late.js +1469 -40
  75. package/dist/src/duck/libc-bound.js +341 -0
  76. package/dist/src/duck/libc-order.js +892 -126
  77. package/dist/src/duck/pg-eval-worker.js +7 -1
  78. package/dist/src/duck/pg-eval.js +6 -4
  79. package/dist/src/duck/pgtree.js +774 -23
  80. package/dist/src/duck/plan.js +275 -30
  81. package/dist/src/duck/publication.js +14 -0
  82. package/dist/src/duck/requests.js +9 -5
  83. package/dist/src/duck/resolved.js +117 -2
  84. package/dist/src/duck/rewrite.js +4567 -583
  85. package/dist/src/duck/row-filter.js +12 -6
  86. package/dist/src/duck/statement.js +19 -0
  87. package/dist/src/duck/store.js +294 -68
  88. package/dist/src/duck/stream.js +9 -5
  89. package/dist/src/duck/sync.js +602 -126
  90. package/dist/src/duck/token.js +4 -1
  91. package/dist/src/duck/types.js +63 -7
  92. package/dist/src/duck/usage.js +6 -4
  93. package/dist/src/duck/users.js +5 -0
  94. package/dist/src/duck/verified.js +159 -2
  95. package/dist/src/duck/views.js +4 -3
  96. package/dist/src/lib/database-url.js +55 -10
  97. package/dist/src/lib/instant.js +50 -0
  98. package/dist/src/lib/log-buffer.js +13 -4
  99. package/dist/src/lib/log-read.js +196 -0
  100. package/dist/src/lib/pg-numeric.js +387 -49
  101. package/dist/src/lib/secret-mask.js +142 -0
  102. package/dist/src/lib/table-sync.js +24 -9
  103. package/dist/src/web/app.js +9 -4
  104. package/dist/src/web/pages/status.js +178 -13
  105. package/dist/src/web/pages/syncs.js +1 -1
  106. package/dist/src/web/pages/tables.js +8 -3
  107. package/dist/src/web/pages/users.js +4 -3
  108. package/dist/src/web/shared.js +19 -3
  109. package/package.json +8 -7
@@ -0,0 +1,283 @@
1
+ /**
2
+ * THE POSTGRES API BEHIND A TCP PROXY (`MASON_PG_PROXY_FROM`; docs/POSTGRES-API.md "Behind a TCP proxy"). Behind a TCP
3
+ * load balancer or proxy (an AWS NLB, HAProxy, an edge of one's own) every connection's peer is the proxy, so the
4
+ * request log could not say who sent what. A proxy that speaks the PROXY protocol v2 (haproxy's proxy-protocol.txt,
5
+ * section 2.2) begins each connection with a binary header naming the client it carries. A header is a CLAIM about
6
+ * where a connection came from, so it is believed only from an address the operator named:
7
+ * - a connection from a named address MUST begin with a valid v2 header, or it is refused;
8
+ * - a connection from any other address is read as before, and a PROXY header from it is refused, never believed;
9
+ * - unset, nothing is named and nothing changes.
10
+ * The header is read whole before anything else on the connection is looked at (an SSLRequest, a direct TLS start, a
11
+ * startup packet, a CancelRequest), and bounded in size (`MAX_PROXY_BODY`) and in time (`PROXY_HEADER_MS`).
12
+ *
13
+ * THE CLIENT'S ADDRESS HAS ONE READER (`clientAddress`): the source a named proxy's header carries, else the socket's
14
+ * peer, a v4-mapped IPv6 address shown as IPv4 (`shownAddress`). The request log's `client_addr` and the session's log
15
+ * line read it, never the socket.
16
+ */
17
+ /** the v2 header's first 12 bytes (section 2.2) */
18
+ export const PROXY_V2_SIGNATURE = Buffer.from([0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a]);
19
+ /** how a v1 (text) header begins */
20
+ const PROXY_V1_START = Buffer.from("PROXY ", "latin1");
21
+ /** The most a header may carry past its 16 fixed bytes: the largest address block of the spec (two UNIX paths, 216
22
+ * bytes), which holds an IPv6 pair (36) and 180 bytes of TLVs. A header that says more is refused. */
23
+ export const MAX_PROXY_BODY = 216;
24
+ /** How long a named proxy has to send its whole header once it has connected. */
25
+ export const PROXY_HEADER_MS = 5_000;
26
+ /** the setting, as every message names it */
27
+ export const PROXY_SETTING = "MASON_PG_PROXY_FROM";
28
+ /**
29
+ * The PROXY v2 header at the start of `b`, read as section 2.2 says: the signature, version 2, the LOCAL or PROXY
30
+ * command, TCP over IPv4 (0x11) or IPv6 (0x21) for a PROXY command, its length at most MAX_PROXY_BODY, and its TLVs
31
+ * skipped, each held inside the block. Asked again as bytes arrive: a header split across reads is `more` until it is
32
+ * whole, and a wrong byte is refused as soon as it arrives. A v1 (text) header is refused by name.
33
+ */
34
+ export function readProxyHeader(b) {
35
+ for (let i = 0; i < Math.min(b.length, PROXY_V2_SIGNATURE.length); i++) {
36
+ if (b[i] === PROXY_V2_SIGNATURE[i])
37
+ continue;
38
+ const v1 = Math.min(b.length, PROXY_V1_START.length);
39
+ if (b.subarray(0, v1).every((c, j) => c === PROXY_V1_START[j])) {
40
+ return v1 < PROXY_V1_START.length ? { more: true } : { refused: "a PROXY protocol v1 (text) header: Mason reads version 2 alone (HAProxy's send-proxy-v2)" };
41
+ }
42
+ return { refused: "no PROXY protocol v2 header: its first 12 bytes are not the signature" };
43
+ }
44
+ if (b.length < 16)
45
+ return { more: true };
46
+ const version = b[12] >> 4, command = b[12] & 0x0f;
47
+ if (version !== 2)
48
+ return { refused: `a PROXY header of version ${version}: Mason reads version 2` };
49
+ if (command > 1)
50
+ return { refused: `a PROXY header with the command 0x${command.toString(16)}: only LOCAL (0x0) and PROXY (0x1) are defined` };
51
+ const family = b[13], length = (b[14] << 8) | b[15];
52
+ if (length > MAX_PROXY_BODY)
53
+ return { refused: `a PROXY header of ${length} bytes past its first 16: Mason reads at most ${MAX_PROXY_BODY}` };
54
+ if (b.length < 16 + length)
55
+ return { more: true };
56
+ // LOCAL: the proxy's own connection (a health check); its address block is discarded, as the spec says
57
+ if (command === 0)
58
+ return { header: { command: "LOCAL", source: null }, length: 16 + length };
59
+ const body = b.subarray(16, 16 + length);
60
+ const addresses = family === 0x11 ? 12 : family === 0x21 ? 36 : null;
61
+ if (addresses === null) {
62
+ return { refused: `a PROXY header for family and transport 0x${family.toString(16).padStart(2, "0")}: Mason reads TCP over IPv4 (0x11) or IPv6 (0x21), and an UNSPEC one with LOCAL alone` };
63
+ }
64
+ if (length < addresses)
65
+ return { refused: `a PROXY header of ${length} bytes past its first 16, too few for its addresses (${addresses})` };
66
+ const source = family === 0x11
67
+ ? { address: Array.from(body.subarray(0, 4)).join("."), port: (body[8] << 8) | body[9] }
68
+ : { address: ipv6Text(body.subarray(0, 16)), port: (body[32] << 8) | body[33] };
69
+ // the TLVs (type, a 2-byte length, the value): skipped, each held inside the block
70
+ for (let at = addresses; at < length;) {
71
+ if (length - at < 3)
72
+ return { refused: "a PROXY header whose last TLV is cut short" };
73
+ at += 3 + ((body[at + 1] << 8) | body[at + 2]);
74
+ if (at > length)
75
+ return { refused: "a PROXY header with a TLV past its end" };
76
+ }
77
+ return { header: { command: "PROXY", source }, length: 16 + length };
78
+ }
79
+ /** Whether a connection's first byte is a PROXY header's, v2 or v1: never a startup packet's (its length's first byte is
80
+ * 0) nor a TLS ClientHello's (0x16). */
81
+ export const startsProxyHeader = (first) => first === PROXY_V2_SIGNATURE[0] || first === PROXY_V1_START[0];
82
+ /**
83
+ * THE CLIENT'S ADDRESS, THE ONE READER OF IT: the source the header of a named proxy carries, else the socket's peer
84
+ * (a LOCAL header's connection is the proxy's own), shown as Mason shows an address (`shownAddress`); null when the
85
+ * peer cannot be read.
86
+ */
87
+ export function clientAddress(peer, header) {
88
+ return header?.source?.address ?? shownAddress(peer);
89
+ }
90
+ /** An address as Mason shows it: IPv4 dotted, IPv6 in RFC 5952's form, a v4-mapped IPv6 one as IPv4; null when it is no
91
+ * address (undefined, a zone index, a name). */
92
+ export function shownAddress(text) {
93
+ if (text === undefined)
94
+ return null;
95
+ const v4 = ipv4Bytes(text);
96
+ if (v4 !== null)
97
+ return Array.from(v4).join(".");
98
+ const v6 = ipv6Bytes(text);
99
+ return v6 === null ? null : ipv6Text(v6);
100
+ }
101
+ /** An IPv4 address's 4 bytes, from dotted decimal with no leading zero; null for anything else. */
102
+ function ipv4Bytes(text) {
103
+ const parts = text.split(".");
104
+ if (parts.length !== 4 || !parts.every((p) => /^(?:0|[1-9]\d{0,2})$/.test(p) && Number(p) <= 255))
105
+ return null;
106
+ return Uint8Array.from(parts.map(Number));
107
+ }
108
+ /** An IPv6 address's 16 bytes (`::` once at most, an IPv4 tail); null for anything else, a zone index included. */
109
+ function ipv6Bytes(text) {
110
+ if (!/^[0-9A-Fa-f:.]+$/.test(text))
111
+ return null;
112
+ const halves = text.split("::");
113
+ if (halves.length > 2 || (halves.length === 2 && halves[0].includes(".")))
114
+ return null;
115
+ const groups = (s) => {
116
+ if (s === "")
117
+ return [];
118
+ const out = [];
119
+ const parts = s.split(":");
120
+ for (const [i, p] of parts.entries()) {
121
+ if (i === parts.length - 1 && p.includes(".")) {
122
+ const v4 = ipv4Bytes(p);
123
+ if (v4 === null)
124
+ return null;
125
+ out.push((v4[0] << 8) | v4[1], (v4[2] << 8) | v4[3]);
126
+ }
127
+ else if (/^[0-9A-Fa-f]{1,4}$/.test(p))
128
+ out.push(parseInt(p, 16));
129
+ else
130
+ return null;
131
+ }
132
+ return out;
133
+ };
134
+ const head = groups(halves[0]), tail = halves.length === 2 ? groups(halves[1]) : [];
135
+ if (head === null || tail === null)
136
+ return null;
137
+ const n = head.length + tail.length;
138
+ if (halves.length === 1 ? n !== 8 : n > 7)
139
+ return null;
140
+ const all = [...head, ...new Array(8 - n).fill(0), ...tail];
141
+ return Uint8Array.from(all.flatMap((g) => [g >> 8, g & 0xff]));
142
+ }
143
+ /** whether 16 bytes are an IPv4 address mapped into IPv6 (::ffff:a.b.c.d) */
144
+ const v4Mapped = (b) => b.subarray(0, 10).every((x) => x === 0) && b[10] === 0xff && b[11] === 0xff;
145
+ /** 16 bytes as Mason shows an IPv6 address: RFC 5952 (lower case, the longest run of two or more zero groups as `::`,
146
+ * the first of equal runs), and a v4-mapped one as its IPv4 address. */
147
+ function ipv6Text(b) {
148
+ if (v4Mapped(b))
149
+ return Array.from(b.subarray(12, 16)).join(".");
150
+ const g = Array.from({ length: 8 }, (_, i) => (b[2 * i] << 8) | b[2 * i + 1]);
151
+ let at = -1, run = 0;
152
+ for (let i = 0; i < 8;) {
153
+ if (g[i] !== 0) {
154
+ i++;
155
+ continue;
156
+ }
157
+ let j = i;
158
+ while (j < 8 && g[j] === 0)
159
+ j++;
160
+ if (j - i > run) {
161
+ at = i;
162
+ run = j - i;
163
+ }
164
+ i = j;
165
+ }
166
+ const hex = g.map((x) => x.toString(16));
167
+ return run < 2 ? hex.join(":") : `${hex.slice(0, at).join(":")}::${hex.slice(at + run).join(":")}`;
168
+ }
169
+ /**
170
+ * EVERY ADDRESS IN ONE SPACE, as Mason means one: IPv6's 128 bits, an IPv4 address at its v4-mapped place
171
+ * (::ffff:a.b.c.d), since a v4-mapped peer or header source IS that IPv4 client (`shownAddress`). What an entry names and
172
+ * what the list covers are both read here, never from how an entry is spelt: `0.0.0.0/1,128.0.0.0/1` is every IPv4
173
+ * address, as `0.0.0.0/0` is, and `::/64` holds every v4-mapped address, so every IPv4 one too.
174
+ */
175
+ const V4_START = 0xffffn << 32n;
176
+ const V4_END = V4_START + (1n << 32n) - 1n;
177
+ const ALL_END = (1n << 128n) - 1n;
178
+ const bigOf = (b) => b.reduce((n, x) => (n << 8n) | BigInt(x), 0n);
179
+ /** an address's place (`V4_START`), or null when it is no address */
180
+ function placeOf(address) {
181
+ const v4 = ipv4Bytes(address);
182
+ if (v4 !== null)
183
+ return V4_START | bigOf(v4);
184
+ const v6 = ipv6Bytes(address);
185
+ return v6 === null ? null : bigOf(v6);
186
+ }
187
+ /** the places an entry names */
188
+ function rangeOf(c) {
189
+ const start = c.family === 4 ? V4_START | bigOf(c.bytes) : bigOf(c.bytes);
190
+ return { start, end: start + (1n << BigInt((c.family === 4 ? 32 : 128) - c.prefix)) - 1n };
191
+ }
192
+ /** Whether `address` (as `shownAddress` shows one, or in any spelling of the same address) is among those `from` names. */
193
+ export function proxyNames(from, address) {
194
+ const at = placeOf(address);
195
+ return at !== null && from.ranges.some((r) => r.start <= at && at <= r.end);
196
+ }
197
+ /** whether `ranges` together hold every place from `lo` to `hi` */
198
+ function covers(ranges, lo, hi) {
199
+ let next = lo;
200
+ for (const r of [...ranges].sort((a, b) => (a.start < b.start ? -1 : a.start > b.start ? 1 : 0))) {
201
+ if (r.start > next)
202
+ return false;
203
+ if (r.end >= next)
204
+ next = r.end + 1n;
205
+ if (next > hi)
206
+ return true;
207
+ }
208
+ return next > hi;
209
+ }
210
+ function prefixEqual(a, b, bits) {
211
+ for (let i = 0; i * 8 < bits; i++) {
212
+ const mask = bits - i * 8 >= 8 ? 0xff : (0xff << (8 - (bits - i * 8))) & 0xff;
213
+ if ((a[i] & mask) !== (b[i] & mask))
214
+ return false;
215
+ }
216
+ return true;
217
+ }
218
+ /** the network `bytes` / `prefix` names, its host bits cleared */
219
+ function network(bytes, prefix) {
220
+ return bytes.map((x, i) => i * 8 >= prefix ? 0 : prefix - i * 8 >= 8 ? x : x & ((0xff << (8 - (prefix - i * 8))) & 0xff));
221
+ }
222
+ const text = (family, bytes) => family === 4 ? Array.from(bytes).join(".") : ipv6Text(bytes);
223
+ /** One entry read, or what is wrong with it, with a did-you-mean where one is a fair guess: a guess is offered only
224
+ * once it is read as an entry itself, so it is never one that would be refused in turn. */
225
+ function cidrOf(entry) {
226
+ const quoted = JSON.stringify(entry);
227
+ const m = /^([^/]*)(?:\/(\d{1,3}))?$/.exec(entry);
228
+ const v4 = m === null ? null : ipv4Bytes(m[1]), v6 = m === null || v4 !== null ? null : ipv6Bytes(m[1]);
229
+ if (m === null || (v4 === null && v6 === null)) {
230
+ return { problem: `${quoted} is not an address or a CIDR (10.0.0.0/16, 2001:db8::/32, or one address such as 10.0.4.7; a host name is not looked up)` };
231
+ }
232
+ const family = v4 !== null ? 4 : 6, bytes = (v4 ?? v6), max = family === 4 ? 32 : 128;
233
+ const prefix = m[2] === undefined ? max : Number(m[2]);
234
+ if (prefix > max)
235
+ return { problem: `${quoted}: an IPv${family} prefix is at most /${max}` };
236
+ if (family === 6 && v4Mapped(bytes) && prefix >= 96) {
237
+ const p4 = prefix - 96, net4 = network(bytes.subarray(12, 16), p4);
238
+ const meant = `${text(4, net4)}${p4 === 32 ? "" : `/${p4}`}`, read = cidrOf(meant);
239
+ return { problem: "problem" in read
240
+ ? `${quoted} is IPv4 written as IPv6, which Mason reads as ${meant}, and ${read.problem}`
241
+ : `${quoted} is IPv4 written as IPv6, which Mason reads as IPv4: did you mean ${meant}?` };
242
+ }
243
+ if (prefix === 0)
244
+ return { problem: `${quoted} names every address, so any client could claim to be any other: name the proxy's own addresses` };
245
+ const net = network(bytes, prefix);
246
+ if (!prefixEqual(net, bytes, max))
247
+ return { problem: `${quoted} has bits set past its prefix: did you mean ${text(family, net)}/${prefix}?` };
248
+ return { family, bytes: net, prefix };
249
+ }
250
+ /**
251
+ * `MASON_PG_PROXY_FROM` read: null when it is unset or empty (no header is believed from anyone, as before), the
252
+ * addresses it names, or why not: every entry that is not an address or a CIDR, each said with what is wrong, or a list
253
+ * whose entries TOGETHER name every IPv4 or every IPv6 address (read as places, never spellings), under which any
254
+ * client could claim to be any other.
255
+ */
256
+ export function parseProxyFrom(value) {
257
+ if (value === undefined || value.trim() === "")
258
+ return null;
259
+ const entries = value.split(",").map((e) => e.trim());
260
+ const ranges = [], wrong = [];
261
+ for (const e of entries) {
262
+ if (e === "") {
263
+ wrong.push("an empty entry between two commas");
264
+ continue;
265
+ }
266
+ const c = cidrOf(e);
267
+ if ("problem" in c)
268
+ wrong.push(c.problem);
269
+ else
270
+ ranges.push(rangeOf(c));
271
+ }
272
+ const setting = `${PROXY_SETTING}=${JSON.stringify(value)}`;
273
+ if (wrong.length)
274
+ return { problem: `${setting}: ${wrong.join("; ")}` };
275
+ const every = [
276
+ ...(covers(ranges, V4_START, V4_END) ? ["every IPv4 address (0.0.0.0/0)"] : []),
277
+ // IPv6's every address but the v4-mapped ones, which are IPv4's and judged above
278
+ ...(covers(ranges, 0n, V4_START - 1n) && covers(ranges, V4_END + 1n, ALL_END) ? ["every IPv6 address (::/0)"] : []),
279
+ ];
280
+ if (every.length)
281
+ return { problem: `${setting}: its entries together name ${every.join(" and ")}, so any client could claim to be any other: name the proxy's own addresses` };
282
+ return { entries, ranges };
283
+ }
@@ -16,6 +16,12 @@ export const PG_API_TYPE_SPELLING = {
16
16
  int2: "smallint", int4: "integer", int8: "bigint", float4: "real", float8: "double precision", numeric: "numeric", text: "text",
17
17
  bool: "boolean", uuid: "uuid", date: "date", timestamptz: "timestamp with time zone", jsonb: "jsonb",
18
18
  };
19
+ /** the input function's name of a type the catalog declares for a `made` column: what the planner tells one by */
20
+ export const HELD_TYPE_INPUT = "mason_held_in";
21
+ /** a composite's: its input function's name holds `HELD_TYPE_INPUT` too */
22
+ export const HELD_ROW_INPUT = "row_mason_held_in";
23
+ /** the name a `made` type is declared under, its typmod and `[]` left off (`vector(3)`, `pair[]`) */
24
+ export const madeTypeName = (spelled) => spelled.replace(/\[\]$/, "").replace(/\(\d+(,\d+)?\)$/, "");
19
25
  /**
20
26
  * A type as format_type spells it (`mood`, `other.level`, `"My Mood"`, `"a.b"."c"`, an array's `[]` left off): its
21
27
  * schema as written (null when it is not qualified) and its name as pg_type.typname holds it (unquoted).