@softure-ai/blog 0.0.0-stage → 0.1.5

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 (418) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +602 -2
  3. package/dist/cli/bin.d.ts +3 -0
  4. package/dist/cli/bin.d.ts.map +1 -0
  5. package/dist/cli/bin.js +5 -0
  6. package/dist/cli/bin.js.map +1 -0
  7. package/dist/cli/command.d.ts +9 -0
  8. package/dist/cli/command.d.ts.map +1 -0
  9. package/dist/cli/command.js +40 -0
  10. package/dist/cli/command.js.map +1 -0
  11. package/dist/cli/index.d.ts +4 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +5 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/run.d.ts +62 -0
  16. package/dist/cli/run.d.ts.map +1 -0
  17. package/dist/cli/run.js +552 -0
  18. package/dist/cli/run.js.map +1 -0
  19. package/dist/cli/skill.d.ts +47 -0
  20. package/dist/cli/skill.d.ts.map +1 -0
  21. package/dist/cli/skill.js +221 -0
  22. package/dist/cli/skill.js.map +1 -0
  23. package/dist/content/article-file.d.ts +27 -0
  24. package/dist/content/article-file.d.ts.map +1 -0
  25. package/dist/content/article-file.js +179 -0
  26. package/dist/content/article-file.js.map +1 -0
  27. package/dist/contract.d.ts +77 -0
  28. package/dist/contract.d.ts.map +1 -0
  29. package/dist/contract.js +5 -0
  30. package/dist/contract.js.map +1 -0
  31. package/dist/db/articles.d.ts +30 -0
  32. package/dist/db/articles.d.ts.map +1 -0
  33. package/dist/db/articles.js +138 -0
  34. package/dist/db/articles.js.map +1 -0
  35. package/dist/db/publish-run.d.ts +50 -0
  36. package/dist/db/publish-run.d.ts.map +1 -0
  37. package/dist/db/publish-run.js +168 -0
  38. package/dist/db/publish-run.js.map +1 -0
  39. package/dist/db/schema.d.ts +403 -0
  40. package/dist/db/schema.d.ts.map +1 -0
  41. package/dist/db/schema.js +33 -0
  42. package/dist/db/schema.js.map +1 -0
  43. package/dist/discovery/dates.d.ts +9 -0
  44. package/dist/discovery/dates.d.ts.map +1 -0
  45. package/dist/discovery/dates.js +19 -0
  46. package/dist/discovery/dates.js.map +1 -0
  47. package/dist/discovery/index.d.ts +8 -0
  48. package/dist/discovery/index.d.ts.map +1 -0
  49. package/dist/discovery/index.js +11 -0
  50. package/dist/discovery/index.js.map +1 -0
  51. package/dist/discovery/indexnow.d.ts +11 -0
  52. package/dist/discovery/indexnow.d.ts.map +1 -0
  53. package/dist/discovery/indexnow.js +23 -0
  54. package/dist/discovery/indexnow.js.map +1 -0
  55. package/dist/discovery/refresh.d.ts +49 -0
  56. package/dist/discovery/refresh.d.ts.map +1 -0
  57. package/dist/discovery/refresh.js +55 -0
  58. package/dist/discovery/refresh.js.map +1 -0
  59. package/dist/discovery/related.d.ts +17 -0
  60. package/dist/discovery/related.d.ts.map +1 -0
  61. package/dist/discovery/related.js +28 -0
  62. package/dist/discovery/related.js.map +1 -0
  63. package/dist/discovery/rss.d.ts +30 -0
  64. package/dist/discovery/rss.d.ts.map +1 -0
  65. package/dist/discovery/rss.js +48 -0
  66. package/dist/discovery/rss.js.map +1 -0
  67. package/dist/discovery/sitemap.d.ts +25 -0
  68. package/dist/discovery/sitemap.d.ts.map +1 -0
  69. package/dist/discovery/sitemap.js +31 -0
  70. package/dist/discovery/sitemap.js.map +1 -0
  71. package/dist/discovery/submit.d.ts +44 -0
  72. package/dist/discovery/submit.d.ts.map +1 -0
  73. package/dist/discovery/submit.js +46 -0
  74. package/dist/discovery/submit.js.map +1 -0
  75. package/dist/index.d.ts +371 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +69 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/messages/en.d.ts +83 -0
  80. package/dist/messages/en.d.ts.map +1 -0
  81. package/dist/messages/en.js +83 -0
  82. package/dist/messages/en.js.map +1 -0
  83. package/dist/messages/index.d.ts +158 -0
  84. package/dist/messages/index.d.ts.map +1 -0
  85. package/dist/messages/index.js +5 -0
  86. package/dist/messages/index.js.map +1 -0
  87. package/dist/messages/pl.d.ts +78 -0
  88. package/dist/messages/pl.d.ts.map +1 -0
  89. package/dist/messages/pl.js +78 -0
  90. package/dist/messages/pl.js.map +1 -0
  91. package/dist/next/context.d.ts +6 -0
  92. package/dist/next/context.d.ts.map +1 -0
  93. package/dist/next/context.js +27 -0
  94. package/dist/next/context.js.map +1 -0
  95. package/dist/next/data.d.ts +11 -0
  96. package/dist/next/data.d.ts.map +1 -0
  97. package/dist/next/data.js +40 -0
  98. package/dist/next/data.js.map +1 -0
  99. package/dist/next/discovery.d.ts +6 -0
  100. package/dist/next/discovery.d.ts.map +1 -0
  101. package/dist/next/discovery.js +49 -0
  102. package/dist/next/discovery.js.map +1 -0
  103. package/dist/next/index.d.ts +9 -0
  104. package/dist/next/index.d.ts.map +1 -0
  105. package/dist/next/index.js +10 -0
  106. package/dist/next/index.js.map +1 -0
  107. package/dist/next/og-fonts.d.ts +6 -0
  108. package/dist/next/og-fonts.d.ts.map +1 -0
  109. package/dist/next/og-fonts.js +8 -0
  110. package/dist/next/og-fonts.js.map +1 -0
  111. package/dist/next/og-image.d.ts +36 -0
  112. package/dist/next/og-image.d.ts.map +1 -0
  113. package/dist/next/og-image.js +77 -0
  114. package/dist/next/og-image.js.map +1 -0
  115. package/dist/next/pages.d.ts +45 -0
  116. package/dist/next/pages.d.ts.map +1 -0
  117. package/dist/next/pages.js +189 -0
  118. package/dist/next/pages.js.map +1 -0
  119. package/dist/next/refresh.d.ts +9 -0
  120. package/dist/next/refresh.d.ts.map +1 -0
  121. package/dist/next/refresh.js +81 -0
  122. package/dist/next/refresh.js.map +1 -0
  123. package/dist/options.d.ts +320 -0
  124. package/dist/options.d.ts.map +1 -0
  125. package/dist/options.js +184 -0
  126. package/dist/options.js.map +1 -0
  127. package/dist/pages/body.d.ts +18 -0
  128. package/dist/pages/body.d.ts.map +1 -0
  129. package/dist/pages/body.js +21 -0
  130. package/dist/pages/body.js.map +1 -0
  131. package/dist/pages/dates.d.ts +21 -0
  132. package/dist/pages/dates.d.ts.map +1 -0
  133. package/dist/pages/dates.js +24 -0
  134. package/dist/pages/dates.js.map +1 -0
  135. package/dist/pages/index.d.ts +7 -0
  136. package/dist/pages/index.d.ts.map +1 -0
  137. package/dist/pages/index.js +9 -0
  138. package/dist/pages/index.js.map +1 -0
  139. package/dist/pages/json-ld.d.ts +33 -0
  140. package/dist/pages/json-ld.d.ts.map +1 -0
  141. package/dist/pages/json-ld.js +95 -0
  142. package/dist/pages/json-ld.js.map +1 -0
  143. package/dist/pages/listing.d.ts +46 -0
  144. package/dist/pages/listing.d.ts.map +1 -0
  145. package/dist/pages/listing.js +57 -0
  146. package/dist/pages/listing.js.map +1 -0
  147. package/dist/pages/paths.d.ts +37 -0
  148. package/dist/pages/paths.d.ts.map +1 -0
  149. package/dist/pages/paths.js +61 -0
  150. package/dist/pages/paths.js.map +1 -0
  151. package/dist/pages/redirects.d.ts +57 -0
  152. package/dist/pages/redirects.d.ts.map +1 -0
  153. package/dist/pages/redirects.js +78 -0
  154. package/dist/pages/redirects.js.map +1 -0
  155. package/dist/proxy/index.d.ts +15 -0
  156. package/dist/proxy/index.d.ts.map +1 -0
  157. package/dist/proxy/index.js +60 -0
  158. package/dist/proxy/index.js.map +1 -0
  159. package/dist/quality/blocks.d.ts +21 -0
  160. package/dist/quality/blocks.d.ts.map +1 -0
  161. package/dist/quality/blocks.js +113 -0
  162. package/dist/quality/blocks.js.map +1 -0
  163. package/dist/quality/catalog.d.ts +10 -0
  164. package/dist/quality/catalog.d.ts.map +1 -0
  165. package/dist/quality/catalog.js +71 -0
  166. package/dist/quality/catalog.js.map +1 -0
  167. package/dist/quality/check-article.d.ts +34 -0
  168. package/dist/quality/check-article.d.ts.map +1 -0
  169. package/dist/quality/check-article.js +74 -0
  170. package/dist/quality/check-article.js.map +1 -0
  171. package/dist/quality/check-files.d.ts +23 -0
  172. package/dist/quality/check-files.d.ts.map +1 -0
  173. package/dist/quality/check-files.js +44 -0
  174. package/dist/quality/check-files.js.map +1 -0
  175. package/dist/quality/external-links.d.ts +19 -0
  176. package/dist/quality/external-links.d.ts.map +1 -0
  177. package/dist/quality/external-links.js +30 -0
  178. package/dist/quality/external-links.js.map +1 -0
  179. package/dist/quality/finding.d.ts +17 -0
  180. package/dist/quality/finding.d.ts.map +1 -0
  181. package/dist/quality/finding.js +15 -0
  182. package/dist/quality/finding.js.map +1 -0
  183. package/dist/quality/gate.d.ts +5 -0
  184. package/dist/quality/gate.d.ts.map +1 -0
  185. package/dist/quality/gate.js +10 -0
  186. package/dist/quality/gate.js.map +1 -0
  187. package/dist/quality/index.d.ts +15 -0
  188. package/dist/quality/index.d.ts.map +1 -0
  189. package/dist/quality/index.js +16 -0
  190. package/dist/quality/index.js.map +1 -0
  191. package/dist/quality/link-targets.d.ts +44 -0
  192. package/dist/quality/link-targets.d.ts.map +1 -0
  193. package/dist/quality/link-targets.js +91 -0
  194. package/dist/quality/link-targets.js.map +1 -0
  195. package/dist/quality/options.d.ts +113 -0
  196. package/dist/quality/options.d.ts.map +1 -0
  197. package/dist/quality/options.js +93 -0
  198. package/dist/quality/options.js.map +1 -0
  199. package/dist/quality/plugin.d.ts +34 -0
  200. package/dist/quality/plugin.d.ts.map +1 -0
  201. package/dist/quality/plugin.js +7 -0
  202. package/dist/quality/plugin.js.map +1 -0
  203. package/dist/quality/rules/blocks.d.ts +5 -0
  204. package/dist/quality/rules/blocks.d.ts.map +1 -0
  205. package/dist/quality/rules/blocks.js +35 -0
  206. package/dist/quality/rules/blocks.js.map +1 -0
  207. package/dist/quality/rules/images.d.ts +4 -0
  208. package/dist/quality/rules/images.d.ts.map +1 -0
  209. package/dist/quality/rules/images.js +33 -0
  210. package/dist/quality/rules/images.js.map +1 -0
  211. package/dist/quality/rules/input.d.ts +11 -0
  212. package/dist/quality/rules/input.d.ts.map +1 -0
  213. package/dist/quality/rules/input.js +2 -0
  214. package/dist/quality/rules/input.js.map +1 -0
  215. package/dist/quality/rules/links.d.ts +17 -0
  216. package/dist/quality/rules/links.d.ts.map +1 -0
  217. package/dist/quality/rules/links.js +46 -0
  218. package/dist/quality/rules/links.js.map +1 -0
  219. package/dist/quality/rules/structure.d.ts +13 -0
  220. package/dist/quality/rules/structure.d.ts.map +1 -0
  221. package/dist/quality/rules/structure.js +139 -0
  222. package/dist/quality/rules/structure.js.map +1 -0
  223. package/dist/quality/rules/style.d.ts +11 -0
  224. package/dist/quality/rules/style.d.ts.map +1 -0
  225. package/dist/quality/rules/style.js +129 -0
  226. package/dist/quality/rules/style.js.map +1 -0
  227. package/dist/quality/rules/ymyl.d.ts +5 -0
  228. package/dist/quality/rules/ymyl.d.ts.map +1 -0
  229. package/dist/quality/rules/ymyl.js +51 -0
  230. package/dist/quality/rules/ymyl.js.map +1 -0
  231. package/dist/quality/rulesets/en/ruleset.d.ts +3 -0
  232. package/dist/quality/rulesets/en/ruleset.d.ts.map +1 -0
  233. package/dist/quality/rulesets/en/ruleset.js +106 -0
  234. package/dist/quality/rulesets/en/ruleset.js.map +1 -0
  235. package/dist/quality/rulesets/index.d.ts +7 -0
  236. package/dist/quality/rulesets/index.d.ts.map +1 -0
  237. package/dist/quality/rulesets/index.js +7 -0
  238. package/dist/quality/rulesets/index.js.map +1 -0
  239. package/dist/quality/rulesets/pl/ruleset.d.ts +3 -0
  240. package/dist/quality/rulesets/pl/ruleset.d.ts.map +1 -0
  241. package/dist/quality/rulesets/pl/ruleset.js +114 -0
  242. package/dist/quality/rulesets/pl/ruleset.js.map +1 -0
  243. package/dist/quality/rulesets/types.d.ts +28 -0
  244. package/dist/quality/rulesets/types.d.ts.map +1 -0
  245. package/dist/quality/rulesets/types.js +2 -0
  246. package/dist/quality/rulesets/types.js.map +1 -0
  247. package/dist/quality/settings.d.ts +26 -0
  248. package/dist/quality/settings.d.ts.map +1 -0
  249. package/dist/quality/settings.js +32 -0
  250. package/dist/quality/settings.js.map +1 -0
  251. package/dist/quality/text.d.ts +43 -0
  252. package/dist/quality/text.d.ts.map +1 -0
  253. package/dist/quality/text.js +85 -0
  254. package/dist/quality/text.js.map +1 -0
  255. package/dist/render/glossary.d.ts +27 -0
  256. package/dist/render/glossary.d.ts.map +1 -0
  257. package/dist/render/glossary.js +71 -0
  258. package/dist/render/glossary.js.map +1 -0
  259. package/dist/render/images.d.ts +42 -0
  260. package/dist/render/images.d.ts.map +1 -0
  261. package/dist/render/images.js +84 -0
  262. package/dist/render/images.js.map +1 -0
  263. package/dist/render/index.d.ts +6 -0
  264. package/dist/render/index.d.ts.map +1 -0
  265. package/dist/render/index.js +8 -0
  266. package/dist/render/index.js.map +1 -0
  267. package/dist/render/reading-time.d.ts +8 -0
  268. package/dist/render/reading-time.d.ts.map +1 -0
  269. package/dist/render/reading-time.js +17 -0
  270. package/dist/render/reading-time.js.map +1 -0
  271. package/dist/render/render-article.d.ts +97 -0
  272. package/dist/render/render-article.d.ts.map +1 -0
  273. package/dist/render/render-article.js +373 -0
  274. package/dist/render/render-article.js.map +1 -0
  275. package/dist/render/slugify-heading.d.ts +2 -0
  276. package/dist/render/slugify-heading.d.ts.map +1 -0
  277. package/dist/render/slugify-heading.js +21 -0
  278. package/dist/render/slugify-heading.js.map +1 -0
  279. package/dist/server/health.d.ts +3 -0
  280. package/dist/server/health.d.ts.map +1 -0
  281. package/dist/server/health.js +11 -0
  282. package/dist/server/health.js.map +1 -0
  283. package/dist/server/index.d.ts +10 -0
  284. package/dist/server/index.d.ts.map +1 -0
  285. package/dist/server/index.js +13 -0
  286. package/dist/server/index.js.map +1 -0
  287. package/dist/server/og-fonts.d.ts +24 -0
  288. package/dist/server/og-fonts.d.ts.map +1 -0
  289. package/dist/server/og-fonts.js +85 -0
  290. package/dist/server/og-fonts.js.map +1 -0
  291. package/dist/server/options.d.ts +17 -0
  292. package/dist/server/options.d.ts.map +1 -0
  293. package/dist/server/options.js +56 -0
  294. package/dist/server/options.js.map +1 -0
  295. package/dist/sitemap.d.ts +11 -0
  296. package/dist/sitemap.d.ts.map +1 -0
  297. package/dist/sitemap.js +35 -0
  298. package/dist/sitemap.js.map +1 -0
  299. package/dist/ui/blog-article.d.ts +49 -0
  300. package/dist/ui/blog-article.d.ts.map +1 -0
  301. package/dist/ui/blog-article.js +45 -0
  302. package/dist/ui/blog-article.js.map +1 -0
  303. package/dist/ui/blog-glossary.d.ts +27 -0
  304. package/dist/ui/blog-glossary.d.ts.map +1 -0
  305. package/dist/ui/blog-glossary.js +20 -0
  306. package/dist/ui/blog-glossary.js.map +1 -0
  307. package/dist/ui/blog-layout.d.ts +31 -0
  308. package/dist/ui/blog-layout.d.ts.map +1 -0
  309. package/dist/ui/blog-layout.js +25 -0
  310. package/dist/ui/blog-layout.js.map +1 -0
  311. package/dist/ui/blog-listing.d.ts +15 -0
  312. package/dist/ui/blog-listing.d.ts.map +1 -0
  313. package/dist/ui/blog-listing.js +29 -0
  314. package/dist/ui/blog-listing.js.map +1 -0
  315. package/dist/ui/blog-method.d.ts +5 -0
  316. package/dist/ui/blog-method.d.ts.map +1 -0
  317. package/dist/ui/blog-method.js +21 -0
  318. package/dist/ui/blog-method.js.map +1 -0
  319. package/dist/ui/index.d.ts +7 -0
  320. package/dist/ui/index.d.ts.map +1 -0
  321. package/dist/ui/index.js +8 -0
  322. package/dist/ui/index.js.map +1 -0
  323. package/dist/ui/page-context.d.ts +15 -0
  324. package/dist/ui/page-context.d.ts.map +1 -0
  325. package/dist/ui/page-context.js +2 -0
  326. package/dist/ui/page-context.js.map +1 -0
  327. package/migrations/0001_create_articles.sql +67 -0
  328. package/migrations/README.md +8 -0
  329. package/module.json +28 -0
  330. package/package.json +98 -4
  331. package/skill/SKILL.md +72 -0
  332. package/skill/references/reviewer.md +39 -0
  333. package/skill/references/rules.md +122 -0
  334. package/skill/references/structure.md +69 -0
  335. package/skill/references/template.md +86 -0
  336. package/src/cli/bin.ts +5 -0
  337. package/src/cli/command.ts +49 -0
  338. package/src/cli/index.ts +20 -0
  339. package/src/cli/run.ts +589 -0
  340. package/src/cli/skill.ts +242 -0
  341. package/src/content/article-file.ts +184 -0
  342. package/src/contract.ts +88 -0
  343. package/src/db/articles.ts +160 -0
  344. package/src/db/publish-run.ts +224 -0
  345. package/src/db/schema.ts +36 -0
  346. package/src/discovery/dates.ts +27 -0
  347. package/src/discovery/index.ts +18 -0
  348. package/src/discovery/indexnow.ts +25 -0
  349. package/src/discovery/refresh.ts +79 -0
  350. package/src/discovery/related.ts +35 -0
  351. package/src/discovery/rss.ts +77 -0
  352. package/src/discovery/sitemap.ts +59 -0
  353. package/src/discovery/submit.ts +69 -0
  354. package/src/index.ts +99 -0
  355. package/src/messages/en.ts +82 -0
  356. package/src/messages/index.ts +7 -0
  357. package/src/messages/pl.ts +77 -0
  358. package/src/next/context.ts +30 -0
  359. package/src/next/data.ts +55 -0
  360. package/src/next/discovery.ts +49 -0
  361. package/src/next/index.ts +25 -0
  362. package/src/next/next-modules.d.ts +15 -0
  363. package/src/next/og-fonts.ts +14 -0
  364. package/src/next/og-image.tsx +108 -0
  365. package/src/next/pages.tsx +251 -0
  366. package/src/next/refresh.ts +85 -0
  367. package/src/options.ts +224 -0
  368. package/src/pages/body.ts +38 -0
  369. package/src/pages/dates.ts +39 -0
  370. package/src/pages/index.ts +37 -0
  371. package/src/pages/json-ld.ts +120 -0
  372. package/src/pages/listing.ts +95 -0
  373. package/src/pages/paths.ts +84 -0
  374. package/src/pages/redirects.ts +120 -0
  375. package/src/proxy/index.ts +66 -0
  376. package/src/quality/blocks.ts +130 -0
  377. package/src/quality/catalog.ts +86 -0
  378. package/src/quality/check-article.ts +112 -0
  379. package/src/quality/check-files.ts +63 -0
  380. package/src/quality/external-links.ts +37 -0
  381. package/src/quality/finding.ts +29 -0
  382. package/src/quality/gate.ts +15 -0
  383. package/src/quality/index.ts +28 -0
  384. package/src/quality/link-targets.ts +107 -0
  385. package/src/quality/options.ts +105 -0
  386. package/src/quality/plugin.ts +43 -0
  387. package/src/quality/rules/blocks.ts +43 -0
  388. package/src/quality/rules/images.ts +34 -0
  389. package/src/quality/rules/input.ts +12 -0
  390. package/src/quality/rules/links.ts +56 -0
  391. package/src/quality/rules/structure.ts +137 -0
  392. package/src/quality/rules/style.ts +137 -0
  393. package/src/quality/rules/ymyl.ts +57 -0
  394. package/src/quality/rulesets/en/ruleset.ts +117 -0
  395. package/src/quality/rulesets/index.ts +9 -0
  396. package/src/quality/rulesets/pl/ruleset.ts +126 -0
  397. package/src/quality/rulesets/types.ts +31 -0
  398. package/src/quality/settings.ts +60 -0
  399. package/src/quality/text.ts +113 -0
  400. package/src/render/glossary.ts +103 -0
  401. package/src/render/images.ts +110 -0
  402. package/src/render/index.ts +29 -0
  403. package/src/render/reading-time.ts +18 -0
  404. package/src/render/render-article.ts +487 -0
  405. package/src/render/slugify-heading.ts +22 -0
  406. package/src/server/health.ts +12 -0
  407. package/src/server/index.ts +28 -0
  408. package/src/server/og-fonts.ts +102 -0
  409. package/src/server/options.ts +62 -0
  410. package/src/sitemap.ts +36 -0
  411. package/src/ui/blog-article.tsx +186 -0
  412. package/src/ui/blog-glossary.tsx +104 -0
  413. package/src/ui/blog-layout.tsx +89 -0
  414. package/src/ui/blog-listing.tsx +95 -0
  415. package/src/ui/blog-method.tsx +34 -0
  416. package/src/ui/index.ts +8 -0
  417. package/src/ui/page-context.ts +17 -0
  418. package/styles.css +498 -0
