@intentius/sql-yodeler 0.0.0-stage → 0.2.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 (324) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +112 -2
  3. package/dist/bin.d.ts +2 -0
  4. package/dist/bin.js +29 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chant/apply.d.ts +59 -0
  7. package/dist/chant/apply.js +116 -0
  8. package/dist/chant/apply.js.map +1 -0
  9. package/dist/chant/build.d.ts +34 -0
  10. package/dist/chant/build.js +48 -0
  11. package/dist/chant/build.js.map +1 -0
  12. package/dist/chant/drift.d.ts +112 -0
  13. package/dist/chant/drift.js +238 -0
  14. package/dist/chant/drift.js.map +1 -0
  15. package/dist/chant/plan.d.ts +45 -0
  16. package/dist/chant/plan.js +61 -0
  17. package/dist/chant/plan.js.map +1 -0
  18. package/dist/chant/process.d.ts +41 -0
  19. package/dist/chant/process.js +87 -0
  20. package/dist/chant/process.js.map +1 -0
  21. package/dist/chant/runnable.d.ts +19 -0
  22. package/dist/chant/runnable.js +47 -0
  23. package/dist/chant/runnable.js.map +1 -0
  24. package/dist/cli.d.ts +8 -0
  25. package/dist/cli.js +99 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/command.d.ts +23 -0
  28. package/dist/command.js +13 -0
  29. package/dist/command.js.map +1 -0
  30. package/dist/commands/apply.d.ts +3 -0
  31. package/dist/commands/apply.js +426 -0
  32. package/dist/commands/apply.js.map +1 -0
  33. package/dist/commands/approve.d.ts +5 -0
  34. package/dist/commands/approve.js +111 -0
  35. package/dist/commands/approve.js.map +1 -0
  36. package/dist/commands/drift.d.ts +14 -0
  37. package/dist/commands/drift.js +267 -0
  38. package/dist/commands/drift.js.map +1 -0
  39. package/dist/commands/index.d.ts +3 -0
  40. package/dist/commands/index.js +28 -0
  41. package/dist/commands/index.js.map +1 -0
  42. package/dist/commands/init.d.ts +6 -0
  43. package/dist/commands/init.js +139 -0
  44. package/dist/commands/init.js.map +1 -0
  45. package/dist/commands/lint.d.ts +3 -0
  46. package/dist/commands/lint.js +202 -0
  47. package/dist/commands/lint.js.map +1 -0
  48. package/dist/commands/mcp.d.ts +3 -0
  49. package/dist/commands/mcp.js +50 -0
  50. package/dist/commands/mcp.js.map +1 -0
  51. package/dist/commands/new.d.ts +5 -0
  52. package/dist/commands/new.js +292 -0
  53. package/dist/commands/new.js.map +1 -0
  54. package/dist/commands/override.d.ts +3 -0
  55. package/dist/commands/override.js +118 -0
  56. package/dist/commands/override.js.map +1 -0
  57. package/dist/commands/plan.d.ts +3 -0
  58. package/dist/commands/plan.js +183 -0
  59. package/dist/commands/plan.js.map +1 -0
  60. package/dist/commands/rebase.d.ts +3 -0
  61. package/dist/commands/rebase.js +167 -0
  62. package/dist/commands/rebase.js.map +1 -0
  63. package/dist/commands/repair.d.ts +14 -0
  64. package/dist/commands/repair.js +125 -0
  65. package/dist/commands/repair.js.map +1 -0
  66. package/dist/commands/status.d.ts +7 -0
  67. package/dist/commands/status.js +54 -0
  68. package/dist/commands/status.js.map +1 -0
  69. package/dist/config/define.d.ts +148 -0
  70. package/dist/config/define.js +20 -0
  71. package/dist/config/define.js.map +1 -0
  72. package/dist/config/index.d.ts +11 -0
  73. package/dist/config/index.js +12 -0
  74. package/dist/config/index.js.map +1 -0
  75. package/dist/config/load.d.ts +17 -0
  76. package/dist/config/load.js +162 -0
  77. package/dist/config/load.js.map +1 -0
  78. package/dist/config/settings.d.ts +53 -0
  79. package/dist/config/settings.js +116 -0
  80. package/dist/config/settings.js.map +1 -0
  81. package/dist/env-args.d.ts +24 -0
  82. package/dist/env-args.js +92 -0
  83. package/dist/env-args.js.map +1 -0
  84. package/dist/exit.d.ts +20 -0
  85. package/dist/exit.js +28 -0
  86. package/dist/exit.js.map +1 -0
  87. package/dist/history/client.d.ts +25 -0
  88. package/dist/history/client.js +42 -0
  89. package/dist/history/client.js.map +1 -0
  90. package/dist/history/ddl.d.ts +58 -0
  91. package/dist/history/ddl.js +100 -0
  92. package/dist/history/ddl.js.map +1 -0
  93. package/dist/history/index.d.ts +25 -0
  94. package/dist/history/index.js +26 -0
  95. package/dist/history/index.js.map +1 -0
  96. package/dist/history/lock.d.ts +104 -0
  97. package/dist/history/lock.js +230 -0
  98. package/dist/history/lock.js.map +1 -0
  99. package/dist/history/postgres/ddl.d.ts +22 -0
  100. package/dist/history/postgres/ddl.js +47 -0
  101. package/dist/history/postgres/ddl.js.map +1 -0
  102. package/dist/history/postgres/index.d.ts +12 -0
  103. package/dist/history/postgres/index.js +13 -0
  104. package/dist/history/postgres/index.js.map +1 -0
  105. package/dist/history/postgres/lock.d.ts +48 -0
  106. package/dist/history/postgres/lock.js +95 -0
  107. package/dist/history/postgres/lock.js.map +1 -0
  108. package/dist/history/postgres/store.d.ts +39 -0
  109. package/dist/history/postgres/store.js +68 -0
  110. package/dist/history/postgres/store.js.map +1 -0
  111. package/dist/history/rows.d.ts +96 -0
  112. package/dist/history/rows.js +73 -0
  113. package/dist/history/rows.js.map +1 -0
  114. package/dist/history/store.d.ts +72 -0
  115. package/dist/history/store.js +134 -0
  116. package/dist/history/store.js.map +1 -0
  117. package/dist/history/topology.d.ts +41 -0
  118. package/dist/history/topology.js +54 -0
  119. package/dist/history/topology.js.map +1 -0
  120. package/dist/index.d.ts +10 -0
  121. package/dist/index.js +10 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/init/adopt.d.ts +70 -0
  124. package/dist/init/adopt.js +143 -0
  125. package/dist/init/adopt.js.map +1 -0
  126. package/dist/init/baseline.d.ts +61 -0
  127. package/dist/init/baseline.js +88 -0
  128. package/dist/init/baseline.js.map +1 -0
  129. package/dist/init/files.d.ts +23 -0
  130. package/dist/init/files.js +77 -0
  131. package/dist/init/files.js.map +1 -0
  132. package/dist/init/index.d.ts +16 -0
  133. package/dist/init/index.js +17 -0
  134. package/dist/init/index.js.map +1 -0
  135. package/dist/init/live.d.ts +39 -0
  136. package/dist/init/live.js +68 -0
  137. package/dist/init/live.js.map +1 -0
  138. package/dist/lint/check.d.ts +88 -0
  139. package/dist/lint/check.js +227 -0
  140. package/dist/lint/check.js.map +1 -0
  141. package/dist/lint/config.d.ts +21 -0
  142. package/dist/lint/config.js +43 -0
  143. package/dist/lint/config.js.map +1 -0
  144. package/dist/lint/index.d.ts +31 -0
  145. package/dist/lint/index.js +43 -0
  146. package/dist/lint/index.js.map +1 -0
  147. package/dist/lint/project-rules.d.ts +19 -0
  148. package/dist/lint/project-rules.js +32 -0
  149. package/dist/lint/project-rules.js.map +1 -0
  150. package/dist/lint/report.d.ts +7 -0
  151. package/dist/lint/report.js +48 -0
  152. package/dist/lint/report.js.map +1 -0
  153. package/dist/lint/rules.d.ts +53 -0
  154. package/dist/lint/rules.js +42 -0
  155. package/dist/lint/rules.js.map +1 -0
  156. package/dist/lint/silences.d.ts +49 -0
  157. package/dist/lint/silences.js +91 -0
  158. package/dist/lint/silences.js.map +1 -0
  159. package/dist/lint/statements.d.ts +38 -0
  160. package/dist/lint/statements.js +120 -0
  161. package/dist/lint/statements.js.map +1 -0
  162. package/dist/mcp/server.d.ts +40 -0
  163. package/dist/mcp/server.js +106 -0
  164. package/dist/mcp/server.js.map +1 -0
  165. package/dist/mcp/tools.d.ts +72 -0
  166. package/dist/mcp/tools.js +178 -0
  167. package/dist/mcp/tools.js.map +1 -0
  168. package/dist/migrations/checks.d.ts +28 -0
  169. package/dist/migrations/checks.js +97 -0
  170. package/dist/migrations/checks.js.map +1 -0
  171. package/dist/migrations/checksum.d.ts +56 -0
  172. package/dist/migrations/checksum.js +72 -0
  173. package/dist/migrations/checksum.js.map +1 -0
  174. package/dist/migrations/create.d.ts +75 -0
  175. package/dist/migrations/create.js +120 -0
  176. package/dist/migrations/create.js.map +1 -0
  177. package/dist/migrations/directory.d.ts +119 -0
  178. package/dist/migrations/directory.js +229 -0
  179. package/dist/migrations/directory.js.map +1 -0
  180. package/dist/migrations/document.d.ts +106 -0
  181. package/dist/migrations/document.js +83 -0
  182. package/dist/migrations/document.js.map +1 -0
  183. package/dist/migrations/index.d.ts +21 -0
  184. package/dist/migrations/index.js +22 -0
  185. package/dist/migrations/index.js.map +1 -0
  186. package/dist/pr/comment.d.ts +32 -0
  187. package/dist/pr/comment.js +99 -0
  188. package/dist/pr/comment.js.map +1 -0
  189. package/dist/pr/forge.d.ts +54 -0
  190. package/dist/pr/forge.js +119 -0
  191. package/dist/pr/forge.js.map +1 -0
  192. package/dist/pr/issue.d.ts +68 -0
  193. package/dist/pr/issue.js +107 -0
  194. package/dist/pr/issue.js.map +1 -0
  195. package/dist/pr/model.d.ts +99 -0
  196. package/dist/pr/model.js +137 -0
  197. package/dist/pr/model.js.map +1 -0
  198. package/dist/pr/render.d.ts +29 -0
  199. package/dist/pr/render.js +279 -0
  200. package/dist/pr/render.js.map +1 -0
  201. package/dist/project.d.ts +62 -0
  202. package/dist/project.js +92 -0
  203. package/dist/project.js.map +1 -0
  204. package/dist/rebase/index.d.ts +121 -0
  205. package/dist/rebase/index.js +390 -0
  206. package/dist/rebase/index.js.map +1 -0
  207. package/dist/rebase/repair.d.ts +41 -0
  208. package/dist/rebase/repair.js +68 -0
  209. package/dist/rebase/repair.js.map +1 -0
  210. package/dist/replay/clickhouse.d.ts +33 -0
  211. package/dist/replay/clickhouse.js +106 -0
  212. package/dist/replay/clickhouse.js.map +1 -0
  213. package/dist/replay/index.d.ts +39 -0
  214. package/dist/replay/index.js +57 -0
  215. package/dist/replay/index.js.map +1 -0
  216. package/dist/replay/postgres.d.ts +31 -0
  217. package/dist/replay/postgres.js +140 -0
  218. package/dist/replay/postgres.js.map +1 -0
  219. package/dist/replay/replay.d.ts +114 -0
  220. package/dist/replay/replay.js +98 -0
  221. package/dist/replay/replay.js.map +1 -0
  222. package/dist/replay/report.d.ts +18 -0
  223. package/dist/replay/report.js +72 -0
  224. package/dist/replay/report.js.map +1 -0
  225. package/dist/replay/shared.d.ts +28 -0
  226. package/dist/replay/shared.js +77 -0
  227. package/dist/replay/shared.js.map +1 -0
  228. package/dist/schemas.d.ts +34 -0
  229. package/dist/schemas.js +38 -0
  230. package/dist/schemas.js.map +1 -0
  231. package/dist/server.d.ts +40 -0
  232. package/dist/server.js +87 -0
  233. package/dist/server.js.map +1 -0
  234. package/dist/steps/backfill.d.ts +132 -0
  235. package/dist/steps/backfill.js +307 -0
  236. package/dist/steps/backfill.js.map +1 -0
  237. package/dist/steps/index.d.ts +52 -0
  238. package/dist/steps/index.js +52 -0
  239. package/dist/steps/index.js.map +1 -0
  240. package/dist/steps/kinds.d.ts +101 -0
  241. package/dist/steps/kinds.js +143 -0
  242. package/dist/steps/kinds.js.map +1 -0
  243. package/dist/steps/postgres-migration.d.ts +95 -0
  244. package/dist/steps/postgres-migration.js +149 -0
  245. package/dist/steps/postgres-migration.js.map +1 -0
  246. package/dist/steps/rebuild.d.ts +97 -0
  247. package/dist/steps/rebuild.js +155 -0
  248. package/dist/steps/rebuild.js.map +1 -0
  249. package/dist/steps/run.d.ts +45 -0
  250. package/dist/steps/run.js +72 -0
  251. package/dist/steps/run.js.map +1 -0
  252. package/dist/topology/render.d.ts +17 -0
  253. package/dist/topology/render.js +17 -0
  254. package/dist/topology/render.js.map +1 -0
  255. package/dist/version.d.ts +2 -0
  256. package/dist/version.js +11 -0
  257. package/dist/version.js.map +1 -0
  258. package/dist/versioned/approval.d.ts +35 -0
  259. package/dist/versioned/approval.js +53 -0
  260. package/dist/versioned/approval.js.map +1 -0
  261. package/dist/versioned/checks.d.ts +29 -0
  262. package/dist/versioned/checks.js +54 -0
  263. package/dist/versioned/checks.js.map +1 -0
  264. package/dist/versioned/clickhouse.d.ts +24 -0
  265. package/dist/versioned/clickhouse.js +42 -0
  266. package/dist/versioned/clickhouse.js.map +1 -0
  267. package/dist/versioned/digest.d.ts +77 -0
  268. package/dist/versioned/digest.js +69 -0
  269. package/dist/versioned/digest.js.map +1 -0
  270. package/dist/versioned/driver.d.ts +51 -0
  271. package/dist/versioned/driver.js +13 -0
  272. package/dist/versioned/driver.js.map +1 -0
  273. package/dist/versioned/engine.d.ts +206 -0
  274. package/dist/versioned/engine.js +370 -0
  275. package/dist/versioned/engine.js.map +1 -0
  276. package/dist/versioned/explain.d.ts +123 -0
  277. package/dist/versioned/explain.js +294 -0
  278. package/dist/versioned/explain.js.map +1 -0
  279. package/dist/versioned/index.d.ts +46 -0
  280. package/dist/versioned/index.js +77 -0
  281. package/dist/versioned/index.js.map +1 -0
  282. package/dist/versioned/op.d.ts +72 -0
  283. package/dist/versioned/op.js +185 -0
  284. package/dist/versioned/op.js.map +1 -0
  285. package/dist/versioned/policy.d.ts +67 -0
  286. package/dist/versioned/policy.js +161 -0
  287. package/dist/versioned/policy.js.map +1 -0
  288. package/dist/versioned/postgres/catalog.d.ts +33 -0
  289. package/dist/versioned/postgres/catalog.js +34 -0
  290. package/dist/versioned/postgres/catalog.js.map +1 -0
  291. package/dist/versioned/postgres/driver.d.ts +71 -0
  292. package/dist/versioned/postgres/driver.js +140 -0
  293. package/dist/versioned/postgres/driver.js.map +1 -0
  294. package/dist/versioned/postgres/executor.d.ts +104 -0
  295. package/dist/versioned/postgres/executor.js +172 -0
  296. package/dist/versioned/postgres/executor.js.map +1 -0
  297. package/dist/versioned/postgres/index.d.ts +11 -0
  298. package/dist/versioned/postgres/index.js +12 -0
  299. package/dist/versioned/postgres/index.js.map +1 -0
  300. package/dist/versioned/postgres/waits.d.ts +46 -0
  301. package/dist/versioned/postgres/waits.js +78 -0
  302. package/dist/versioned/postgres/waits.js.map +1 -0
  303. package/dist/versioned/report.d.ts +25 -0
  304. package/dist/versioned/report.js +84 -0
  305. package/dist/versioned/report.js.map +1 -0
  306. package/dist/versioned/stand-down.d.ts +30 -0
  307. package/dist/versioned/stand-down.js +56 -0
  308. package/dist/versioned/stand-down.js.map +1 -0
  309. package/dist/versioned/state.d.ts +108 -0
  310. package/dist/versioned/state.js +153 -0
  311. package/dist/versioned/state.js.map +1 -0
  312. package/package.json +61 -4
  313. package/schemas/apply.schema.json +269 -0
  314. package/schemas/common.schema.json +213 -0
  315. package/schemas/drift.schema.json +36 -0
  316. package/schemas/init.schema.json +31 -0
  317. package/schemas/lint-rules.schema.json +54 -0
  318. package/schemas/lint.schema.json +290 -0
  319. package/schemas/mcp.schema.json +70 -0
  320. package/schemas/new.schema.json +71 -0
  321. package/schemas/plan.schema.json +371 -0
  322. package/schemas/rebase.schema.json +54 -0
  323. package/schemas/repair.schema.json +20 -0
  324. package/schemas/status.schema.json +310 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ddl.js","sourceRoot":"","sources":["../../../src/history/postgres/ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AAEjF,sFAAsF;AACtF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AAErF,+FAA+F;AAC/F,MAAM,CAAC,MAAM,kBAAkB,GAAuD;IACpF,CAAC,KAAK,EAAE,6BAA6B,CAAC;IACtC,CAAC,QAAQ,EAAE,eAAe,CAAC;IAC3B,CAAC,MAAM,EAAE,kEAAkE,CAAC;IAC5E,CAAC,cAAc,EAAE,eAAe,CAAC;IACjC,CAAC,QAAQ,EAAE,0BAA0B,CAAC;IACtC,CAAC,UAAU,EAAE,0BAA0B,CAAC;IACxC,CAAC,iBAAiB,EAAE,kBAAkB,CAAC;IACvC,CAAC,eAAe,EAAE,0BAA0B,CAAC;IAC7C,CAAC,QAAQ,EAAE,oEAAoE,CAAC;IAChF,CAAC,OAAO,EAAE,0BAA0B,CAAC;IACrC,CAAC,YAAY,EAAE,0BAA0B,CAAC;IAC1C,CAAC,aAAa,EAAE,0BAA0B,CAAC;IAC3C,CAAC,UAAU,EAAE,4BAA4B,CAAC;IAC1C,CAAC,MAAM,EAAE,0BAA0B,CAAC;IACpC,CAAC,aAAa,EAAE,2DAA2D,CAAC;CAC7E,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC;AAE9G,kCAAkC;AAClC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;AAEzG,kGAAkG;AAClG,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1F,OAAO;QACL,+BAA+B,OAAO,CAAC,MAAM,CAAC,EAAE;QAChD,8BAA8B,cAAc,CAAC,MAAM,CAAC,OAAO,OAAO,KAAK;QACvE,oBAAoB,cAAc,CAAC,MAAM,CAAC,6FAA6F;KACxI,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The Postgres history (#13): `<schema>.history` (default `yodeler.history`),
3
+ * the same rows as ClickHouse's, and the session advisory lock.
4
+ *
5
+ * pgHistoryDdl(schema) CREATE SCHEMA / TABLE
6
+ * new PostgresHistoryStore(client, {schema}) exists(), ensure(), rows(), state(), append(rows)
7
+ * acquireAdvisoryLock(conn, {schema, holder}) the apply lock, on a connection of its own
8
+ * lockKeys(schema) the lock's two keys
9
+ */
10
+ export * from "./ddl.js";
11
+ export * from "./lock.js";
12
+ export * from "./store.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The Postgres history (#13): `<schema>.history` (default `yodeler.history`),
3
+ * the same rows as ClickHouse's, and the session advisory lock.
4
+ *
5
+ * pgHistoryDdl(schema) CREATE SCHEMA / TABLE
6
+ * new PostgresHistoryStore(client, {schema}) exists(), ensure(), rows(), state(), append(rows)
7
+ * acquireAdvisoryLock(conn, {schema, holder}) the apply lock, on a connection of its own
8
+ * lockKeys(schema) the lock's two keys
9
+ */
10
+ export * from "./ddl.js";
11
+ export * from "./lock.js";
12
+ export * from "./store.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/history/postgres/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * One apply at a time per environment, on Postgres: a session advisory lock.
3
+ *
4
+ * `pg_try_advisory_lock(<yodel>, <history schema>)` on a connection of its
5
+ * own, opened for the lock and held for the whole apply. The two keys are
6
+ * fixed: the first is yodel's ({@link LOCK_CLASS}), the second a hash of the
7
+ * history schema's name ({@link lockKeys}), so every runner of the same
8
+ * environment asks for the same lock, and two environments with their own
9
+ * history schemas in one database do not share one. A session lock is
10
+ * Postgres's own: it is released when the session ends, however it ends
11
+ * (the runner exits, crashes, or its connection drops), so it needs no
12
+ * expiry, and a dead runner never leaves one behind.
13
+ *
14
+ * The statements run on another connection (the work connection), so the
15
+ * lock's connection is never inside a transaction a statement rolls back.
16
+ *
17
+ * A runner that finds the lock taken refuses with exit 5, as on ClickHouse,
18
+ * naming the session that holds it (its pid, its `application_name`, which
19
+ * says who, and since when). The heartbeat checks that the lock's session
20
+ * still holds it; a dropped connection stops the apply before its next
21
+ * statement.
22
+ *
23
+ * Clearing it by hand, when you know the holder is stuck: end that session,
24
+ * `SELECT pg_terminate_backend(<pid>)`.
25
+ */
26
+ import { type Lock } from "../lock.js";
27
+ import type { PgClient } from "./store.js";
28
+ /** The first key of every yodel lock: "YODL" as an int4. */
29
+ export declare const LOCK_CLASS = 1498367052;
30
+ /** The lock's two int4 keys for a history schema. Both non-negative, so `pg_locks.objid` (an oid) reads them back as they are. */
31
+ export declare function lockKeys(schema: string): [number, number];
32
+ /** A connection the lock holds for itself. */
33
+ export interface PgLockConnection extends PgClient {
34
+ end(): Promise<void>;
35
+ }
36
+ export interface PgLockOptions {
37
+ /** The history schema: the lock's second key. */
38
+ schema: string;
39
+ /** Who holds it, for the refusal another runner sees. */
40
+ holder: string;
41
+ }
42
+ /**
43
+ * Takes the advisory lock on `conn`, a connection opened for it, or throws
44
+ * {@link LockHeldError} (exit 5). On a refusal the connection is closed.
45
+ * The lock is released with the returned {@link Lock}'s `release`, which
46
+ * also closes the connection.
47
+ */
48
+ export declare function acquireAdvisoryLock(conn: PgLockConnection, options: PgLockOptions): Promise<Lock>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * One apply at a time per environment, on Postgres: a session advisory lock.
3
+ *
4
+ * `pg_try_advisory_lock(<yodel>, <history schema>)` on a connection of its
5
+ * own, opened for the lock and held for the whole apply. The two keys are
6
+ * fixed: the first is yodel's ({@link LOCK_CLASS}), the second a hash of the
7
+ * history schema's name ({@link lockKeys}), so every runner of the same
8
+ * environment asks for the same lock, and two environments with their own
9
+ * history schemas in one database do not share one. A session lock is
10
+ * Postgres's own: it is released when the session ends, however it ends
11
+ * (the runner exits, crashes, or its connection drops), so it needs no
12
+ * expiry, and a dead runner never leaves one behind.
13
+ *
14
+ * The statements run on another connection (the work connection), so the
15
+ * lock's connection is never inside a transaction a statement rolls back.
16
+ *
17
+ * A runner that finds the lock taken refuses with exit 5, as on ClickHouse,
18
+ * naming the session that holds it (its pid, its `application_name`, which
19
+ * says who, and since when). The heartbeat checks that the lock's session
20
+ * still holds it; a dropped connection stops the apply before its next
21
+ * statement.
22
+ *
23
+ * Clearing it by hand, when you know the holder is stuck: end that session,
24
+ * `SELECT pg_terminate_backend(<pid>)`.
25
+ */
26
+ import { createHash } from "node:crypto";
27
+ import { lockOwner, LockHeldError, LockLostError } from "../lock.js";
28
+ /** The first key of every yodel lock: "YODL" as an int4. */
29
+ export const LOCK_CLASS = 0x594f444c;
30
+ /** The lock's two int4 keys for a history schema. Both non-negative, so `pg_locks.objid` (an oid) reads them back as they are. */
31
+ export function lockKeys(schema) {
32
+ const digest = createHash("sha256").update(`yodel apply lock\n${schema}`, "utf8").digest();
33
+ return [LOCK_CLASS, digest.readUInt32BE(0) & 0x7fffffff];
34
+ }
35
+ /** `application_name` is 63 bytes at most; the lock's says it is yodel's and whose. */
36
+ const applicationName = (owner) => `yodel apply ${owner}`.slice(0, 63);
37
+ /**
38
+ * Takes the advisory lock on `conn`, a connection opened for it, or throws
39
+ * {@link LockHeldError} (exit 5). On a refusal the connection is closed.
40
+ * The lock is released with the returned {@link Lock}'s `release`, which
41
+ * also closes the connection.
42
+ */
43
+ export async function acquireAdvisoryLock(conn, options) {
44
+ const [k1, k2] = lockKeys(options.schema);
45
+ const owner = lockOwner(options.holder);
46
+ const where = `Postgres advisory lock (${String(k1)}, ${String(k2)}) for history schema ${options.schema}`;
47
+ try {
48
+ await conn.query("SELECT pg_catalog.set_config('application_name', $1, false)", [applicationName(owner)]);
49
+ const [row] = await conn.query("SELECT pg_catalog.pg_try_advisory_lock($1::int4, $2::int4) AS ok", [k1, k2]);
50
+ if (row?.ok !== true) {
51
+ const [held] = await conn.query(`SELECT l.pid, a.application_name AS app, pg_catalog.to_char(a.backend_start AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS since
52
+ FROM pg_catalog.pg_locks l LEFT JOIN pg_catalog.pg_stat_activity a ON a.pid = l.pid
53
+ WHERE l.locktype = 'advisory' AND l.granted AND l.classid = $1::oid AND l.objid = $2::oid AND l.objsubid = 2
54
+ LIMIT 1`, [k1, k2]);
55
+ const who = held ? `${held.app || "(no application_name)"}, pid ${String(held.pid)}` : "(another session)";
56
+ throw new LockHeldError(who, held?.since ?? "(unknown)", where, held ? `SELECT pg_terminate_backend(${String(held.pid)})` : "end the session that holds it");
57
+ }
58
+ }
59
+ catch (e) {
60
+ await conn.end().catch(() => undefined);
61
+ throw e;
62
+ }
63
+ let released = false;
64
+ return {
65
+ kind: "advisory",
66
+ owner,
67
+ where,
68
+ async heartbeat() {
69
+ if (released)
70
+ throw new LockLostError(where);
71
+ let holds;
72
+ try {
73
+ const [row] = await conn.query("SELECT count(*) AS n FROM pg_catalog.pg_locks WHERE locktype = 'advisory' AND granted AND pid = pg_catalog.pg_backend_pid() AND classid = $1::oid AND objid = $2::oid AND objsubid = 2", [k1, k2]);
74
+ holds = Number(row?.n ?? 0) > 0;
75
+ }
76
+ catch {
77
+ holds = false;
78
+ }
79
+ if (!holds)
80
+ throw new LockLostError(where);
81
+ },
82
+ async release() {
83
+ if (released)
84
+ return;
85
+ released = true;
86
+ try {
87
+ await conn.query("SELECT pg_catalog.pg_advisory_unlock($1::int4, $2::int4)", [k1, k2]);
88
+ }
89
+ finally {
90
+ await conn.end().catch(() => undefined);
91
+ }
92
+ },
93
+ };
94
+ }
95
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/history/postgres/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Reading and appending the Postgres history table. Nothing here updates or
3
+ * deletes a row.
4
+ *
5
+ * The store works on one connection, the apply's work connection, so a row
6
+ * appended while that connection has a transaction open commits or rolls
7
+ * back with it: that is how a statement and its `succeeded` row commit
8
+ * together (`../../versioned/postgres/executor.ts`).
9
+ */
10
+ import { type HistoryRow, type HistoryState } from "../rows.js";
11
+ import { type HistoryStoreLike } from "../store.js";
12
+ /** What the history needs from a Postgres connection: chant's `PostgresClient`. */
13
+ export interface PgClient {
14
+ query<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
15
+ }
16
+ export interface PgHistoryLocation {
17
+ /** The schema the history table lives in. */
18
+ schema: string;
19
+ }
20
+ export declare class PostgresHistoryStore implements HistoryStoreLike {
21
+ readonly client: PgClient;
22
+ readonly location: PgHistoryLocation;
23
+ constructor(client: PgClient, location: PgHistoryLocation);
24
+ get table(): string;
25
+ /** Whether the history table is there. A history that was never made has no rows. */
26
+ exists(): Promise<boolean>;
27
+ /** Creates the schema and table when they are not there. */
28
+ ensure(): Promise<void>;
29
+ /** Every row, in seq order. Postgres reads are consistent, so the `sync` option the ClickHouse store takes changes nothing here. */
30
+ rows(): Promise<HistoryRow[]>;
31
+ state(): Promise<HistoryState>;
32
+ /** Appends rows, as written; `recorded_at` is the server's. On the store's connection, so inside its open transaction, if any. */
33
+ append(rows: readonly HistoryRow[]): Promise<void>;
34
+ }
35
+ /** The parameterized `INSERT` of `rows`. */
36
+ export declare function insertRows(schema: string, rows: readonly HistoryRow[]): {
37
+ sql: string;
38
+ params: unknown[];
39
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Reading and appending the Postgres history table. Nothing here updates or
3
+ * deletes a row.
4
+ *
5
+ * The store works on one connection, the apply's work connection, so a row
6
+ * appended while that connection has a transaction open commits or rolls
7
+ * back with it: that is how a statement and its `succeeded` row commit
8
+ * together (`../../versioned/postgres/executor.ts`).
9
+ */
10
+ import { deriveState } from "../rows.js";
11
+ import { fromRaw } from "../store.js";
12
+ import { HISTORY_TABLE } from "../ddl.js";
13
+ import { pgHistoryDdl, pgHistoryTable, PG_HISTORY_COLUMNS, PG_WRITTEN_COLUMNS } from "./ddl.js";
14
+ /** The history's rows read back, `recorded_at` in the ClickHouse table's form (`YYYY-MM-DD hh:mm:ss.ffffff`, UTC). */
15
+ const SELECT = PG_HISTORY_COLUMNS.map(([name]) => name === "recorded_at" ? `pg_catalog.to_char(recorded_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS.US') AS recorded_at` : name === "seq" ? "seq::text AS seq" : name).join(", ");
16
+ export class PostgresHistoryStore {
17
+ client;
18
+ location;
19
+ constructor(client, location) {
20
+ this.client = client;
21
+ this.location = location;
22
+ }
23
+ get table() {
24
+ return pgHistoryTable(this.location.schema);
25
+ }
26
+ /** Whether the history table is there. A history that was never made has no rows. */
27
+ async exists() {
28
+ const [row] = await this.client.query("SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 AND c.relkind = 'r') AS present", [this.location.schema, HISTORY_TABLE]);
29
+ return row?.present === true;
30
+ }
31
+ /** Creates the schema and table when they are not there. */
32
+ async ensure() {
33
+ if (await this.exists())
34
+ return;
35
+ for (const sql of pgHistoryDdl(this.location.schema))
36
+ await this.client.query(sql);
37
+ }
38
+ /** Every row, in seq order. Postgres reads are consistent, so the `sync` option the ClickHouse store takes changes nothing here. */
39
+ async rows() {
40
+ if (!(await this.exists()))
41
+ return [];
42
+ const raw = await this.client.query(`SELECT ${SELECT} FROM ${this.table} ORDER BY seq`);
43
+ return raw.map(fromRaw);
44
+ }
45
+ async state() {
46
+ return deriveState(await this.rows());
47
+ }
48
+ /** Appends rows, as written; `recorded_at` is the server's. On the store's connection, so inside its open transaction, if any. */
49
+ async append(rows) {
50
+ if (rows.length === 0)
51
+ return;
52
+ const { sql, params } = insertRows(this.location.schema, rows);
53
+ await this.client.query(sql, params);
54
+ }
55
+ }
56
+ /** The parameterized `INSERT` of `rows`. */
57
+ export function insertRows(schema, rows) {
58
+ const params = [];
59
+ const tuples = rows.map((r) => {
60
+ const values = PG_WRITTEN_COLUMNS.map((c) => {
61
+ params.push(c === "seq" ? String(r.seq) : r[c]);
62
+ return `$${String(params.length)}`;
63
+ });
64
+ return `(${values.join(", ")})`;
65
+ });
66
+ return { sql: `INSERT INTO ${pgHistoryTable(schema)} (${PG_WRITTEN_COLUMNS.join(", ")}) VALUES ${tuples.join(", ")}`, params };
67
+ }
68
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/history/postgres/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAsC,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAyB,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAYhG,sHAAsH;AACtH,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAC/C,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,+FAA+F,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CACtK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,OAAO,oBAAoB;IAEpB;IACA;IAFX,YACW,MAAgB,EAChB,QAA2B;QAD3B,WAAM,GAAN,MAAM,CAAU;QAChB,aAAQ,GAAR,QAAQ,CAAmB;IACnC,CAAC;IAEJ,IAAI,KAAK;QACP,OAAO,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,qFAAqF;IACrF,KAAK,CAAC,MAAM;QACV,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CACnC,qLAAqL,EACrL,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,CACtC,CAAC;QACF,OAAO,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,MAAM;QACV,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO;QAChC,KAAK,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrF,CAAC;IAED,oIAAoI;IACpI,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,MAAM,SAAS,IAAI,CAAC,KAAK,eAAe,CAAC,CAAC;QACxF,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,WAAW,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,kIAAkI;IAClI,KAAK,CAAC,MAAM,CAAC,IAA2B;QACtC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC/D,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;CACF;AAED,4CAA4C;AAC5C,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,IAA2B;IACpE,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAwC,CAAC,CAAC,CAAC,CAAC,CAAC;YACxF,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,GAAG,EAAE,eAAe,cAAc,CAAC,MAAM,CAAC,KAAK,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;AACjI,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * History rows and the state they add up to. Pure: rows in, state out.
3
+ *
4
+ * Rows are only ever appended. The state of a migration is its migration
5
+ * row with the highest `seq`; the state of one of its statements is that
6
+ * statement's row with the highest `seq`. A migration whose latest row is
7
+ * `succeeded` is applied. One whose latest row is `failed` (a statement
8
+ * failed) or `started` (the runner died) is part way: it resumes at the
9
+ * first statement whose latest row is not `succeeded`.
10
+ */
11
+ export type HistoryStatus = "started" | "succeeded" | "failed";
12
+ /** `step`: an Op step (#12), recorded as a statement is, its `note` the step's summary (receipts skipped and run). */
13
+ export type HistoryKind = "migration" | "statement" | "step";
14
+ /** The statement index of a migration's own rows. */
15
+ export declare const MIGRATION_ROW = -1;
16
+ /** One row of the history table, as written and as read back. */
17
+ export interface HistoryRow {
18
+ seq: number;
19
+ /** The apply run that wrote it. */
20
+ run_id: string;
21
+ kind: HistoryKind;
22
+ migration_id: string;
23
+ /** The migration's parent id, `""` for the first. */
24
+ parent: string;
25
+ /** The migration's checksum when the row was written. */
26
+ checksum: string;
27
+ /** {@link MIGRATION_ROW} on a migration row; else the statement's (or Op step's) index in the migration's `steps`. */
28
+ statement_index: number;
29
+ /** `sha256:<hex>` of the statement's SQL; `""` on a migration row. */
30
+ statement_sha: string;
31
+ status: HistoryStatus;
32
+ /** The server's message, on a `failed` row. */
33
+ error: string;
34
+ /** Who ran it: the CI actor or the user, and the host. */
35
+ applied_by: string;
36
+ /** The plan digest the run was approved under. */
37
+ plan_digest: string;
38
+ /** The silences (`-- yodel:allow <RULE> <reason>`) in force, as JSON text; `"[]"` when none. */
39
+ silences: string;
40
+ /** Free text, e.g. a repair's reason (#15). */
41
+ note: string;
42
+ /** The server's time, `YYYY-MM-DD hh:mm:ss.ffffff` UTC; absent on a row not yet written. */
43
+ recorded_at?: string;
44
+ }
45
+ /** The hash a statement row records of the SQL it sent. */
46
+ export declare const statementSha: (sql: string) => string;
47
+ export interface StatementRecord {
48
+ index: number;
49
+ status: HistoryStatus;
50
+ sha: string;
51
+ error: string;
52
+ seq: number;
53
+ }
54
+ /** What the history says about one migration. */
55
+ export interface MigrationRecord {
56
+ id: string;
57
+ parent: string | null;
58
+ /** The checksum on its latest migration row. */
59
+ checksum: string;
60
+ status: HistoryStatus;
61
+ error: string;
62
+ /** Who applied it; until it is applied, who wrote its latest migration row. A repair does not change it. */
63
+ appliedBy: string;
64
+ planDigest: string;
65
+ /** When it was applied (the row that first recorded it succeeded); until then, its latest migration row's time. A repair does not change it. */
66
+ recordedAt: string;
67
+ /** The repairs recorded since it was applied (`yodel repair`: a later succeeded migration row), oldest first. */
68
+ repairs: Repair[];
69
+ /** The seq of its latest migration row. */
70
+ seq: number;
71
+ /** The seq of the first row that recorded it `succeeded`; orders applied migrations by when they were applied. */
72
+ appliedSeq: number | undefined;
73
+ /** Each statement's latest row, by index. */
74
+ statements: Map<number, StatementRecord>;
75
+ }
76
+ /** A `yodel repair` of an applied migration: the new checksum, who recorded it, when and why. */
77
+ export interface Repair {
78
+ checksum: string;
79
+ by: string;
80
+ at: string;
81
+ reason: string;
82
+ seq: number;
83
+ }
84
+ export interface HistoryState {
85
+ /** Every migration the history names, in the order each was first recorded. */
86
+ migrations: MigrationRecord[];
87
+ byId: Map<string, MigrationRecord>;
88
+ /** The highest seq in the history, 0 when it is empty. */
89
+ maxSeq: number;
90
+ }
91
+ /** The state the rows add up to. Rows may come in any order. */
92
+ export declare function deriveState(rows: readonly HistoryRow[]): HistoryState;
93
+ /** Whether the history records the migration applied. */
94
+ export declare const isApplied: (m: MigrationRecord | undefined) => boolean;
95
+ /** Whether the statement at `index` has already run: its latest row is `succeeded`. Such a statement is never sent again. */
96
+ export declare const statementDone: (m: MigrationRecord | undefined, index: number) => boolean;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * History rows and the state they add up to. Pure: rows in, state out.
3
+ *
4
+ * Rows are only ever appended. The state of a migration is its migration
5
+ * row with the highest `seq`; the state of one of its statements is that
6
+ * statement's row with the highest `seq`. A migration whose latest row is
7
+ * `succeeded` is applied. One whose latest row is `failed` (a statement
8
+ * failed) or `started` (the runner died) is part way: it resumes at the
9
+ * first statement whose latest row is not `succeeded`.
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ /** The statement index of a migration's own rows. */
13
+ export const MIGRATION_ROW = -1;
14
+ /** The hash a statement row records of the SQL it sent. */
15
+ export const statementSha = (sql) => `sha256:${createHash("sha256").update(sql, "utf8").digest("hex")}`;
16
+ /** The state the rows add up to. Rows may come in any order. */
17
+ export function deriveState(rows) {
18
+ const sorted = [...rows].sort((a, b) => a.seq - b.seq);
19
+ const byId = new Map();
20
+ const order = [];
21
+ let maxSeq = 0;
22
+ for (const r of sorted) {
23
+ maxSeq = Math.max(maxSeq, r.seq);
24
+ let m = byId.get(r.migration_id);
25
+ if (!m) {
26
+ m = {
27
+ id: r.migration_id,
28
+ parent: r.parent === "" ? null : r.parent,
29
+ checksum: r.checksum,
30
+ status: "started",
31
+ error: "",
32
+ appliedBy: r.applied_by,
33
+ planDigest: r.plan_digest,
34
+ recordedAt: r.recorded_at ?? "",
35
+ seq: r.seq,
36
+ appliedSeq: undefined,
37
+ repairs: [],
38
+ statements: new Map(),
39
+ };
40
+ byId.set(r.migration_id, m);
41
+ order.push(m);
42
+ }
43
+ if (r.kind === "migration" && r.status === "succeeded" && m.status === "succeeded" && m.appliedSeq !== undefined) {
44
+ // Already applied: a repair. It records a new checksum; the migration was applied when and by whom it was.
45
+ m.parent = r.parent === "" ? null : r.parent;
46
+ m.checksum = r.checksum;
47
+ m.planDigest = r.plan_digest;
48
+ m.seq = r.seq;
49
+ m.repairs.push({ checksum: r.checksum, by: r.applied_by, at: r.recorded_at ?? "", reason: r.note, seq: r.seq });
50
+ }
51
+ else if (r.kind === "migration") {
52
+ m.parent = r.parent === "" ? null : r.parent;
53
+ m.checksum = r.checksum;
54
+ m.status = r.status;
55
+ m.error = r.error;
56
+ m.appliedBy = r.applied_by;
57
+ m.planDigest = r.plan_digest;
58
+ m.recordedAt = r.recorded_at ?? "";
59
+ m.seq = r.seq;
60
+ if (r.status === "succeeded" && m.appliedSeq === undefined)
61
+ m.appliedSeq = r.seq;
62
+ }
63
+ else {
64
+ m.statements.set(r.statement_index, { index: r.statement_index, status: r.status, sha: r.statement_sha, error: r.error, seq: r.seq });
65
+ }
66
+ }
67
+ return { migrations: order, byId, maxSeq };
68
+ }
69
+ /** Whether the history records the migration applied. */
70
+ export const isApplied = (m) => m?.status === "succeeded";
71
+ /** Whether the statement at `index` has already run: its latest row is `succeeded`. Such a statement is never sent again. */
72
+ export const statementDone = (m, index) => m?.statements.get(index)?.status === "succeeded";
73
+ //# sourceMappingURL=rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.js","sourceRoot":"","sources":["../../src/history/rows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Reading and appending the history table. Nothing here updates or deletes
3
+ * a row.
4
+ */
5
+ import type { ClickHousePort } from "./client.js";
6
+ import { type HistoryTopology } from "./topology.js";
7
+ import { type HistoryRow, type HistoryState } from "./rows.js";
8
+ export interface HistoryLocation {
9
+ database: string;
10
+ topology: HistoryTopology;
11
+ }
12
+ /** Who is applying: the CI actor or the user, at this host. */
13
+ export declare function appliedBy(env?: NodeJS.ProcessEnv): string;
14
+ type RawRow = Record<string, unknown>;
15
+ /** A row as a server returns it (strings or numbers), as a {@link HistoryRow}. */
16
+ export declare function fromRaw(r: RawRow): HistoryRow;
17
+ /** What the versioned verbs need of a history, on either dialect: this ClickHouse store, or `./postgres/store.ts`. */
18
+ export interface HistoryStoreLike {
19
+ exists(): Promise<boolean>;
20
+ ensure(): Promise<void>;
21
+ rows(options?: {
22
+ sync?: boolean;
23
+ }): Promise<HistoryRow[]>;
24
+ state(options?: {
25
+ sync?: boolean;
26
+ }): Promise<HistoryState>;
27
+ append(rows: readonly HistoryRow[]): Promise<void>;
28
+ }
29
+ export declare class HistoryStore implements HistoryStoreLike {
30
+ readonly port: ClickHousePort;
31
+ readonly location: HistoryLocation;
32
+ constructor(port: ClickHousePort, location: HistoryLocation);
33
+ get table(): string;
34
+ /** Whether the history table is there. A history that was never made has no rows. */
35
+ exists(): Promise<boolean>;
36
+ /** Creates the database and table when they are not there. */
37
+ ensure(): Promise<void>;
38
+ /**
39
+ * Every row. On a cluster or a Replicated database the replica first
40
+ * catches up with the others (`SYSTEM SYNC REPLICA`), so a run reads what
41
+ * a run on another host wrote; on Cloud the read is sequentially
42
+ * consistent. `sync: false` skips that, for a status read that need not
43
+ * wait.
44
+ */
45
+ rows(options?: {
46
+ sync?: boolean;
47
+ }): Promise<HistoryRow[]>;
48
+ state(options?: {
49
+ sync?: boolean;
50
+ }): Promise<HistoryState>;
51
+ /** Appends rows, as written; `recorded_at` is the server's. */
52
+ append(rows: readonly HistoryRow[]): Promise<void>;
53
+ }
54
+ /** The fields a run sets on every row it writes. */
55
+ export interface RunContext {
56
+ runId: string;
57
+ appliedBy: string;
58
+ planDigest: string;
59
+ }
60
+ export declare const newRunContext: (planDigest: string, env?: NodeJS.ProcessEnv) => RunContext;
61
+ /**
62
+ * Writes one run's rows, each with the next `seq`. Made under the lock from
63
+ * the history's highest seq, so seqs never collide across runs.
64
+ */
65
+ export declare class HistoryRecorder {
66
+ readonly store: HistoryStoreLike;
67
+ readonly run: RunContext;
68
+ private next;
69
+ constructor(store: HistoryStoreLike, run: RunContext, maxSeq: number);
70
+ record(row: Omit<HistoryRow, "seq" | "run_id" | "applied_by" | "plan_digest" | "recorded_at" | "error" | "note" | "silences"> & Partial<Pick<HistoryRow, "error" | "note" | "silences">>): Promise<HistoryRow>;
71
+ }
72
+ export {};