@snagset/server 0.0.1

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 (105) hide show
  1. package/README.md +83 -0
  2. package/dist/app.js +453 -0
  3. package/dist/app.js.map +1 -0
  4. package/dist/bin.js +951 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/boot.js +321 -0
  7. package/dist/boot.js.map +1 -0
  8. package/dist/config.generated.js +116 -0
  9. package/dist/config.generated.js.map +1 -0
  10. package/dist/config.js +180 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/crypto.js +26 -0
  13. package/dist/crypto.js.map +1 -0
  14. package/dist/db/connect.js +49 -0
  15. package/dist/db/connect.js.map +1 -0
  16. package/dist/db/migrate.js +210 -0
  17. package/dist/db/migrate.js.map +1 -0
  18. package/dist/db/migrations/0001_init.sql +1060 -0
  19. package/dist/db/migrations/0002_maintenance_claim.sql +109 -0
  20. package/dist/db/migrations/0003_email_provider.sql +35 -0
  21. package/dist/db/migrations/0004_unsubscribe.sql +24 -0
  22. package/dist/db/rls.js +58 -0
  23. package/dist/db/rls.js.map +1 -0
  24. package/dist/jobs/index.js +171 -0
  25. package/dist/jobs/index.js.map +1 -0
  26. package/dist/jobs/reaper.js +182 -0
  27. package/dist/jobs/reaper.js.map +1 -0
  28. package/dist/keys.js +77 -0
  29. package/dist/keys.js.map +1 -0
  30. package/dist/mail/index.js +75 -0
  31. package/dist/mail/index.js.map +1 -0
  32. package/dist/mail/mailer.js +27 -0
  33. package/dist/mail/mailer.js.map +1 -0
  34. package/dist/mail/notify.js +405 -0
  35. package/dist/mail/notify.js.map +1 -0
  36. package/dist/mail/resend.js +95 -0
  37. package/dist/mail/resend.js.map +1 -0
  38. package/dist/mail/smtp.js +315 -0
  39. package/dist/mail/smtp.js.map +1 -0
  40. package/dist/mail/unsubscribe.js +134 -0
  41. package/dist/mail/unsubscribe.js.map +1 -0
  42. package/dist/projectCache.js +50 -0
  43. package/dist/projectCache.js.map +1 -0
  44. package/dist/projects.js +101 -0
  45. package/dist/projects.js.map +1 -0
  46. package/dist/routes/attachments.js +159 -0
  47. package/dist/routes/attachments.js.map +1 -0
  48. package/dist/routes/auth.js +55 -0
  49. package/dist/routes/auth.js.map +1 -0
  50. package/dist/routes/commentEdit.js +201 -0
  51. package/dist/routes/commentEdit.js.map +1 -0
  52. package/dist/routes/comments.js +313 -0
  53. package/dist/routes/comments.js.map +1 -0
  54. package/dist/routes/cors.js +174 -0
  55. package/dist/routes/cors.js.map +1 -0
  56. package/dist/routes/jobs.js +56 -0
  57. package/dist/routes/jobs.js.map +1 -0
  58. package/dist/routes/mcpAuth.js +187 -0
  59. package/dist/routes/mcpAuth.js.map +1 -0
  60. package/dist/routes/mcpToken.js +94 -0
  61. package/dist/routes/mcpToken.js.map +1 -0
  62. package/dist/routes/owner.js +288 -0
  63. package/dist/routes/owner.js.map +1 -0
  64. package/dist/routes/reviewLinks.js +169 -0
  65. package/dist/routes/reviewLinks.js.map +1 -0
  66. package/dist/routes/rounds.js +137 -0
  67. package/dist/routes/rounds.js.map +1 -0
  68. package/dist/routes/sessions.js +349 -0
  69. package/dist/routes/sessions.js.map +1 -0
  70. package/dist/routes/sites.js +192 -0
  71. package/dist/routes/sites.js.map +1 -0
  72. package/dist/routes/threads.js +1080 -0
  73. package/dist/routes/threads.js.map +1 -0
  74. package/dist/routes/widget.js +132 -0
  75. package/dist/routes/widget.js.map +1 -0
  76. package/dist/serverless.js +97 -0
  77. package/dist/serverless.js.map +1 -0
  78. package/dist/session.js +152 -0
  79. package/dist/session.js.map +1 -0
  80. package/dist/storage/bucket.js +116 -0
  81. package/dist/storage/bucket.js.map +1 -0
  82. package/dist/storage/settings.js +132 -0
  83. package/dist/storage/settings.js.map +1 -0
  84. package/dist/vendor/adapters/index.js +1 -0
  85. package/dist/vendor/adapters/outbound/fence.js +44 -0
  86. package/dist/vendor/adapters/outbound/sanitise.js +233 -0
  87. package/dist/vendor/types/index.js +4 -0
  88. package/dist/vendor/types/names.js +254 -0
  89. package/dist/vendor/widget/object.js +38 -0
  90. package/dist/vendor/widget/predicates.js +377 -0
  91. package/package.json +53 -0
  92. package/static/dashboard/assets/index-CaPfKhFP.css +1 -0
  93. package/static/dashboard/assets/index-DfTNjHCK.js +1 -0
  94. package/static/dashboard/index.html +14 -0
  95. package/static/widget/snag.anchor-ext.js +1 -0
  96. package/static/widget/snag.anchor-fuzzy.js +1 -0
  97. package/static/widget/snag.capture.js +4 -0
  98. package/static/widget/snag.crumbs.js +4 -0
  99. package/static/widget/snag.dock.js +5 -0
  100. package/static/widget/snag.js +5 -0
  101. package/static/widget/snag.panels.js +2 -0
  102. package/static/widget/snag.runtime.js +2 -0
  103. package/static/widget/snag.screenshot.js +1 -0
  104. package/static/widget/snag.text.js +2 -0
  105. package/vercel.json +6 -0
