Public: anyone can read this; the crew can change it.
Production answers every page again: a database made before the Profile line gets its missing column, and a test fails when a migration is inserted anywhere but the end #148
Check · Review · Land · Live done — Landed and live in production.
mason/deal-clock:mason/146-production-answers-every-page-again-a-da → main 2 commits fork
3 files
changed +91
−1
src/db.ts +8 −1
| @@ -14,7 +14,11 @@ import { join } from "node:path"; | ||
| 14 | 14 | import { dataDir } from "./env.ts"; |
| 15 | 15 | import { refresh, seed } from "./sample.ts"; |
| 16 | 16 | |
| 17 | /** Append, never edit: a migration that has run is history. */ | |
| 17 | /** | |
| 18 | * Append-only. A migration that has run is history: never edit, remove, | |
| 19 | * reorder or insert before the end, because a database records only how many | |
| 20 | * ran. src/migrations.test.ts pins every entry by position. | |
| 21 | */ | |
| 18 | 22 | export const migrations: string[] = [ |
| 19 | 23 | `CREATE TABLE note ( |
| 20 | 24 | id INTEGER PRIMARY KEY, |
| @@ -426,6 +430,9 @@ export const migrations: string[] = [ | ||
| 426 | 430 | `ALTER TABLE deal ADD COLUMN side TEXT NOT NULL DEFAULT 'buyer' CHECK (side IN ('buyer', 'listing', 'both'))`, |
| 427 | 431 | // The agent's commission rate on a deal, in hundredths of a percent (250 is 2.5%), 0 to 10%; null while none is set. |
| 428 | 432 | `ALTER TABLE deal ADD COLUMN commission_rate INTEGER CHECK (commission_rate IS NULL OR commission_rate BETWEEN 0 AND 1000)`, |
| 433 | // Repair: the Profile-line change inserted the profile line mid-list, so databases already past | |
| 434 | // that position never got it. Tolerated as a duplicate where it exists. | |
| 435 | `ALTER TABLE profile ADD COLUMN line TEXT NOT NULL DEFAULT '' CHECK (length(line) <= 200)`, | |
| 429 | 436 | ]; |
| 430 | 437 | |
| 431 | 438 | export function openDb(dir = dataDir): Database { |
src/migrations.pin.json added +55
| @@ -0,0 +1,55 @@ | ||
| 1 | [ | |
| 2 | "18e7c357d1afe0f3ec30db9a0d1113e7f768bd7562f63bc419cdd3a919bad905", | |
| 3 | "319e8f4c7771ed1d2ddf32bd47eebf082f89eb79c7a7cd0c3e9fc9ccdb85d6f0", | |
| 4 | "4ad1dcd49a8e082b178cee55072951b2de454236d30f1d43c248a81f94a7e138", | |
| 5 | "7fade1decc6d9a7a26720ec1d19f32459f21d96cb304bf226767f5d1ef9df55c", | |
| 6 | "f3ac0eccea193cee78e58abd5ccedcbb243e32f8b08b3194b5c5cefe7ab7338b", | |
| 7 | "4ca087a9251d9b104dd68d1e269080938c2113e3655dc47800fb7a2a843fda91", | |
| 8 | "6b957a7fefc2993fbadb1cdb54b88d05e9fc5c957da25e6ab32b8663d8d8b3f4", | |
| 9 | "ada96ffaf3c06910f0b670f01ab867845cab735985fcd9e23ddc55f964c52add", | |
| 10 | "eefbc3eb101b88f24e509d9d8f68335581d30b3d05d26bcf513acc84d8595f71", | |
| 11 | "2b1795ce253701f4dd79f3511911934652999361051360105455731539a84997", | |
| 12 | "791db72aee2cc8f952300b1f9ba8347d2689763206b0c6c04cdb168894e8197b", | |
| 13 | "f266f8e883fdf18d6a1027338df2eac08d21f0972baf0685f49aef146816d2e7", | |
| 14 | "b9200d9459a6e60206a98c6de4618e6ffeea4126b74c8aa5d7ef0561f9f052e4", | |
| 15 | "23415bdc4bee35d8d64a003f96fb6aefa0980e9e5047b16326adb7fdf2f541f2", | |
| 16 | "07bf073dc048e9c0ceb787185a777403685899065d7245b3a45c954ae0e5d987", | |
| 17 | "254722cb91b59deaceae8826d92d5cf240df89310748950372571762d4d6850c", | |
| 18 | "c94bda45c56d49ccefee26a950eec45cd37b80cd7a28814bc0cb0de337a53491", | |
| 19 | "650b0c5596ba4ebd1f21f7d8f9888b2172b3139b917ce31ded259354090bd3a7", | |
| 20 | "1e326b0e83e0a6e61543a06e772d50724bd00695b103b2bf6bf18daddea0a667", | |
| 21 | "17185fad2336c8251c7adfd3321c1846d78e63e259af234a709964a50af2a3c7", | |
| 22 | "46dec56ebb90a5f6ceddd538633412a8066ebe77b0ee589c903118cf9a4e7f0c", | |
| 23 | "b698040ff2fdb3db7d8db3495ef6b7fe9291194fd923c8d6624a386ad67d044a", | |
| 24 | "2569347b7f614579d909ce927f8b08788f88d65f745b63ddf8de8d11ee63fb02", | |
| 25 | "3f1fbb3a7550a923ebf504a87ae06231cd62ad0faf858a44e36773cd7c6198bb", | |
| 26 | "1efe0c811b2f7046249fdba4f1629fb74f6754937917b5c8624cba4c196cdac4", | |
| 27 | "7d15b45bb6b20cc4b0b8a2f4f5fc06a6d9436017d9b04642ada60dfc4396aa80", | |
| 28 | "534ce8607106ac086dcfd5413e789066c10f2a6060d14cc7c4bbee805807727e", | |
| 29 | "72f67a8e8f06f9b9fcac58ff22eb3f4222c77a0968da5981d4e7dce8d4b8ce7d", | |
| 30 | "3f108f94a689e99698937f8329e65ddd129ca6d7046f97ddd354db39c2635b4b", | |
| 31 | "b68a6f74ca6c1e9bd3d3c16cce9d8d4985250f0ecb05d34de33c47ecbf4252d7", | |
| 32 | "7f8986d8e50e0e8366d4f67d58b3121032ec6e32fa594659b1bd42c5fc79d0de", | |
| 33 | "26d27c436e6fc94690d0cbea29af62adfdf4360eca50940bd58bd63e11bb2a29", | |
| 34 | "ac0af3e527d87fe393f7dd06607754668e88e655386adbee973a0604cea25b7a", | |
| 35 | "3175f6484390097d68daac7f987e9461d10a7b4f4b85216c0eebdb9a02d0c714", | |
| 36 | "fcf79269e83bc0d9c0e6fd9bb4b0e2c29676ca2674e3555914bc76f8b0653d78", | |
| 37 | "484e2ce356a18a28f5878b33be3f50dcbb0adbc09c35f37cef438bef0aaea9c5", | |
| 38 | "9900ff28f61d9dd16e3cebc1ca9203d6a212076b6c8aff8f2c7ba5eaffaa38dc", | |
| 39 | "5f4bf810d1c3d703865b29f0bbab9f16c064fa2054a7ceaa7eb2c5f4041811d7", | |
| 40 | "8a2d7d68ce199e88441da7d5eb8a4780f4d1cc5caadeea9b2c3bd8dc1886dcfd", | |
| 41 | "1543897fd9b75e65d7727e3c6f6ddf0990a1e925e08e1e145ecc2d54dba0767a", | |
| 42 | "b4ae3234ae229a16146d06fc391217a838e862ea25a418a2eb0f63a15a49eb62", | |
| 43 | "db97a5da0b4aba4612841222fdae50fc27fff51383f74c5d525ab9bf161fccc6", | |
| 44 | "dec7a2e2f471dd1b426da3cd890af77e2852fb71268bb3c8271c66e3d72971f2", | |
| 45 | "d08fe630a2ddaacc08d5945dc67b5103052a9096ac5a27aca68bc7faa66ef70d", | |
| 46 | "7ef5159c0e2d5ae4df22f23263024f8f70298e935112d1042cf1ee1224ca2f23", | |
| 47 | "9e45e8c8bd82eab1cae6ffdba0e122be3831f8beebc157941490811897505c0c", | |
| 48 | "83472cd59a1fe7dd540fba000f0a110b52dd6b5949d01202f6d8cd527d8489a4", | |
| 49 | "59bd37adacf69cbe607acf33e3ac82a386f5acbef328a047fb77ebf9f97162cb", | |
| 50 | "7255f8baa8700202884be4fbfbd925383182f00a049f8fe49df190cabbf90632", | |
| 51 | "cf6972801626419644e27b9bc4e6d338367e1c6fe67bb5ca338dc0c9bb408fc1", | |
| 52 | "fd1b67b66fb8ff34326ca5cb86ab04678619dfea420f21d8f85b2d05c999907f", | |
| 53 | "f836a1da11c1f51a255301c4ee62c441612baada0345c1310f4149e5d5ccf5b9", | |
| 54 | "72f67a8e8f06f9b9fcac58ff22eb3f4222c77a0968da5981d4e7dce8d4b8ce7d" | |
| 55 | ] |
src/migrations.test.ts added +28
| @@ -0,0 +1,28 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import { Database } from "bun:sqlite"; | |
| 3 | import { migrate, migrations } from "./db.ts"; | |
| 4 | import { agentOf } from "./profile.ts"; | |
| 5 | import pinned from "./migrations.pin.json"; | |
| 6 | ||
| 7 | const hash = (sql: string) => new Bun.CryptoHasher("sha256").update(sql).digest("hex"); | |
| 8 | ||
| 9 | test("every migration is pinned by position; a pull may only append", () => { | |
| 10 | const now = migrations.map(hash); | |
| 11 | expect(now.length).toBeGreaterThanOrEqual(pinned.length); | |
| 12 | pinned.forEach((h, i) => expect(now[i], `migration ${i} was edited, moved or removed`).toBe(h)); | |
| 13 | }); | |
| 14 | ||
| 15 | test("a database made before the Profile line gets the column and reads the agent", () => { | |
| 16 | const before = migrations.filter((m) => !/profile ADD COLUMN line|commission_rate/.test(m)); | |
| 17 | const db = new Database(":memory:"); | |
| 18 | db.exec("CREATE TABLE schema_version (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))"); | |
| 19 | before.forEach((sql, i) => { | |
| 20 | db.exec(sql); | |
| 21 | db.query("INSERT INTO schema_version (version) VALUES (?1)").run(i + 1); | |
| 22 | }); | |
| 23 | db.exec("INSERT INTO profile (id, name, brokerage, phone, email) VALUES (1, 'Ana', 'B', '555', '[email protected]')"); | |
| 24 | expect(migrate(db)).toBe(migrations.length); | |
| 25 | expect(agentOf(db).name).toBe("Ana"); | |
| 26 | expect(db.query("SELECT line FROM profile").get()).toEqual({ line: "" }); | |
| 27 | expect(db.query("SELECT commission_rate FROM deal").all()).toEqual([]); | |
| 28 | }); |
new above
offline