@@ -0,0 +1,138 @@
1
+ import { and, asc, desc, eq, ne } from "drizzle-orm";
2
+ import { articles, slugHistory } from "./schema.js";
3
+ /**
4
+ * Inserts or updates the article by its `id`. Idempotent: the same file again is `unchanged` and
5
+ * writes nothing, so a release can publish every file on every deploy.
6
+ *
7
+ * Dates:
8
+ * - `published_at` is the file's date when given; otherwise the row's; otherwise the moment the
9
+ * text first becomes `published`. A draft gets none.
10
+ * - `updated_at` moves only when the content hash of a text that already has `published_at` changes.
11
+ *
12
+ * Runs in a transaction that locks the row (`FOR UPDATE`), so two publishes of one article never
13
+ * overwrite each other silently; inside an open transaction it becomes a savepoint.
14
+ */
15
+ export async function publishArticle(ctx, input) {
16
+ return ctx.db.transaction(async (tx) => {
17
+ const [existing] = await tx.select().from(articles).where(eq(articles.id, input.id)).for("update");
18
+ // Current slugs first, then old ones (BF-12). A rename writes both tables in one commit, so a run
19
+ // that reads before it commits finds the article still at the slug, and one that reads after finds
20
+ // the slug in the history. Read the other way round, the rename could commit between the two reads
21
+ // and the slug would end up both current here and an old slug of the renamed article.
22
+ const [slugOwner] = await tx
23
+ .select({ id: articles.id })
24
+ .from(articles)
25
+ .where(and(eq(articles.slug, input.slug), ne(articles.id, input.id)));
26
+ if (slugOwner !== undefined)
27
+ return { ok: false, error: "blog.slug_taken", otherArticleId: slugOwner.id };
28
+ const [historyOwner] = await tx
29
+ .select({ articleId: slugHistory.articleId })
30
+ .from(slugHistory)
31
+ .where(and(eq(slugHistory.oldSlug, input.slug), ne(slugHistory.articleId, input.id)));
32
+ if (historyOwner !== undefined)
33
+ return { ok: false, error: "blog.slug_in_history", otherArticleId: historyOwner.articleId };
34
+ const now = ctx.clock.now();
35
+ const content = {
36
+ slug: input.slug,
37
+ kind: input.kind,
38
+ cluster: input.cluster,
39
+ isPillar: input.isPillar,
40
+ title: input.title,
41
+ description: input.description,
42
+ summary: input.summary,
43
+ bodyMarkdown: input.bodyMarkdown,
44
+ status: input.status,
45
+ currentAsOf: input.currentAsOf,
46
+ sources: input.sources,
47
+ faq: input.faq,
48
+ termForms: input.termForms,
49
+ fields: input.fields,
50
+ contentSha256: input.contentSha256,
51
+ };
52
+ if (existing === undefined) {
53
+ const publishedAt = input.publishedAt ?? (input.status === "published" ? now : null);
54
+ const [inserted] = await tx
55
+ .insert(articles)
56
+ .values({ id: input.id, ...content, publishedAt, updatedAt: null, createdAt: now })
57
+ .returning();
58
+ return { ok: true, action: "added", before: null, after: readState(requireRow(inserted, input.id)), previousSlug: null };
59
+ }
60
+ const before = readState(existing);
61
+ const publishedAt = input.publishedAt ?? existing.publishedAt ?? (input.status === "published" ? now : null);
62
+ const isContentChanged = existing.contentSha256 !== input.contentSha256;
63
+ const updatedAt = isContentChanged && existing.publishedAt !== null ? now : existing.updatedAt;
64
+ const isSlugChanged = existing.slug !== input.slug;
65
+ const isUnchanged = !isContentChanged &&
66
+ !isSlugChanged &&
67
+ existing.isPillar === input.isPillar &&
68
+ existing.status === input.status &&
69
+ isSameMoment(existing.publishedAt, publishedAt);
70
+ if (isUnchanged)
71
+ return { ok: true, action: "unchanged", before, after: before, previousSlug: null };
72
+ if (isSlugChanged) {
73
+ // Back to one of its own old slugs: that address is current again.
74
+ await tx.delete(slugHistory).where(eq(slugHistory.oldSlug, input.slug));
75
+ await tx.insert(slugHistory).values({ oldSlug: existing.slug, articleId: input.id, changedAt: now }).onConflictDoNothing();
76
+ }
77
+ const [updated] = await tx
78
+ .update(articles)
79
+ .set({ ...content, publishedAt, updatedAt })
80
+ .where(eq(articles.id, input.id))
81
+ .returning();
82
+ return {
83
+ ok: true,
84
+ action: "changed",
85
+ before,
86
+ after: readState(requireRow(updated, input.id)),
87
+ previousSlug: isSlugChanged ? existing.slug : null,
88
+ };
89
+ });
90
+ }
91
+ /** The article under its current slug, in any status: a page tells 200 from 410 by it. */
92
+ export async function findArticleBySlug(ctx, slug) {
93
+ const [row] = await ctx.db.select().from(articles).where(eq(articles.slug, slug));
94
+ return row ?? null;
95
+ }
96
+ /** The published article under its current slug, or `null` (a draft, a withdrawn text, no text). */
97
+ export async function getPublishedArticle(ctx, slug) {
98
+ const [row] = await ctx.db
99
+ .select()
100
+ .from(articles)
101
+ .where(and(eq(articles.slug, slug), eq(articles.status, "published")));
102
+ return row ?? null;
103
+ }
104
+ /** The current slug of the article that once had `oldSlug`: the target of a 301. */
105
+ export async function findSlugRedirect(ctx, oldSlug) {
106
+ const [row] = await ctx.db
107
+ .select({ slug: articles.slug })
108
+ .from(slugHistory)
109
+ .innerJoin(articles, eq(articles.id, slugHistory.articleId))
110
+ .where(eq(slugHistory.oldSlug, oldSlug));
111
+ return row?.slug ?? null;
112
+ }
113
+ /** Published texts, newest first; ties by slug. Without a filter, both kinds. */
114
+ export async function listArticles(ctx, filter = {}) {
115
+ const conditions = [eq(articles.status, "published")];
116
+ if (filter.kind !== undefined)
117
+ conditions.push(eq(articles.kind, filter.kind));
118
+ if (filter.cluster !== undefined)
119
+ conditions.push(eq(articles.cluster, filter.cluster));
120
+ return ctx.db
121
+ .select()
122
+ .from(articles)
123
+ .where(and(...conditions))
124
+ .orderBy(desc(articles.publishedAt), asc(articles.slug));
125
+ }
126
+ function readState(row) {
127
+ return { slug: row.slug, status: row.status, publishedAt: row.publishedAt, updatedAt: row.updatedAt, contentSha256: row.contentSha256 };
128
+ }
129
+ function isSameMoment(a, b) {
130
+ return (a?.getTime() ?? null) === (b?.getTime() ?? null);
131
+ }
132
+ /** `RETURNING` of a write that matched its row; nothing else is possible inside the row lock. */
133
+ function requireRow(row, id) {
134
+ if (row === undefined)
135
+ throw new Error(`publishArticle: the write of article ${id} returned no row`);
136
+ return row;
137
+ }
138
+ //# sourceMappingURL=articles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"articles.js","sourceRoot":"","sources":["../../src/db/articles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { BlogArticleInput, BlogArticleKind, BlogArticleStatus, BlogPublishAction } from "../contract.js";
2
+ import { type ParseArticleFileOptions } from "../content/article-file.js";
3
+ import { type BlogContext } from "./articles.js";
4
+ export interface ArticleFile {
5
+ /** The file name (or path); it must read `<slug>.md`. */
6
+ readonly name: string;
7
+ readonly text: string;
8
+ }
9
+ /**
10
+ * The quality gate (BL-6 fills it): the problems of a file that is about to go public. It runs only
11
+ * for files whose status is `published` and not under `withdraw`: a withdrawal must always pass, and a
12
+ * draft reaches no reader.
13
+ */
14
+ export type PublishGate = (file: ArticleFile, article: BlogArticleInput) => readonly string[];
15
+ export interface RunBlogPublishOptions extends ParseArticleFileOptions {
16
+ /** Write the changes; without it the run is a dry run. */
17
+ readonly commit?: boolean;
18
+ /** Publish the files as `withdrawn`, whatever their status (taking a text down at once). */
19
+ readonly withdraw?: boolean;
20
+ readonly gate?: PublishGate;
21
+ }
22
+ /** A problem that stops the run; `subject` is a file name, an article id or a cluster. */
23
+ export interface PublishProblem {
24
+ readonly subject: string;
25
+ readonly message: string;
26
+ }
27
+ /** What the run did (or would do) to one article; BL-5 derives the changed addresses from it. */
28
+ export interface PublishedChange {
29
+ readonly id: string;
30
+ readonly kind: BlogArticleKind;
31
+ readonly action: BlogPublishAction;
32
+ readonly statusBefore: BlogArticleStatus | null;
33
+ readonly statusAfter: BlogArticleStatus;
34
+ readonly slugBefore: string | null;
35
+ readonly slug: string;
36
+ /** The slug that entered the slug history in this run. */
37
+ readonly previousSlug: string | null;
38
+ }
39
+ export type BlogPublishRun = {
40
+ readonly status: "refused";
41
+ readonly problems: readonly PublishProblem[];
42
+ readonly warnings: readonly PublishProblem[];
43
+ } | {
44
+ readonly status: "done";
45
+ readonly committed: boolean;
46
+ readonly changes: readonly PublishedChange[];
47
+ readonly warnings: readonly PublishProblem[];
48
+ };
49
+ export declare function runBlogPublish(ctx: BlogContext, files: readonly ArticleFile[], options?: RunBlogPublishOptions): Promise<BlogPublishRun>;
50
+ //# sourceMappingURL=publish-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-run.d.ts","sourceRoot":"","sources":["../../src/db/publish-run.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9G,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAI5F,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAG/E,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,KAAK,SAAS,MAAM,EAAE,CAAC;AAE9F,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IACpE,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED,0FAA0F;AAC1F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE,GAC1H;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE,CAAC;AAgBzJ,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAmElJ"}
@@ -0,0 +1,168 @@
1
+ import { parseArticleFile } from "../content/article-file.js";
2
+ import { eq } from "drizzle-orm";
3
+ import { findTermFormConflicts, toGlossary } from "../render/glossary.js";
4
+ import { listArticles, publishArticle } from "./articles.js";
5
+ import { articles } from "./schema.js";
6
+ /** SQLSTATE of an exclusion constraint violation: the deferred one-pillar rule at commit. */
7
+ const EXCLUSION_VIOLATION = "23P01";
8
+ /** SQLSTATE of a unique violation; on `articles_slug_key` it is a lost slug race. */
9
+ const UNIQUE_VIOLATION = "23505";
10
+ const SLUG_CONSTRAINT = "articles_slug_key";
11
+ class DryRunRollback extends Error {
12
+ }
13
+ class PublishRefused extends Error {
14
+ problems;
15
+ constructor(problems) {
16
+ super(problems.map((problem) => problem.message).join("; "));
17
+ this.problems = problems;
18
+ }
19
+ }
20
+ export async function runBlogPublish(ctx, files, options = {}) {
21
+ const problems = [];
22
+ const warnings = [];
23
+ const inputs = [];
24
+ for (const file of files) {
25
+ const parsed = parseArticleFile(file.text, file.name, options);
26
+ if (!parsed.ok) {
27
+ problems.push(...parsed.errors.map((message) => ({ subject: file.name, message })));
28
+ continue;
29
+ }
30
+ const isGoingPublic = options.withdraw !== true && parsed.article.status === "published";
31
+ const gateProblems = isGoingPublic && options.gate !== undefined ? options.gate(file, parsed.article) : [];
32
+ if (gateProblems.length > 0) {
33
+ problems.push(...gateProblems.map((message) => ({ subject: file.name, message: `quality gate: ${message}` })));
34
+ continue;
35
+ }
36
+ if (options.withdraw === true && parsed.article.status !== "withdrawn") {
37
+ warnings.push({
38
+ subject: file.name,
39
+ message: `the file says status: ${parsed.article.status}; set it to withdrawn, or the next full publish brings the text back`,
40
+ });
41
+ }
42
+ inputs.push(options.withdraw === true ? { ...parsed.article, status: "withdrawn" } : parsed.article);
43
+ }
44
+ problems.push(...findDuplicateIds(inputs), ...findPillarProblems(inputs));
45
+ if (problems.length > 0)
46
+ return { status: "refused", problems, warnings };
47
+ const changes = [];
48
+ try {
49
+ await ctx.db.transaction(async (tx) => {
50
+ for (const input of inputs) {
51
+ const result = await publishArticleOrRefuse({ ...ctx, db: tx }, input);
52
+ if (!result.ok) {
53
+ const reason = result.error === "blog.slug_taken" ? "is the slug of" : "redirects to";
54
+ throw new PublishRefused([{ subject: input.id, message: `slug ${input.slug} ${reason} article ${result.otherArticleId} (${result.error})` }]);
55
+ }
56
+ changes.push({
57
+ id: input.id,
58
+ kind: input.kind,
59
+ action: result.action,
60
+ statusBefore: result.before?.status ?? null,
61
+ statusAfter: result.after.status,
62
+ slugBefore: result.before?.slug ?? null,
63
+ slug: result.after.slug,
64
+ previousSlug: result.previousSlug,
65
+ });
66
+ }
67
+ const glossary = await checkGlossaryForms({ ...ctx, db: tx }, inputs);
68
+ if (glossary.problems.length > 0)
69
+ throw new PublishRefused(glossary.problems);
70
+ warnings.push(...glossary.warnings);
71
+ if (options.commit !== true)
72
+ throw new DryRunRollback();
73
+ });
74
+ }
75
+ catch (error) {
76
+ if (error instanceof DryRunRollback)
77
+ return { status: "done", committed: false, changes, warnings };
78
+ if (error instanceof PublishRefused)
79
+ return { status: "refused", problems: error.problems, warnings };
80
+ if (findDriverError(error)?.code === EXCLUSION_VIOLATION) {
81
+ return {
82
+ status: "refused",
83
+ problems: [{ subject: "pillar", message: "a cluster would have two pillars: one is in the database and not in this run; mark only one text of a cluster pillar: true" }],
84
+ warnings,
85
+ };
86
+ }
87
+ throw error;
88
+ }
89
+ return { status: "done", committed: true, changes, warnings };
90
+ }
91
+ function findDuplicateIds(inputs) {
92
+ const seen = new Set();
93
+ const duplicates = new Set();
94
+ for (const { id } of inputs) {
95
+ if (seen.has(id))
96
+ duplicates.add(id);
97
+ seen.add(id);
98
+ }
99
+ return [...duplicates].map((id) => ({ subject: id, message: "two files have this id" }));
100
+ }
101
+ /**
102
+ * One pillar per cluster among the run's texts that are not withdrawn, checked before any write
103
+ * for a clear message. The database's deferred exclusion constraint also covers texts outside the
104
+ * run (a run of one file).
105
+ */
106
+ function findPillarProblems(inputs) {
107
+ const pillars = new Map();
108
+ for (const input of inputs) {
109
+ if (input.isPillar && input.cluster !== null && input.status !== "withdrawn") {
110
+ pillars.set(input.cluster, [...(pillars.get(input.cluster) ?? []), input.id]);
111
+ }
112
+ }
113
+ return [...pillars]
114
+ .filter(([, ids]) => ids.length > 1)
115
+ .map(([cluster, ids]) => ({ subject: cluster, message: `two pillars in one cluster: ${ids.join(", ")}` }));
116
+ }
117
+ /**
118
+ * One term per glossary form, over the published terms as this run leaves them (read inside its
119
+ * transaction, after its writes), so a run of one file sees the terms stored before it. A conflict
120
+ * with a term of this run refuses the run; one only between stored terms is not this run's doing and
121
+ * is a warning. The renderer links such a form to one of the terms either way.
122
+ */
123
+ async function checkGlossaryForms(ctx, inputs) {
124
+ const runTerms = new Set(inputs.filter((input) => input.kind === "term").map((input) => input.slug));
125
+ const problems = [];
126
+ const warnings = [];
127
+ for (const conflict of findTermFormConflicts(toGlossary(await listArticles(ctx, { kind: "term" })))) {
128
+ const problem = { subject: "glossary", message: `form "${conflict.form}" is claimed by terms ${conflict.slugs.join(", ")}; a form belongs to one term, so remove it from all but one` };
129
+ if (conflict.slugs.some((slug) => runTerms.has(slug)))
130
+ problems.push(problem);
131
+ else
132
+ warnings.push(problem);
133
+ }
134
+ return { problems, warnings };
135
+ }
136
+ /**
137
+ * `publishArticle`, with a lost slug race turned into a refusal. Another run can commit the same
138
+ * slug between this run's free-slug read and its write; the unique index then makes the write wait
139
+ * for that run and fail with 23505. The savepoint is rolled back, so the transaction still reads
140
+ * (at read committed, the winner's row is visible now) and names the article that took the slug.
141
+ */
142
+ async function publishArticleOrRefuse(ctx, input) {
143
+ try {
144
+ return await publishArticle(ctx, input);
145
+ }
146
+ catch (error) {
147
+ const driverError = findDriverError(error);
148
+ if (driverError?.code !== UNIQUE_VIOLATION || driverError.constraint !== SLUG_CONSTRAINT)
149
+ throw error;
150
+ const winner = await findSlugOwner(ctx.db, input.slug);
151
+ const owner = winner === undefined ? "another article published at the same time" : `article ${winner}`;
152
+ throw new PublishRefused([{ subject: input.id, message: `slug ${input.slug} is the slug of ${owner} (blog.slug_taken)` }]);
153
+ }
154
+ }
155
+ async function findSlugOwner(db, slug) {
156
+ const [row] = await db.select({ id: articles.id }).from(articles).where(eq(articles.slug, slug));
157
+ return row?.id;
158
+ }
159
+ /** The driver error (SQLSTATE and constraint) behind an error; drizzle wraps it as the `cause`. */
160
+ function findDriverError(error) {
161
+ for (let current = error, depth = 0; current instanceof Error && depth < 3; current = current.cause, depth += 1) {
162
+ const { code, constraint } = current;
163
+ if (typeof code === "string")
164
+ return { code, constraint: typeof constraint === "string" ? constraint : undefined };
165
+ }
166
+ return undefined;
167
+ }
168
+ //# sourceMappingURL=publish-run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-run.js","sourceRoot":"","sources":["../../src/db/publish-run.ts"],"names":[],"mappings":"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"}