package/dist/config.js ADDED
@@ -0,0 +1,180 @@
1
+ import { DELETED, REGISTRY } from "./config.generated.js";
2
+ /**
3
+ * The boot validator — 08 §4, and the ONLY module in the server that may read
4
+ * `process.env`.
5
+ *
6
+ * LOCK L7 and the scaffold's import lint make that mechanical rather than
7
+ * conventional: a route handler that reaches for `process.env` fails the
8
+ * lint. Every handler takes its config from `createApp(deps)`, which is what
9
+ * lets a test construct one instead of mutating the process.
10
+ *
11
+ * The REGISTRY is generated from 08 §4 — see `scripts/gen-config.mjs` and the
12
+ * rule it quotes. Nothing here is hand-maintained; this file is the rules,
13
+ * not the list.
14
+ *
15
+ * §4.1's rules, as implemented:
16
+ *
17
+ * 1. `DATABASE_URL` is the one required variable, in the platform's own
18
+ * spelling — the deliberate exception to the `SNAGSET_*` shape, because
19
+ * the Vercel marketplace mints it in the deploy click and Railway/Render
20
+ * inject it. A9's promise stands: one deployable unit, one required
21
+ * datastore, and everything else defaults to something that works.
22
+ * 2. An unknown `SNAGSET_*` is FATAL at boot, "with the offending name, the
23
+ * closest registered name by edit distance, and a link to this section" —
24
+ * because a transposed letter in a retention variable that silently
25
+ * reverts to its default is a data-loss bug discovered at restore time.
26
+ * 7. Customer-owned variables are not ours: `*_SNAGSET_ENABLED` and
27
+ * `SNAGSET_PRODUCTION_TOKEN_MODE` are set on the CUSTOMER's build (06
28
+ * §4), and the server "never reads them and must not reject them,
29
+ * because it never sees them" — so the prefix test is anchored.
30
+ * 8. Booleans are `true|false|1|0` case-insensitively; anything else fatal.
31
+ */
32
+ export class ConfigError extends Error {
33
+ /** What the operator should do about it — printed under the message. */
34
+ fix;
35
+ constructor(message, fix) {
36
+ super(message);
37
+ this.name = "ConfigError";
38
+ this.fix = fix;
39
+ }
40
+ }
41
+ /** §4.1 rule 7's carve-out: names a customer sets on THEIR build. */
42
+ const NOT_OURS = /^.+_SNAGSET_ENABLED$|^SNAGSET_PRODUCTION_TOKEN_MODE$/;
43
+ /** Levenshtein, for rule 2's "closest registered name". */
44
+ function distance(a, b) {
45
+ const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
46
+ for (let i = 1; i <= a.length; i++) {
47
+ for (let j = 1; j <= b.length; j++) {
48
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
49
+ }
50
+ }
51
+ return d[a.length][b.length];
52
+ }
53
+ /**
54
+ * The near-miss suggestion, and it is TOKEN-AWARE for a measured reason.
55
+ *
56
+ * Plain edit distance failed the exact mistake a first-time operator makes.
57
+ * `SNAGSET_OWNER_EMAIL` → `SNAGSET_BOOTSTRAP_OWNER_EMAIL` is 10 edits away —
58
+ * over any sane threshold — so the error said "unknown variable" and suggested
59
+ * nothing, while the right answer was the same name with one word inserted.
60
+ * Caught by mistyping it on a cold install and getting a dead end.
61
+ *
62
+ * So: if the typed name's underscore-separated words appear IN ORDER inside a
63
+ * registered name, that is a dropped or added segment rather than a typo, and
64
+ * it is a far stronger signal than character distance. Edit distance stays as
65
+ * the fallback for genuine typos (`SNAGSET_PROT`).
66
+ */
67
+ function closest(name) {
68
+ const words = name.toUpperCase().split("_").filter(Boolean);
69
+ // Subsequence match first, shortest winner — `SNAGSET_OWNER_EMAIL` should
70
+ // reach `SNAGSET_BOOTSTRAP_OWNER_EMAIL` rather than some longer name that
71
+ // also happens to contain those words.
72
+ let bySegment = null;
73
+ for (const row of REGISTRY) {
74
+ const target = row.name.split("_");
75
+ let i = 0;
76
+ for (const part of target)
77
+ if (i < words.length && part === words[i])
78
+ i++;
79
+ if (i === words.length && (bySegment === null || row.name.length < bySegment.length)) {
80
+ bySegment = row.name;
81
+ }
82
+ }
83
+ if (bySegment)
84
+ return bySegment;
85
+ let best = null;
86
+ let score = Infinity;
87
+ for (const row of REGISTRY) {
88
+ const n = distance(name, row.name);
89
+ if (n < score) {
90
+ score = n;
91
+ best = row.name;
92
+ }
93
+ }
94
+ // Beyond a few edits it is not a typo, it is a different idea — and a
95
+ // confident wrong suggestion is worse than none.
96
+ return score <= 6 ? best : null;
97
+ }
98
+ export function loadConfig(env) {
99
+ const rows = new Map(REGISTRY.map((r) => [r.name, r]));
100
+ const retired = new Map(DELETED.map((d) => [d.name, d.to]));
101
+ // Rule 2, FIRST — before any value is read. An operator who typo'd a flag
102
+ // wants to hear about the typo, not about the consequence of it being
103
+ // unset: they turned something off, it stayed on, and nothing said so.
104
+ for (const name of Object.keys(env)) {
105
+ if (!name.startsWith("SNAGSET_") || rows.has(name) || NOT_OURS.test(name))
106
+ continue;
107
+ // §4.11 exists precisely so an upgrade says what a name BECAME.
108
+ if (retired.has(name)) {
109
+ const to = retired.get(name);
110
+ throw new ConfigError(`${name} is no longer a Snagset variable`, to ? `it was renamed — set ${to} instead (08 §4.11)` : `it was deleted; see 08 §4.11 for what replaced it`);
111
+ }
112
+ const near = closest(name);
113
+ throw new ConfigError(`unknown environment variable ${name}`,
114
+ // `specs/08-deployment.md §4` is the registry and is NOT in the npm
115
+ // tarball — an operator who installed the package has no `specs/`
116
+ // directory, so the one pointer this error gave was to a file they could
117
+ // not open. `snagset env` is the same registry, printed by the binary
118
+ // they already have in front of them.
119
+ `${near ? `did you mean ${near}? ` : ""}run \`snagset env\` for the full list`);
120
+ }
121
+ const resolved = new Map();
122
+ for (const row of REGISTRY) {
123
+ const raw = env[row.name];
124
+ if (raw !== undefined && raw !== "") {
125
+ validate(row, raw);
126
+ resolved.set(row.name, { value: raw, source: "env" });
127
+ }
128
+ else if (row.required) {
129
+ throw new ConfigError(`${row.name} is required`, `set ${row.name} — a Postgres connection string (08 §${row.section})`);
130
+ }
131
+ else {
132
+ resolved.set(row.name, { value: row.default, source: "default" });
133
+ }
134
+ }
135
+ const get = (name) => resolved.get(name)?.value ?? undefined;
136
+ return {
137
+ get,
138
+ int: (name) => Number(get(name)),
139
+ bool: (name) => /^(true|1)$/i.test(get(name) ?? ""),
140
+ required: (name) => {
141
+ const v = get(name);
142
+ if (v === undefined)
143
+ throw new ConfigError(`${name} has no value`, `set ${name}`);
144
+ return v;
145
+ },
146
+ describe: () => REGISTRY.map((r) => ({
147
+ name: r.name,
148
+ value: resolved.get(r.name).value,
149
+ source: resolved.get(r.name).source,
150
+ secret: r.secret,
151
+ })),
152
+ };
153
+ }
154
+ function validate(row, raw) {
155
+ const bad = (why, fix) => {
156
+ throw new ConfigError(`${row.name} ${why}`, fix);
157
+ };
158
+ if (row.oneOf && !row.oneOf.includes(raw)) {
159
+ bad(`must be one of ${row.oneOf.join(" | ")}, got ${JSON.stringify(raw)}`, `set ${row.name} to one of: ${row.oneOf.join(", ")}`);
160
+ }
161
+ // Rule 8: byte counts are integers, never "5MB" strings — a value the
162
+ // operator believes is 5 MB and the program reads as NaN is the worst of
163
+ // both, so it is refused rather than coerced.
164
+ if (row.type === "int" && !/^\d+$/.test(raw)) {
165
+ bad(`must be a whole number, got ${JSON.stringify(raw)}`, `set ${row.name} to digits only (durations are seconds/days by their suffix; byte counts are bytes)`);
166
+ }
167
+ if (row.type === "bool" && !/^(true|false|1|0)$/i.test(raw)) {
168
+ bad(`must be true, false, 1 or 0 — got ${JSON.stringify(raw)}`, `set ${row.name} to true or false`);
169
+ }
170
+ // The `url` type covers three families and the allowlist has to name all of
171
+ // them: http(s) for base URLs and webhooks, postgres(ql) for DATABASE_URL,
172
+ // and smtp(s) for the mail transport (08 §4.8). Found live: a correct
173
+ // `smtp://host:2525` was refused by a validator that only knew the first
174
+ // two, so the SMTP adapter could not be configured at all — a boot failure
175
+ // for a variable whose entire purpose is to hold an SMTP URL.
176
+ if (row.type === "url" && !/^(https?|postgres(ql)?|smtps?):\/\//.test(raw)) {
177
+ bad(`must be a URL, got ${JSON.stringify(raw)}`, `set ${row.name} to a full URL including the scheme (http://, https://, postgres://, smtp:// or smtps://)`);
178
+ }
179
+ }
180
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/crypto.js ADDED
@@ -0,0 +1,26 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ export function seal(keys, plaintext) {
3
+ // 12 bytes is GCM's canonical nonce length — the one at which the counter
4
+ // construction needs no extra hashing step.
5
+ const iv = randomBytes(12);
6
+ const c = createCipheriv("aes-256-gcm", keys.derive("credential-enc"), iv);
7
+ const body = Buffer.concat([c.update(plaintext, "utf8"), c.final()]);
8
+ return { ciphertext: Buffer.concat([body, c.getAuthTag()]), iv };
9
+ }
10
+ export function open(keys, sealed) {
11
+ try {
12
+ const tag = sealed.ciphertext.subarray(sealed.ciphertext.length - 16);
13
+ const body = sealed.ciphertext.subarray(0, sealed.ciphertext.length - 16);
14
+ const d = createDecipheriv("aes-256-gcm", keys.derive("credential-enc"), sealed.iv);
15
+ d.setAuthTag(tag);
16
+ return Buffer.concat([d.update(body), d.final()]).toString("utf8");
17
+ }
18
+ catch {
19
+ // The overwhelmingly likely cause is a changed `SNAGSET_SECRET_KEY` —
20
+ // which 08 §4.7 warns makes stored credentials "unrecoverable
21
+ // ciphertext". Null rather than throwing, so a boot with a lost key
22
+ // degrades to "storage disconnected" instead of failing to start.
23
+ return null;
24
+ }
25
+ }
26
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6B5E,MAAM,UAAU,IAAI,CAAC,IAAa,EAAE,SAAiB;IACnD,0EAA0E;IAC1E,4CAA4C;IAC5C,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACrE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAa,EAAE,MAAc;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;QAC1E,MAAM,CAAC,GAAG,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;QACtE,8DAA8D;QAC9D,oEAAoE;QACpE,kEAAkE;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ import postgres from "postgres";
2
+ /**
3
+ * The one place a connection is opened — and the one place `prepare: false`
4
+ * is explained, so it does not get "optimised" back on.
5
+ *
6
+ * 08 §3.2 describes the blessed path as *"marketplace Postgres attached in the
7
+ * click, handing the app a **pooled** `DATABASE_URL`"*. A pooled endpoint is
8
+ * PgBouncer in transaction mode: a connection is handed to one transaction and
9
+ * then to somebody else, so a prepared statement created on it may be executed
10
+ * later against a different backend that never saw the PREPARE.
11
+ *
12
+ * The failure is not a clean error at the point of the mistake. It surfaces
13
+ * as `cached plan must not change result type` on some later query, usually
14
+ * after a migration has changed a table the plan referenced — which reads as
15
+ * database corruption and is nothing of the kind. It was found here by running
16
+ * the share-link suite twice against one pooled Neon endpoint: the second run
17
+ * dropped and recreated the schema, and every query afterwards failed.
18
+ *
19
+ * THE COST IS ONE EXTRA ROUND TRIP PER PARAMETERISED STATEMENT, EVERY TIME —
20
+ * not per unique query shape, which is what this comment used to claim.
21
+ * postgres.js sets `describeFirst` whenever a query has parameters and is not
22
+ * prepared (connection.js: `parameters.length && !q.prepared`), so it sends
23
+ * Describe+Flush, waits, then Bind+Execute+Sync. Measured against London from
24
+ * Seoul, 4 runs each: a parameterised `SELECT $1::int` costs **520 / 521 / 509
25
+ * / 519 ms** unprepared against **539 / 268 / 270 / 264 ms** prepared, on a
26
+ * link whose one-way is ~260 ms. Every statement in a request pays it.
27
+ *
28
+ * It is still the right default, and the reason is deployment rather than
29
+ * performance: 08 §3.2's blessed path hands out a pooled URL, and this is the
30
+ * setting that makes that URL work. But the cost is now written down, because
31
+ * it is the largest single term in the create path's latency and the next
32
+ * person to look at that number should not have to measure it again.
33
+ *
34
+ * (For the record, postgres.js DOES retry both `RevalidateCachedQuery` and
35
+ * `FetchPreparedStatement` — so re-enabling `prepare` is a real option to
36
+ * evaluate, not a settled impossibility. It wants its own test against a
37
+ * schema change on a pooled endpoint, which is the scenario that produced the
38
+ * original failure, and it should not be flipped without one.)
39
+ */
40
+ export function connect(url, opts = {}) {
41
+ return postgres(url, {
42
+ max: opts.max ?? 10,
43
+ prepare: false,
44
+ // The server has its own logging rules (app.ts); postgres.js NOTICE output
45
+ // would bypass them.
46
+ onnotice: () => { },
47
+ });
48
+ }
49
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/db/connect.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,OAAyB,EAAE;IAC9D,OAAO,QAAQ,CAAC,GAAG,EAAE;QACnB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE;QACnB,OAAO,EAAE,KAAK;QACd,2EAA2E;QAC3E,qBAAqB;QACrB,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;KACnB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,210 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import postgres from "postgres";
6
+ /**
7
+ * The migrator — 08 §5.
8
+ *
9
+ * Hand-written, and deliberately not `drizzle-kit migrate`. A9 promises `npx
10
+ * snagset` against one `DATABASE_URL` works, which means migrations run at BOOT
11
+ * on a machine nobody is watching, on a platform that may start several
12
+ * instances of the process at once. That is a concurrency problem before it is
13
+ * a schema problem, and it is the half a migration runner bolted onto a CLI
14
+ * does not solve.
15
+ *
16
+ * The four rules that make an unattended boot safe:
17
+ *
18
+ * 1. **One advisory lock, taken first.** Vercel starts N lambdas; a Railway
19
+ * deploy overlaps old and new containers. Without the lock, two processes
20
+ * run `CREATE TABLE` concurrently and the loser dies on a duplicate — which
21
+ * looks exactly like a corrupt database to whoever reads the logs. With it,
22
+ * the loser waits and then finds there is nothing to do.
23
+ * 2. **Checksums, compared every boot.** A migration file that changed after
24
+ * it was applied is the single most expensive thing to discover late: every
25
+ * environment is now a different schema wearing the same version number.
26
+ * Refused loudly, exit 4, before a single statement runs.
27
+ * 3. **Each file in ONE transaction.** Postgres does transactional DDL, so a
28
+ * migration either happened or did not. The `.no-txn.sql` suffix opts out
29
+ * for the statements that cannot run inside one (`CREATE INDEX
30
+ * CONCURRENTLY`); nothing needs it at 0001, and the mechanism exists so
31
+ * that the first thing that does is not also the first time this code is
32
+ * asked to handle it.
33
+ * 4. **A short `lock_timeout`, per file.** A migration blocked behind a long
34
+ * read holds the advisory lock while every other instance queues behind it.
35
+ * Three seconds, then fail cleanly and let the platform retry — a failed
36
+ * deploy is recoverable, a deploy wedged for an hour is an outage.
37
+ *
38
+ * Exit codes, because a boot script has to be able to tell these apart:
39
+ * 0 up to date (or applied cleanly)
40
+ * 3 could not connect / could not take the lock
41
+ * 4 refused: a checksum changed, or an applied migration is missing
42
+ * 1 a migration failed to apply
43
+ */
44
+ const HERE = dirname(fileURLToPath(import.meta.url));
45
+ const DIR = join(HERE, "migrations");
46
+ /**
47
+ * The advisory lock id. A constant, because every instance has to pick the
48
+ * SAME number without coordinating — that is the entire mechanism.
49
+ *
50
+ * 0x63644D47 = 1667517767, the ASCII for "cdMG". Kept rather than re-derived
51
+ * from the new name: the number is meaningless by design, and changing it in a
52
+ * fleet where an old instance still holds the old one is precisely the
53
+ * split-brain the lock exists to prevent. Rename residue elsewhere is a bug;
54
+ * here it is the point.
55
+ */
56
+ export const MIGRATION_LOCK_ID = 0x63644d47;
57
+ const sha256 = (s) => createHash("sha256").update(s, "utf8").digest("hex");
58
+ /** Files in lexical order — `0001_…` before `0002_…`, which is why they are numbered. */
59
+ function files() {
60
+ return readdirSync(DIR)
61
+ .filter((f) => f.endsWith(".sql"))
62
+ .sort()
63
+ .map((name) => {
64
+ const sql = readFileSync(join(DIR, name), "utf8");
65
+ return { name, sql, checksum: sha256(sql), txn: !name.endsWith(".no-txn.sql") };
66
+ });
67
+ }
68
+ export class MigrationError extends Error {
69
+ exitCode;
70
+ constructor(message, exitCode) {
71
+ super(message);
72
+ this.name = "MigrationError";
73
+ this.exitCode = exitCode;
74
+ }
75
+ }
76
+ export async function migrate(sql, log = console.log) {
77
+ // The ledger itself, first and outside the lock: it is `IF NOT EXISTS`, it is
78
+ // the only DDL that can safely race, and every later step reads it.
79
+ await sql `
80
+ CREATE TABLE IF NOT EXISTS _migrations (
81
+ name text PRIMARY KEY,
82
+ checksum text NOT NULL,
83
+ applied_at timestamptz NOT NULL DEFAULT now(),
84
+ duration_ms integer NOT NULL
85
+ )
86
+ `;
87
+ // Rule 1. Session-scoped, released in the `finally` — not `pg_advisory_xact_lock`,
88
+ // because each migration wants its own transaction inside the held lock.
89
+ const lock = await sql `
90
+ SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID})
91
+ `;
92
+ if (!lock[0]?.pg_try_advisory_lock) {
93
+ // Another instance is mid-migration. Waiting is correct: by the time the
94
+ // lock frees, the work is done and this process finds nothing to apply.
95
+ log("migrate: another instance holds the lock — waiting");
96
+ await sql `SELECT pg_advisory_lock(${MIGRATION_LOCK_ID})`;
97
+ }
98
+ try {
99
+ const done = new Map((await sql `SELECT name, checksum FROM _migrations`).map((r) => [r.name, r.checksum]));
100
+ const pending = [];
101
+ // Rule 2, and it runs over EVERY file before any of them is applied. A
102
+ // half-migrated database whose next file is the corrupt one is worse than
103
+ // one that refused at the door.
104
+ /*
105
+ * WHEN 0001 STOPS BEING REGENERABLE, and why it still is today.
106
+ *
107
+ * 0001_init.sql is GENERATED from 05 §3 (gen-migration.mjs), so §3 is the
108
+ * single description of the schema and the file is its rendering. That
109
+ * works right up to the moment a second person has applied it, and then it
110
+ * cannot: an applied migration is history, and history that regenerates is
111
+ * a checksum mismatch on somebody else's database.
112
+ *
113
+ * The switch point is M2-35, where schemaVersion freezes for publish
114
+ * (spine §0.2). Before it there is exactly one real install -- the studio's
115
+ * own dogfood -- and correcting §3 plus bringing that one database along by
116
+ * hand is both cheaper and more honest than a numbered ALTER for a schema
117
+ * nobody else has. After it, §3 describes the CURRENT schema, 0001 is
118
+ * frozen at what publish shipped, and every change is a new numbered file.
119
+ * The generator's check has to change shape with it: from "0001 renders §3"
120
+ * to "0001 plus every later file EQUALS §3", which needs a scratch database
121
+ * to answer and is therefore a gate rather than a pure function.
122
+ *
123
+ * The rule below does not soften either way. It is what makes that switch
124
+ * point real rather than aspirational.
125
+ */
126
+ for (const f of files()) {
127
+ const seen = done.get(f.name);
128
+ if (seen === undefined) {
129
+ pending.push(f);
130
+ }
131
+ else if (seen !== f.checksum) {
132
+ throw new MigrationError(`${f.name} has changed since it was applied (recorded ${seen.slice(0, 12)}…, ` +
133
+ `file is ${f.checksum.slice(0, 12)}…). An applied migration is history: ` +
134
+ `add a new file instead of editing this one.`, 4);
135
+ }
136
+ done.delete(f.name);
137
+ }
138
+ // Whatever is left was applied and no longer exists on disk — a rollback to
139
+ // an older build against a newer database. Refused for the same reason:
140
+ // the code is about to assume a schema that is not the one present.
141
+ if (done.size) {
142
+ throw new MigrationError(`the database has migrations this build does not ship: ${[...done.keys()].join(", ")}. ` +
143
+ `This is an older release running against a newer database.`, 4);
144
+ }
145
+ if (!pending.length) {
146
+ log(`migrate: up to date (${files().length} applied)`);
147
+ return { applied: [], alreadyApplied: files().length };
148
+ }
149
+ const applied = [];
150
+ for (const f of pending) {
151
+ const started = Date.now();
152
+ log(`migrate: applying ${f.name}…`);
153
+ const run = async (tx) => {
154
+ // Rule 4, per file and inside the transaction so it reverts with it.
155
+ await tx `SET LOCAL lock_timeout = '3s'`;
156
+ await tx.unsafe(f.sql);
157
+ await tx `
158
+ INSERT INTO _migrations (name, checksum, duration_ms)
159
+ VALUES (${f.name}, ${f.checksum}, ${Date.now() - started})
160
+ `;
161
+ };
162
+ // Rule 3. `.no-txn.sql` runs on the connection directly — its statements
163
+ // are the ones Postgres refuses inside a transaction block.
164
+ if (f.txn)
165
+ await sql.begin(run);
166
+ else
167
+ await run(sql);
168
+ log(`migrate: applied ${f.name} in ${Date.now() - started} ms`);
169
+ applied.push(f.name);
170
+ }
171
+ return { applied, alreadyApplied: files().length - applied.length };
172
+ }
173
+ finally {
174
+ await sql `SELECT pg_advisory_unlock(${MIGRATION_LOCK_ID})`;
175
+ }
176
+ }
177
+ /**
178
+ * `pnpm migrate` / `snagset migrate`. The library above takes a connection so a
179
+ * test can hand it one; only this half reads the environment and exits.
180
+ */
181
+ if (import.meta.url === `file://${process.argv[1]}`) {
182
+ const { loadConfig } = await import("../config.js");
183
+ let code = 0;
184
+ let sql;
185
+ try {
186
+ const cfg = loadConfig(process.env);
187
+ sql = (await import("./connect.js")).connect(cfg.required("DATABASE_URL"), { max: 1 });
188
+ await migrate(sql);
189
+ }
190
+ catch (e) {
191
+ if (e instanceof MigrationError) {
192
+ console.error(`✗ migrate: ${e.message}`);
193
+ code = e.exitCode;
194
+ }
195
+ else {
196
+ const msg = e instanceof Error ? e.message : String(e);
197
+ // A connection failure and a bad migration are different operator
198
+ // problems; 3 means "the database was not reachable", 1 means "it was,
199
+ // and it said no".
200
+ const connectionish = /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|password|SASL|SSL|does not exist/i.test(msg);
201
+ console.error(`✗ migrate: ${msg}`);
202
+ code = connectionish ? 3 : 1;
203
+ }
204
+ }
205
+ finally {
206
+ await sql?.end({ timeout: 5 });
207
+ }
208
+ process.exit(code);
209
+ }
210
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"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"}