@pithy-sh/core 0.1.2 → 0.1.4

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 (393) hide show
  1. package/dist/address/address.d.ts +57 -0
  2. package/dist/address/address.d.ts.map +1 -0
  3. package/dist/address/address.js +71 -0
  4. package/dist/audit/auditEvent.d.ts +89 -0
  5. package/dist/audit/auditEvent.d.ts.map +1 -0
  6. package/dist/audit/auditEvent.js +69 -0
  7. package/dist/audit/recorder.d.ts +18 -0
  8. package/dist/audit/recorder.d.ts.map +1 -0
  9. package/dist/audit/recorder.js +8 -0
  10. package/dist/capability/bindings.d.ts +62 -0
  11. package/dist/capability/bindings.d.ts.map +1 -0
  12. package/dist/capability/bindings.js +97 -0
  13. package/dist/capability/capability.d.ts +501 -0
  14. package/dist/capability/capability.d.ts.map +1 -0
  15. package/dist/capability/capability.js +16 -0
  16. package/dist/capability/client.d.ts +70 -0
  17. package/dist/capability/client.d.ts.map +1 -0
  18. package/dist/capability/client.js +86 -0
  19. package/dist/capability/compose.d.ts +25 -0
  20. package/dist/capability/compose.d.ts.map +1 -0
  21. package/dist/capability/compose.js +54 -0
  22. package/dist/capability/composition.d.ts +26 -0
  23. package/dist/capability/composition.d.ts.map +1 -0
  24. package/dist/capability/composition.js +84 -0
  25. package/dist/capability/config.d.ts +7 -0
  26. package/dist/capability/config.d.ts.map +1 -0
  27. package/dist/capability/config.js +14 -0
  28. package/dist/capability/devSecret.d.ts +32 -0
  29. package/dist/capability/devSecret.d.ts.map +1 -0
  30. package/dist/capability/devSecret.js +28 -0
  31. package/dist/capability/manifest.d.ts +298 -0
  32. package/dist/capability/manifest.d.ts.map +1 -0
  33. package/dist/capability/manifest.js +356 -0
  34. package/dist/capability/secretOrigin.d.ts +121 -0
  35. package/dist/capability/secretOrigin.d.ts.map +1 -0
  36. package/dist/capability/secretOrigin.js +134 -0
  37. package/dist/capability/settings.d.ts +119 -0
  38. package/dist/capability/settings.d.ts.map +1 -0
  39. package/dist/capability/settings.js +66 -0
  40. package/dist/capability/validateBindings.d.ts +4 -0
  41. package/dist/capability/validateBindings.d.ts.map +1 -0
  42. package/dist/capability/validateBindings.js +34 -0
  43. package/dist/capability/vanishingKey.d.ts +31 -0
  44. package/dist/capability/vanishingKey.d.ts.map +1 -0
  45. package/dist/capability/vanishingKey.js +79 -0
  46. package/dist/controlPlane/audit/actions.d.ts +70 -0
  47. package/dist/controlPlane/audit/actions.d.ts.map +1 -0
  48. package/dist/controlPlane/audit/actions.js +76 -0
  49. package/dist/controlPlane/capability.d.ts +47 -0
  50. package/dist/controlPlane/capability.d.ts.map +1 -0
  51. package/dist/controlPlane/capability.js +138 -0
  52. package/dist/controlPlane/config/config.d.ts +19 -0
  53. package/dist/controlPlane/config/config.d.ts.map +1 -0
  54. package/dist/controlPlane/config/config.js +85 -0
  55. package/dist/controlPlane/context.d.ts +27 -0
  56. package/dist/controlPlane/context.d.ts.map +1 -0
  57. package/dist/controlPlane/context.js +29 -0
  58. package/dist/controlPlane/data/connection.d.ts +79 -0
  59. package/dist/controlPlane/data/connection.d.ts.map +1 -0
  60. package/dist/controlPlane/data/connection.js +55 -0
  61. package/dist/controlPlane/data/keyLifecycle.d.ts +62 -0
  62. package/dist/controlPlane/data/keyLifecycle.d.ts.map +1 -0
  63. package/dist/controlPlane/data/keyLifecycle.js +126 -0
  64. package/dist/controlPlane/data/replay.d.ts +18 -0
  65. package/dist/controlPlane/data/replay.d.ts.map +1 -0
  66. package/dist/controlPlane/data/replay.js +20 -0
  67. package/dist/controlPlane/data/tables.d.ts +34 -0
  68. package/dist/controlPlane/data/tables.d.ts.map +1 -0
  69. package/dist/controlPlane/data/tables.js +37 -0
  70. package/dist/controlPlane/discovery/adminRoute.d.ts +313 -0
  71. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -0
  72. package/dist/controlPlane/discovery/adminRoute.js +136 -0
  73. package/dist/controlPlane/discovery/configuration.d.ts +108 -0
  74. package/dist/controlPlane/discovery/configuration.d.ts.map +1 -0
  75. package/dist/controlPlane/discovery/configuration.js +187 -0
  76. package/dist/controlPlane/discovery/drift.d.ts +64 -0
  77. package/dist/controlPlane/discovery/drift.d.ts.map +1 -0
  78. package/dist/controlPlane/discovery/drift.js +58 -0
  79. package/dist/controlPlane/discovery/health.d.ts +99 -0
  80. package/dist/controlPlane/discovery/health.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/health.js +151 -0
  82. package/dist/controlPlane/discovery/healthSummary.d.ts +236 -0
  83. package/dist/controlPlane/discovery/healthSummary.d.ts.map +1 -0
  84. package/dist/controlPlane/discovery/healthSummary.js +278 -0
  85. package/dist/controlPlane/error/errors.d.ts +67 -0
  86. package/dist/controlPlane/error/errors.d.ts.map +1 -0
  87. package/dist/controlPlane/error/errors.js +79 -0
  88. package/dist/controlPlane/http/cors.d.ts +112 -0
  89. package/dist/controlPlane/http/cors.d.ts.map +1 -0
  90. package/dist/controlPlane/http/cors.js +192 -0
  91. package/dist/controlPlane/http/guard.d.ts +77 -0
  92. package/dist/controlPlane/http/guard.d.ts.map +1 -0
  93. package/dist/controlPlane/http/guard.js +142 -0
  94. package/dist/controlPlane/http/handlers.d.ts +179 -0
  95. package/dist/controlPlane/http/handlers.d.ts.map +1 -0
  96. package/dist/controlPlane/http/handlers.js +245 -0
  97. package/dist/controlPlane/http/responses.d.ts +99 -0
  98. package/dist/controlPlane/http/responses.d.ts.map +1 -0
  99. package/dist/controlPlane/http/responses.js +63 -0
  100. package/dist/controlPlane/http/routes.d.ts +40 -0
  101. package/dist/controlPlane/http/routes.d.ts.map +1 -0
  102. package/dist/controlPlane/http/routes.js +83 -0
  103. package/dist/controlPlane/http/schemas.d.ts +39 -0
  104. package/dist/controlPlane/http/schemas.d.ts.map +1 -0
  105. package/dist/controlPlane/http/schemas.js +31 -0
  106. package/dist/controlPlane/http/verify.d.ts +84 -0
  107. package/dist/controlPlane/http/verify.d.ts.map +1 -0
  108. package/dist/controlPlane/http/verify.js +65 -0
  109. package/dist/controlPlane/migrations/0001_init.d.ts +51 -0
  110. package/dist/controlPlane/migrations/0001_init.d.ts.map +1 -0
  111. package/dist/controlPlane/migrations/0001_init.js +65 -0
  112. package/dist/controlPlane/replay/d1Guard.d.ts +29 -0
  113. package/dist/controlPlane/replay/d1Guard.d.ts.map +1 -0
  114. package/dist/controlPlane/replay/d1Guard.js +39 -0
  115. package/dist/controlPlane/replay/guard.d.ts +52 -0
  116. package/dist/controlPlane/replay/guard.d.ts.map +1 -0
  117. package/dist/controlPlane/replay/guard.js +0 -0
  118. package/dist/controlPlane/replay/kvGuard.d.ts +99 -0
  119. package/dist/controlPlane/replay/kvGuard.d.ts.map +1 -0
  120. package/dist/controlPlane/replay/kvGuard.js +101 -0
  121. package/dist/controlPlane/scope/scope.d.ts +68 -0
  122. package/dist/controlPlane/scope/scope.d.ts.map +1 -0
  123. package/dist/controlPlane/scope/scope.js +63 -0
  124. package/dist/controlPlane/token/base64url.d.ts +10 -0
  125. package/dist/controlPlane/token/base64url.d.ts.map +1 -0
  126. package/dist/controlPlane/token/base64url.js +48 -0
  127. package/dist/controlPlane/token/claims.d.ts +83 -0
  128. package/dist/controlPlane/token/claims.d.ts.map +1 -0
  129. package/dist/controlPlane/token/claims.js +86 -0
  130. package/dist/controlPlane/token/digest.d.ts +44 -0
  131. package/dist/controlPlane/token/digest.d.ts.map +1 -0
  132. package/dist/controlPlane/token/digest.js +56 -0
  133. package/dist/controlPlane/token/jws.d.ts +52 -0
  134. package/dist/controlPlane/token/jws.d.ts.map +1 -0
  135. package/dist/controlPlane/token/jws.js +65 -0
  136. package/dist/controlPlane/token/mint.d.ts +56 -0
  137. package/dist/controlPlane/token/mint.d.ts.map +1 -0
  138. package/dist/controlPlane/token/mint.js +55 -0
  139. package/dist/controlPlane/wire.d.ts +122 -0
  140. package/dist/controlPlane/wire.d.ts.map +1 -0
  141. package/dist/controlPlane/wire.js +123 -0
  142. package/dist/createBackend.d.ts +58 -0
  143. package/dist/createBackend.d.ts.map +1 -0
  144. package/dist/createBackend.js +170 -0
  145. package/dist/createEntrypoint.d.ts +53 -0
  146. package/dist/createEntrypoint.d.ts.map +1 -0
  147. package/dist/createEntrypoint.js +52 -0
  148. package/dist/data/boundParameters.d.ts +83 -0
  149. package/dist/data/boundParameters.d.ts.map +1 -0
  150. package/dist/data/boundParameters.js +162 -0
  151. package/dist/data/codecs.d.ts +34 -0
  152. package/dist/data/codecs.d.ts.map +1 -0
  153. package/dist/data/codecs.js +135 -0
  154. package/dist/data/cursor.d.ts +81 -0
  155. package/dist/data/cursor.d.ts.map +1 -0
  156. package/dist/data/cursor.js +104 -0
  157. package/dist/data/databases.d.ts +55 -0
  158. package/dist/data/databases.d.ts.map +1 -0
  159. package/dist/data/databases.js +32 -0
  160. package/dist/data/db.d.ts +42 -0
  161. package/dist/data/db.d.ts.map +1 -0
  162. package/dist/data/db.js +24 -0
  163. package/dist/data/withD1Retry.d.ts +91 -0
  164. package/dist/data/withD1Retry.d.ts.map +1 -0
  165. package/dist/data/withD1Retry.js +152 -0
  166. package/dist/entitlement/entitlement.d.ts +118 -0
  167. package/dist/entitlement/entitlement.d.ts.map +1 -0
  168. package/dist/entitlement/entitlement.js +59 -0
  169. package/dist/entitlement/gateScan.d.ts +26 -0
  170. package/dist/entitlement/gateScan.d.ts.map +1 -0
  171. package/dist/entitlement/gateScan.js +96 -0
  172. package/dist/entitlement/require.d.ts +12 -0
  173. package/dist/entitlement/require.d.ts.map +1 -0
  174. package/dist/entitlement/require.js +123 -0
  175. package/dist/env/ambient.d.ts +52 -0
  176. package/dist/env/ambient.d.ts.map +1 -0
  177. package/dist/env/ambient.js +35 -0
  178. package/dist/env/ci.d.ts +36 -0
  179. package/dist/env/ci.d.ts.map +1 -0
  180. package/dist/env/ci.js +40 -0
  181. package/dist/env/stem.d.ts +23 -0
  182. package/dist/env/stem.d.ts.map +1 -0
  183. package/dist/env/stem.js +29 -0
  184. package/dist/error/cause.d.ts +143 -0
  185. package/dist/error/cause.d.ts.map +1 -0
  186. package/dist/error/cause.js +192 -0
  187. package/dist/error/client.d.ts +35 -0
  188. package/dist/error/client.d.ts.map +1 -0
  189. package/dist/error/client.js +38 -0
  190. package/dist/error/extend.d.ts +101 -0
  191. package/dist/error/extend.d.ts.map +1 -0
  192. package/dist/error/extend.js +76 -0
  193. package/dist/error/http.d.ts +1516 -0
  194. package/dist/error/http.d.ts.map +1 -0
  195. package/dist/error/http.js +72 -0
  196. package/dist/error/payload.d.ts +3034 -0
  197. package/dist/error/payload.d.ts.map +1 -0
  198. package/dist/error/payload.js +1110 -0
  199. package/dist/error/pithyError.d.ts +154 -0
  200. package/dist/error/pithyError.d.ts.map +1 -0
  201. package/dist/error/pithyError.js +197 -0
  202. package/dist/error/terminal.d.ts +28 -0
  203. package/dist/error/terminal.d.ts.map +1 -0
  204. package/dist/error/terminal.js +33 -0
  205. package/dist/http/authContext.d.ts +21 -0
  206. package/dist/http/authContext.d.ts.map +1 -0
  207. package/dist/http/authContext.js +23 -0
  208. package/dist/http/routeContract.d.ts +39 -0
  209. package/dist/http/routeContract.d.ts.map +1 -0
  210. package/dist/http/routeContract.js +82 -0
  211. package/dist/http/sameOrigin.d.ts +47 -0
  212. package/dist/http/sameOrigin.d.ts.map +1 -0
  213. package/dist/http/sameOrigin.js +24 -0
  214. package/dist/http/signedWebhook.d.ts +225 -0
  215. package/dist/http/signedWebhook.d.ts.map +1 -0
  216. package/dist/http/signedWebhook.js +247 -0
  217. package/dist/http/validation.d.ts +37 -0
  218. package/dist/http/validation.d.ts.map +1 -0
  219. package/dist/http/validation.js +14 -0
  220. package/dist/http/verification.d.ts +10 -0
  221. package/dist/http/verification.d.ts.map +1 -0
  222. package/dist/http/verification.js +17 -0
  223. package/dist/i18n/acceptLanguage.d.ts +22 -0
  224. package/dist/i18n/acceptLanguage.d.ts.map +1 -0
  225. package/dist/i18n/acceptLanguage.js +55 -0
  226. package/dist/i18n/catalog.d.ts +39 -0
  227. package/dist/i18n/catalog.d.ts.map +1 -0
  228. package/dist/i18n/catalog.js +57 -0
  229. package/dist/i18n/locale.d.ts +74 -0
  230. package/dist/i18n/locale.d.ts.map +1 -0
  231. package/dist/i18n/locale.js +109 -0
  232. package/dist/i18n/localeMarker.d.ts +29 -0
  233. package/dist/i18n/localeMarker.d.ts.map +1 -0
  234. package/dist/i18n/localeMarker.js +112 -0
  235. package/dist/i18n/match.d.ts +26 -0
  236. package/dist/i18n/match.d.ts.map +1 -0
  237. package/dist/i18n/match.js +88 -0
  238. package/dist/i18n/registry.d.ts +25 -0
  239. package/dist/i18n/registry.d.ts.map +1 -0
  240. package/dist/i18n/registry.js +51 -0
  241. package/dist/i18n/translator.d.ts +107 -0
  242. package/dist/i18n/translator.d.ts.map +1 -0
  243. package/dist/i18n/translator.js +80 -0
  244. package/dist/index.d.ts +32 -0
  245. package/dist/index.d.ts.map +1 -0
  246. package/dist/index.js +17 -0
  247. package/dist/kv/kv.d.ts +166 -0
  248. package/dist/kv/kv.d.ts.map +1 -0
  249. package/dist/kv/kv.js +261 -0
  250. package/dist/kv/namespaces.d.ts +58 -0
  251. package/dist/kv/namespaces.d.ts.map +1 -0
  252. package/dist/kv/namespaces.js +40 -0
  253. package/dist/logger/local.d.ts +41 -0
  254. package/dist/logger/local.d.ts.map +1 -0
  255. package/dist/logger/local.js +45 -0
  256. package/dist/logger/logger.d.ts +56 -0
  257. package/dist/logger/logger.d.ts.map +1 -0
  258. package/dist/logger/logger.js +83 -0
  259. package/dist/logger/record.d.ts +919 -0
  260. package/dist/logger/record.d.ts.map +1 -0
  261. package/dist/logger/record.js +79 -0
  262. package/dist/logger/worker.d.ts +74 -0
  263. package/dist/logger/worker.d.ts.map +1 -0
  264. package/dist/logger/worker.js +54 -0
  265. package/dist/migrations/batch.d.ts +10 -0
  266. package/dist/migrations/batch.d.ts.map +1 -0
  267. package/dist/migrations/batch.js +193 -0
  268. package/dist/migrations/bookkeeping.d.ts +24 -0
  269. package/dist/migrations/bookkeeping.d.ts.map +1 -0
  270. package/dist/migrations/bookkeeping.js +57 -0
  271. package/dist/migrations/owner.d.ts +41 -0
  272. package/dist/migrations/owner.d.ts.map +1 -0
  273. package/dist/migrations/owner.js +105 -0
  274. package/dist/migrations/registry.d.ts +36 -0
  275. package/dist/migrations/registry.d.ts.map +1 -0
  276. package/dist/migrations/registry.js +70 -0
  277. package/dist/migrations/runner.d.ts +67 -0
  278. package/dist/migrations/runner.d.ts.map +1 -0
  279. package/dist/migrations/runner.js +218 -0
  280. package/dist/naming/domains.d.ts +108 -0
  281. package/dist/naming/domains.d.ts.map +1 -0
  282. package/dist/naming/domains.js +156 -0
  283. package/dist/naming/environment.d.ts +118 -0
  284. package/dist/naming/environment.d.ts.map +1 -0
  285. package/dist/naming/environment.js +190 -0
  286. package/dist/naming/feature.d.ts +68 -0
  287. package/dist/naming/feature.d.ts.map +1 -0
  288. package/dist/naming/feature.js +106 -0
  289. package/dist/naming/limits.d.ts +229 -0
  290. package/dist/naming/limits.d.ts.map +1 -0
  291. package/dist/naming/limits.js +187 -0
  292. package/dist/naming/provisionScope.d.ts +91 -0
  293. package/dist/naming/provisionScope.d.ts.map +1 -0
  294. package/dist/naming/provisionScope.js +65 -0
  295. package/dist/naming/resource.d.ts +169 -0
  296. package/dist/naming/resource.d.ts.map +1 -0
  297. package/dist/naming/resource.js +220 -0
  298. package/dist/naming/resourceNames.d.ts +83 -0
  299. package/dist/naming/resourceNames.d.ts.map +1 -0
  300. package/dist/naming/resourceNames.js +102 -0
  301. package/dist/naming/segment.d.ts +29 -0
  302. package/dist/naming/segment.d.ts.map +1 -0
  303. package/dist/naming/segment.js +31 -0
  304. package/dist/projection/asRead.d.ts +72 -0
  305. package/dist/projection/asRead.d.ts.map +1 -0
  306. package/dist/projection/asRead.js +169 -0
  307. package/dist/projection/published.d.ts +95 -0
  308. package/dist/projection/published.d.ts.map +1 -0
  309. package/dist/projection/published.js +114 -0
  310. package/dist/schema/describedness.d.ts +55 -0
  311. package/dist/schema/describedness.d.ts.map +1 -0
  312. package/dist/schema/describedness.js +180 -0
  313. package/dist/seed/compose.d.ts +41 -0
  314. package/dist/seed/compose.d.ts.map +1 -0
  315. package/dist/seed/compose.js +51 -0
  316. package/dist/seed/devLogin.d.ts +50 -0
  317. package/dist/seed/devLogin.d.ts.map +1 -0
  318. package/dist/seed/devLogin.js +52 -0
  319. package/dist/seed/exampleIdentities.d.ts +58 -0
  320. package/dist/seed/exampleIdentities.d.ts.map +1 -0
  321. package/dist/seed/exampleIdentities.js +26 -0
  322. package/dist/seed/metadata.d.ts +20 -0
  323. package/dist/seed/metadata.d.ts.map +1 -0
  324. package/dist/seed/metadata.js +27 -0
  325. package/dist/seed/seed.d.ts +264 -0
  326. package/dist/seed/seed.d.ts.map +1 -0
  327. package/dist/seed/seed.js +37 -0
  328. package/dist/seed/seededRows.d.ts +26 -0
  329. package/dist/seed/seededRows.d.ts.map +1 -0
  330. package/dist/seed/seededRows.js +20 -0
  331. package/dist/seed/writeD1.d.ts +43 -0
  332. package/dist/seed/writeD1.d.ts.map +1 -0
  333. package/dist/seed/writeD1.js +73 -0
  334. package/dist/seed/writeKv.d.ts +29 -0
  335. package/dist/seed/writeKv.d.ts.map +1 -0
  336. package/dist/seed/writeKv.js +63 -0
  337. package/dist/semver/semver.d.ts +62 -0
  338. package/dist/semver/semver.d.ts.map +1 -0
  339. package/dist/semver/semver.js +137 -0
  340. package/dist/text/comments.d.ts +62 -0
  341. package/dist/text/comments.d.ts.map +1 -0
  342. package/dist/text/comments.js +161 -0
  343. package/dist/version.generated.d.ts +5 -0
  344. package/dist/version.generated.d.ts.map +1 -0
  345. package/dist/version.generated.js +7 -0
  346. package/dist/worker/health.d.ts +39 -0
  347. package/dist/worker/health.d.ts.map +1 -0
  348. package/dist/worker/health.js +41 -0
  349. package/dist/worker/identity.d.ts +191 -0
  350. package/dist/worker/identity.d.ts.map +1 -0
  351. package/dist/worker/identity.js +162 -0
  352. package/dist/workflow/bindings.d.ts +41 -0
  353. package/dist/workflow/bindings.d.ts.map +1 -0
  354. package/dist/workflow/bindings.js +53 -0
  355. package/dist/workflow/dispatch.d.ts +98 -0
  356. package/dist/workflow/dispatch.d.ts.map +1 -0
  357. package/dist/workflow/dispatch.js +148 -0
  358. package/dist/workflow/dispatchRoute.d.ts +80 -0
  359. package/dist/workflow/dispatchRoute.d.ts.map +1 -0
  360. package/dist/workflow/dispatchRoute.js +134 -0
  361. package/dist/workflow/faults.d.ts +97 -0
  362. package/dist/workflow/faults.d.ts.map +1 -0
  363. package/dist/workflow/faults.js +111 -0
  364. package/dist/workflow/host.d.ts +176 -0
  365. package/dist/workflow/host.d.ts.map +1 -0
  366. package/dist/workflow/host.js +116 -0
  367. package/dist/workflow/hostEntry.d.ts +50 -0
  368. package/dist/workflow/hostEntry.d.ts.map +1 -0
  369. package/dist/workflow/hostEntry.js +24 -0
  370. package/dist/workflow/hostEnv.d.ts +143 -0
  371. package/dist/workflow/hostEnv.d.ts.map +1 -0
  372. package/dist/workflow/hostEnv.js +170 -0
  373. package/dist/workflow/loopback.d.ts +52 -0
  374. package/dist/workflow/loopback.d.ts.map +1 -0
  375. package/dist/workflow/loopback.js +76 -0
  376. package/dist/workflow/naming.d.ts +79 -0
  377. package/dist/workflow/naming.d.ts.map +1 -0
  378. package/dist/workflow/naming.js +138 -0
  379. package/dist/workflow/register.d.ts +19 -0
  380. package/dist/workflow/register.d.ts.map +1 -0
  381. package/dist/workflow/register.js +40 -0
  382. package/dist/workflow/schemas.d.ts +44 -0
  383. package/dist/workflow/schemas.d.ts.map +1 -0
  384. package/dist/workflow/schemas.js +43 -0
  385. package/dist/workflow/spec.d.ts +81 -0
  386. package/dist/workflow/spec.d.ts.map +1 -0
  387. package/dist/workflow/spec.js +0 -0
  388. package/dist/workflow/stepMessage.d.ts +122 -0
  389. package/dist/workflow/stepMessage.d.ts.map +1 -0
  390. package/dist/workflow/stepMessage.js +152 -0
  391. package/package.json +20 -9
  392. package/src/capability/manifest.ts +21 -0
  393. package/src/version.generated.ts +1 -1
@@ -0,0 +1,105 @@
1
+ import { ConflictError, ValidationError } from "../error/pithyError.js";
2
+ import { SQLiteDate } from "../data/codecs.js";
3
+ import { MIGRATION_OWNER_TABLE, migrationKysely } from "./bookkeeping.js";
4
+ import { z } from "zod";
5
+ import { sql } from "kysely";
6
+ //#region src/migrations/owner.ts
7
+ /**
8
+ * Which project owns a database — the guard behind `pithy migrate`.
9
+ *
10
+ * Table names stay `pithy_<capability>_<table>` in every project, on purpose: they are baked into
11
+ * migration keys and every meta-test, and the prefix exists to avoid clashing with the *adopter's*
12
+ * tables, not with another Pithy app's. The cost is that two unrelated projects pointed at one D1
13
+ * merge their schemas silently, each applying migrations the other has never heard of. So the name
14
+ * stays and the database records its owner instead: the first run stamps the project, every later run
15
+ * checks it, and a second project is refused by name rather than quietly joined.
16
+ *
17
+ * The stamp is bookkeeping, not a migration. It sits in its own table beside the ledger and the lock,
18
+ * created on demand, so no capability ships it, `--rollback` cannot step it back, and `pithy seed
19
+ * --redo`'s full reset — which rolls every migration down and back up — cannot wipe it. A database
20
+ * that once belonged to a project keeps saying so even with no schema left in it.
21
+ *
22
+ * Workers sharing one database is the supported path and is unaffected: they share by declaring the
23
+ * same binding name *within one project*, so every one of them claims the same owner.
24
+ */
25
+ /** The single bookkeeping row's fixed primary key — one owner per database, by construction. */
26
+ const OWNER_ROW_ID = 1;
27
+ /** The recorded owner of a database: who stamped it, and when. */
28
+ const MigrationOwner = z.object({
29
+ project: z.string().describe("The owning project — the root pithy.config.ts `name`, kebab-cased, as it was at first migrate."),
30
+ stampedAt: SQLiteDate.describe("When the project first claimed this database. Never rewritten by a later run.")
31
+ }).describe("One database's owning project, recorded in the migration bookkeeping the first time it is migrated.");
32
+ /** Whether the owner table has been created yet — a plain `sqlite_master` select, which D1 permits. */
33
+ async function ownerTableExists(db) {
34
+ const { rows } = await sql`select name from sqlite_master where type = 'table' and name = ${MIGRATION_OWNER_TABLE}`.execute(db);
35
+ return rows.length > 0;
36
+ }
37
+ /** The recorded owner, validated through the codec, or `undefined` when nothing has claimed it. */
38
+ async function currentOwner(db) {
39
+ if (!await ownerTableExists(db)) return void 0;
40
+ const row = await db.selectFrom("pithy_migrations_owner").select(["project", "stampedAt"]).where("id", "=", OWNER_ROW_ID).executeTakeFirst();
41
+ return row ? MigrationOwner.parse(row) : void 0;
42
+ }
43
+ /** The refusal: both project names in the problem line, both remedies in the action line. */
44
+ function foreignDatabase(owner, project, binding) {
45
+ return new ConflictError({
46
+ message: `${binding ?? "This database"} belongs to project "${owner}", not "${project}".`,
47
+ action: `Bind ${project} to its own database, or set the project name back to ${owner}. Run pithy migrate again.`,
48
+ detail: `Recorded owner "${owner}" in ${MIGRATION_OWNER_TABLE}.`
49
+ });
50
+ }
51
+ /**
52
+ * The project recorded as this database's owner, or `undefined` when none is — read-only, creating
53
+ * nothing. `pithy doctor` and any other reporter reads the stamp through this; only
54
+ * {@link claimMigrationOwnership} writes it.
55
+ */
56
+ async function readMigrationOwner(database) {
57
+ return currentOwner(migrationKysely(database));
58
+ }
59
+ /**
60
+ * Claim a database for a project, or refuse it to a foreign one. Idempotent and safe to re-run: an
61
+ * unstamped database is adopted (nothing has shipped, so a first run has no ambiguity to preserve),
62
+ * the owning project re-claims as a no-op, and any other project throws a `core/conflict` naming both
63
+ * projects. The insert is `on conflict do nothing` against a fixed primary key and is followed by a
64
+ * re-read, so two runs racing the very first claim settle on one owner rather than two rows.
65
+ */
66
+ async function claimMigrationOwnership(database, options) {
67
+ const project = options.project.trim();
68
+ if (!project) throw new ValidationError({
69
+ message: "A migration run needs a project name.",
70
+ action: "Set `name` in pithy.config.ts. It is what stamps the database as this project's."
71
+ });
72
+ const db = migrationKysely(database);
73
+ const existing = await currentOwner(db);
74
+ if (existing) {
75
+ if (existing.project !== project) throw foreignDatabase(existing.project, project, options.binding);
76
+ return {
77
+ project,
78
+ outcome: "confirmed",
79
+ stampedAt: existing.stampedAt
80
+ };
81
+ }
82
+ await db.schema.createTable(MIGRATION_OWNER_TABLE).ifNotExists().addColumn("id", "integer", (column) => column.primaryKey()).addColumn("project", "text", (column) => column.notNull()).addColumn("stampedAt", "integer", (column) => column.notNull()).execute();
83
+ const record = MigrationOwner.encode({
84
+ project,
85
+ stampedAt: /* @__PURE__ */ new Date()
86
+ });
87
+ await db.insertInto("pithy_migrations_owner").values({
88
+ id: OWNER_ROW_ID,
89
+ ...record
90
+ }).onConflict((conflict) => conflict.doNothing()).execute();
91
+ const settled = await currentOwner(db);
92
+ if (!settled) throw new ConflictError({
93
+ message: `${options.binding ?? "This database"} could not be stamped as "${project}".`,
94
+ action: "Check the database is writable. Run pithy migrate again.",
95
+ detail: `No row in ${MIGRATION_OWNER_TABLE} after the claim insert.`
96
+ });
97
+ if (settled.project !== project) throw foreignDatabase(settled.project, project, options.binding);
98
+ return {
99
+ project,
100
+ outcome: "adopted",
101
+ stampedAt: settled.stampedAt
102
+ };
103
+ }
104
+ //#endregion
105
+ export { MigrationOwner, claimMigrationOwnership, readMigrationOwner };
@@ -0,0 +1,36 @@
1
+ import type { Migration, MigrationProvider } from "kysely/migration";
2
+ /** Maximum per-capability `order`, derived from the prefix width so the two never drift. */
3
+ export declare const MAX_MIGRATION_ORDER: number;
4
+ /** Capability namespace: lowercase, starts with a letter, no separators (keeps keys injective). */
5
+ export declare const NAMESPACE_PATTERN: RegExp;
6
+ export interface NamespacedMigrations {
7
+ /**
8
+ * Target database name — the same name used in a capability's `databases:` map (e.g. "app",
9
+ * "analytics"). D1 is multi-database; each database migrates independently, so every set names
10
+ * the database it belongs to. A capability with tables in two databases contributes one set per.
11
+ */
12
+ database: string;
13
+ /** Capability namespace, e.g. "core", "auth", "app". Must match `^[a-z][a-z0-9]*$`. */
14
+ namespace: string;
15
+ /** Sort order within its database (core low, app high). Unique per database; 0..MAX_MIGRATION_ORDER. */
16
+ order: number;
17
+ /** Stable, per-namespace keys, each `^\d{4}_[a-z0-9_]+$` (e.g. "0001_init"). */
18
+ migrations: Record<string, Migration>;
19
+ }
20
+ /**
21
+ * Merge namespaced migration sets into one ordered `MigrationProvider` **per database** — matching
22
+ * multi-database D1, where each `c.var.db.<database>` is its own binding and schema. The result is
23
+ * keyed by database name (the future `pithy migrate` maps each to its binding and runs its provider);
24
+ * an empty input yields `{}`.
25
+ *
26
+ * Within each database the keys are `NNNN_<namespace>_<localKey>` (NNNN = zero-padded `order`).
27
+ * Kysely orders migrations by name (`localeCompare`); the enforced namespace/key formats keep that
28
+ * sort equal to a plain lexicographic one and dependency-correct (core before app), and make the key
29
+ * scheme injective. Keys never change across releases — new migrations append within a namespace — so
30
+ * Kysely's recorded names stay valid and an upgrade ships migrations without renumbering.
31
+ *
32
+ * `order` and `namespace` uniqueness scope **per database**: the same value may recur across
33
+ * different databases; a duplicate within one database throws.
34
+ */
35
+ export declare function createMigrationRegistry(sets: NamespacedMigrations[]): Record<string, MigrationProvider>;
36
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/migrations/registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAMrE,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,QAAyB,CAAC;AAE1D,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,QAAqB,CAAC;AAKpD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACvC;AA6DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAavG"}
@@ -0,0 +1,70 @@
1
+ import { InternalError } from "../error/pithyError.js";
2
+ //#region src/migrations/registry.ts
3
+ /** Width of the zero-padded `order` prefix in a composed key — the stable anchor. */
4
+ const ORDER_DIGITS = 4;
5
+ /** Maximum per-capability `order`, derived from the prefix width so the two never drift. */
6
+ const MAX_MIGRATION_ORDER = 10 ** ORDER_DIGITS - 1;
7
+ /** Capability namespace: lowercase, starts with a letter, no separators (keeps keys injective). */
8
+ const NAMESPACE_PATTERN = /^[a-z][a-z0-9]*$/;
9
+ /** Local migration key: a zero-padded sequence + lowercase snake, e.g. "0001_init". */
10
+ const LOCAL_KEY_PATTERN = /^\d{4}_[a-z0-9_]+$/;
11
+ function assertValidNamespace(namespace) {
12
+ if (!NAMESPACE_PATTERN.test(namespace)) throw new InternalError({ message: `migration namespace "${namespace}" must match ${NAMESPACE_PATTERN}` });
13
+ }
14
+ function assertValidOrder(order, namespace) {
15
+ if (!Number.isInteger(order) || order < 0 || order > 9999) throw new InternalError({ message: `migration order ${order} (namespace "${namespace}") must be an integer in 0..${MAX_MIGRATION_ORDER}` });
16
+ }
17
+ function assertValidLocalKey(localKey, namespace) {
18
+ if (!LOCAL_KEY_PATTERN.test(localKey)) throw new InternalError({ message: `migration key "${localKey}" (namespace "${namespace}") must match ${LOCAL_KEY_PATTERN}` });
19
+ }
20
+ /** Merge one database's sets into a provider, enforcing the per-database invariants. */
21
+ function composeDatabase(database, sets) {
22
+ const seenOrders = /* @__PURE__ */ new Set();
23
+ const seenNamespaces = /* @__PURE__ */ new Set();
24
+ for (const set of sets) {
25
+ assertValidNamespace(set.namespace);
26
+ assertValidOrder(set.order, set.namespace);
27
+ for (const localKey of Object.keys(set.migrations)) assertValidLocalKey(localKey, set.namespace);
28
+ if (seenOrders.has(set.order)) throw new InternalError({ message: `duplicate migration order ${set.order} in database "${database}" (namespace "${set.namespace}")` });
29
+ if (seenNamespaces.has(set.namespace)) throw new InternalError({ message: `duplicate namespace "${set.namespace}" in database "${database}"` });
30
+ seenOrders.add(set.order);
31
+ seenNamespaces.add(set.namespace);
32
+ }
33
+ const composed = {};
34
+ for (const set of [...sets].sort((a, b) => a.order - b.order)) {
35
+ const prefix = String(set.order).padStart(ORDER_DIGITS, "0");
36
+ for (const localKey of Object.keys(set.migrations).sort()) {
37
+ const migration = set.migrations[localKey];
38
+ if (migration) composed[`${prefix}_${set.namespace}_${localKey}`] = migration;
39
+ }
40
+ }
41
+ return { getMigrations: async () => composed };
42
+ }
43
+ /**
44
+ * Merge namespaced migration sets into one ordered `MigrationProvider` **per database** — matching
45
+ * multi-database D1, where each `c.var.db.<database>` is its own binding and schema. The result is
46
+ * keyed by database name (the future `pithy migrate` maps each to its binding and runs its provider);
47
+ * an empty input yields `{}`.
48
+ *
49
+ * Within each database the keys are `NNNN_<namespace>_<localKey>` (NNNN = zero-padded `order`).
50
+ * Kysely orders migrations by name (`localeCompare`); the enforced namespace/key formats keep that
51
+ * sort equal to a plain lexicographic one and dependency-correct (core before app), and make the key
52
+ * scheme injective. Keys never change across releases — new migrations append within a namespace — so
53
+ * Kysely's recorded names stay valid and an upgrade ships migrations without renumbering.
54
+ *
55
+ * `order` and `namespace` uniqueness scope **per database**: the same value may recur across
56
+ * different databases; a duplicate within one database throws.
57
+ */
58
+ function createMigrationRegistry(sets) {
59
+ const byDatabase = /* @__PURE__ */ new Map();
60
+ for (const set of sets) {
61
+ const group = byDatabase.get(set.database);
62
+ if (group) group.push(set);
63
+ else byDatabase.set(set.database, [set]);
64
+ }
65
+ const registry = {};
66
+ for (const [database, dbSets] of byDatabase) registry[database] = composeDatabase(database, dbSets);
67
+ return registry;
68
+ }
69
+ //#endregion
70
+ export { MAX_MIGRATION_ORDER, NAMESPACE_PATTERN, createMigrationRegistry };
@@ -0,0 +1,67 @@
1
+ import type { D1Database } from "@cloudflare/workers-types";
2
+ import { type MigrationProvider, type MigrationResult } from "kysely/migration";
3
+ /**
4
+ * Which database a run is reporting on — what a failure names.
5
+ *
6
+ * Optional at every entry point, and defaulted the same way `claimMigrationOwnership`'s refusal defaults
7
+ * its own binding — a runner handed nothing still has a sentence. The CLI always passes one, from the
8
+ * group it is running, because "which database" is the first question a failed migration raises and the
9
+ * runner is the only place that can answer it in the same breath as the error (#282).
10
+ */
11
+ export interface MigrationTarget {
12
+ /** The D1 binding, as `wrangler.jsonc` declares it — the name an adopter recognizes. */
13
+ binding: string;
14
+ /** The database name: a capability's `databases` key. Throw-site context, not the adopter's handle. */
15
+ database: string;
16
+ }
17
+ /** Run every pending migration to latest. An empty provider resolves to `[]`. */
18
+ export declare function runMigrations(database: D1Database, provider: MigrationProvider, target?: MigrationTarget): Promise<MigrationResult[]>;
19
+ /** One database's ledger beside the declaration it is supposed to match — see {@link readMigrationLedger}. */
20
+ export interface MigrationLedger {
21
+ /** Declared and not yet applied, in the order they would run. */
22
+ pending: string[];
23
+ /**
24
+ * Applied and no longer declared, sorted. Kysely refuses the whole chain on any one of these, so
25
+ * this is not drift a later run works around — it is the reason nothing can run at all.
26
+ */
27
+ undeclared: string[];
28
+ }
29
+ /**
30
+ * What this database has applied, against what this project declares — **both directions, one read**.
31
+ *
32
+ * `pithy doctor` used to ask only how many declared migrations had not run, because that is the
33
+ * question Kysely's own `getMigrations` answers: it maps over the *provider's* migrations and looks
34
+ * each one up in the ledger, so a row the provider does not carry is not in the result at all. A
35
+ * subtraction cannot see an extra. A database holding a migration the project has since deleted
36
+ * therefore reported `none pending ✓` while the migrator refused to run against it at all — Kysely
37
+ * reads an unrecognized applied migration as a corrupted chain and applies nothing (#282).
38
+ *
39
+ * Asking both halves in one function is the point. Two functions — one counting pending, one hunting
40
+ * undeclared — is how the first half shipped alone, and the caller reaching for the count is exactly
41
+ * the caller who needs the other answer.
42
+ *
43
+ * Read-only, applying nothing: the seam behind `pithy doctor`'s migrations line, `pithy deploy`'s
44
+ * warn-only "schema is behind" check, and the refusal `pithy migrate` raises before it writes.
45
+ */
46
+ export declare function readMigrationLedger(database: D1Database, provider: MigrationProvider): Promise<MigrationLedger>;
47
+ /** Step the latest applied migration back — one step, the `pithy migrate --rollback` seam. */
48
+ export declare function rollbackMigration(database: D1Database, provider: MigrationProvider, target?: MigrationTarget): Promise<MigrationResult[]>;
49
+ /**
50
+ * Fully reset one database's schema: every applied migration's `down` runs, in one pass, in reverse
51
+ * chronological order — Kysely's `NO_MIGRATIONS` target, not just the latest — then every migration's
52
+ * `up` reapplies from empty. The seam behind `pithy seed --redo`'s destructive rebuild: because the
53
+ * schema comes back empty, the ordinary non-destructive seed writes (`INSERT OR IGNORE`, KV
54
+ * skip-if-exists) simply work afterward — there is no per-row identity problem to solve. An empty
55
+ * ledger rolls back nothing; an empty provider reapplies nothing.
56
+ */
57
+ export declare function resetMigrations(database: D1Database, provider: MigrationProvider, target?: MigrationTarget): Promise<MigrationResult[]>;
58
+ /**
59
+ * Surgically drop **one capability's** migrations: run each of the provider's `down` functions in
60
+ * reverse order and delete only those ledger rows, leaving every other capability's tables and
61
+ * bookkeeping untouched. The seam behind `pithy remove --drop`. Kysely's stepwise `Migrator` refuses a
62
+ * provider that doesn't span the whole ledger (it reads a foreign row as corrupt state), so a
63
+ * per-capability drop can't go through it — this reverses the capability's own migrations directly.
64
+ * Only migrations recorded in the ledger are reversed; an absent ledger drops nothing.
65
+ */
66
+ export declare function dropMigrations(database: D1Database, provider: MigrationProvider, target?: MigrationTarget): Promise<MigrationResult[]>;
67
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/migrations/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAA2B,MAAM,kBAAkB,CAAC;AA0DzG;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB,uGAAuG;IACvG,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,iFAAiF;AACjF,wBAAsB,aAAa,CACjC,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC,CAG5B;AAED,8GAA8G;AAC9G,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAOrH;AAED,8FAA8F;AAC9F,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC,CAG5B;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC,CAO5B;AAgBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC,CA+B5B"}
@@ -0,0 +1,218 @@
1
+ import { InternalError } from "../error/pithyError.js";
2
+ import { causeMessage } from "../error/cause.js";
3
+ import { MIGRATION_LOCK_TABLE, MIGRATION_TABLE, migrationKysely } from "./bookkeeping.js";
4
+ import { batchedProvider } from "./batch.js";
5
+ import { sql } from "kysely";
6
+ import { Migrator, NO_MIGRATIONS } from "kysely/migration";
7
+ //#region src/migrations/runner.ts
8
+ /**
9
+ * The per-database migration runner. The registry yields one `MigrationProvider` per database
10
+ * (`createMigrationRegistry`); the caller pairs each provider with that database's D1 binding and
11
+ * runs them independently — there is no global run across databases. `pithy migrate` is a thin
12
+ * wrapper over these two functions.
13
+ *
14
+ * Both take the raw binding, not a Kysely instance: the dialect, the `CamelCasePlugin`, and the
15
+ * renamed bookkeeping tables all live in `./bookkeeping`, and the runner builds its Kysely from
16
+ * there (`owner.ts` builds the same one to stamp the owning project). Migration `up`/`down`
17
+ * functions receive that instance — write camelCase, store snake_case, like every Pithy database.
18
+ *
19
+ * One runner at a time per database. Kysely's SQLite adapter reports no transactional DDL and its
20
+ * migration lock is a no-op, so concurrent runs can interleave. That fits the deployment model —
21
+ * migrations run from `pithy migrate` (CLI/CI), not inside request handlers — but it is an
22
+ * assumption, not a guard. On failure the thrown `InternalError` names the failed key, the database
23
+ * it was running against, and **what the runtime actually said**, all in `message`; `detail` keeps
24
+ * the throw-site half — the database name behind the binding, and the migrations applied before the
25
+ * failure, since those stay applied.
26
+ *
27
+ * **Each migration body is one `d1.batch()` — see `./batch`, which is where the failure semantics
28
+ * are argued.** The short version: a migration is now all-or-nothing where it used to be able to
29
+ * half-apply, and nothing across a migration boundary changed, because the ledger names migrations
30
+ * and a partial chain has to stay representable in it.
31
+ */
32
+ /**
33
+ * **`allowUnorderedMigrations` is on, and it is not a loosening.** A composed key leads with its
34
+ * capability's `migrationOrder` (`0250_audit_0001_init`), so the sorted registry *is* the order Pithy
35
+ * promises — and it is the same order whatever sequence an adopter typed `pithy add` in. Kysely's
36
+ * default mode additionally requires the applied ledger to be a prefix of that order, which nothing in
37
+ * the model can guarantee: add `email` (200), then `auth` (300), then `audit` (250), and audit's
38
+ * migration sorts between two applied ones. Every later run then failed, naming keys and index
39
+ * positions the adopter never chose, and the only recovery was wiping the database.
40
+ *
41
+ * Unordered mode still applies pending migrations in `migrationOrder`. It drops one thing: the demand
42
+ * that the past agree with it. That is sound here because no capability's tables reference another's
43
+ * — order across capabilities is arbitrary by design, and order *within* one is preserved, since a
44
+ * capability arrives with its whole set. Refusing the add with an actionable error was the
45
+ * alternative; it explains the corner instead of removing it.
46
+ */
47
+ function migrator(database, provider) {
48
+ return new Migrator({
49
+ db: migrationKysely(database),
50
+ provider: batchedProvider(provider, database),
51
+ migrationTableName: MIGRATION_TABLE,
52
+ migrationLockTableName: MIGRATION_LOCK_TABLE,
53
+ allowUnorderedMigrations: true
54
+ });
55
+ }
56
+ /** Run every pending migration to latest. An empty provider resolves to `[]`. */
57
+ async function runMigrations(database, provider, target) {
58
+ const { error, results } = await migrator(database, provider).migrateToLatest();
59
+ return settle("run", error, results, target);
60
+ }
61
+ /**
62
+ * What this database has applied, against what this project declares — **both directions, one read**.
63
+ *
64
+ * `pithy doctor` used to ask only how many declared migrations had not run, because that is the
65
+ * question Kysely's own `getMigrations` answers: it maps over the *provider's* migrations and looks
66
+ * each one up in the ledger, so a row the provider does not carry is not in the result at all. A
67
+ * subtraction cannot see an extra. A database holding a migration the project has since deleted
68
+ * therefore reported `none pending ✓` while the migrator refused to run against it at all — Kysely
69
+ * reads an unrecognized applied migration as a corrupted chain and applies nothing (#282).
70
+ *
71
+ * Asking both halves in one function is the point. Two functions — one counting pending, one hunting
72
+ * undeclared — is how the first half shipped alone, and the caller reaching for the count is exactly
73
+ * the caller who needs the other answer.
74
+ *
75
+ * Read-only, applying nothing: the seam behind `pithy doctor`'s migrations line, `pithy deploy`'s
76
+ * warn-only "schema is behind" check, and the refusal `pithy migrate` raises before it writes.
77
+ */
78
+ async function readMigrationLedger(database, provider) {
79
+ const declared = Object.keys(await provider.getMigrations()).sort();
80
+ const applied = await appliedMigrationNames(migrationKysely(database));
81
+ return {
82
+ pending: declared.filter((name) => !applied.has(name)),
83
+ undeclared: [...applied].filter((name) => !declared.includes(name)).sort()
84
+ };
85
+ }
86
+ /** Step the latest applied migration back — one step, the `pithy migrate --rollback` seam. */
87
+ async function rollbackMigration(database, provider, target) {
88
+ const { error, results } = await migrator(database, provider).migrateDown();
89
+ return settle("rollback", error, results, target);
90
+ }
91
+ /**
92
+ * Fully reset one database's schema: every applied migration's `down` runs, in one pass, in reverse
93
+ * chronological order — Kysely's `NO_MIGRATIONS` target, not just the latest — then every migration's
94
+ * `up` reapplies from empty. The seam behind `pithy seed --redo`'s destructive rebuild: because the
95
+ * schema comes back empty, the ordinary non-destructive seed writes (`INSERT OR IGNORE`, KV
96
+ * skip-if-exists) simply work afterward — there is no per-row identity problem to solve. An empty
97
+ * ledger rolls back nothing; an empty provider reapplies nothing.
98
+ */
99
+ async function resetMigrations(database, provider, target) {
100
+ const runner = migrator(database, provider);
101
+ const down = await runner.migrateTo(NO_MIGRATIONS);
102
+ const downResults = settle("resetDown", down.error, down.results, target);
103
+ const up = await runner.migrateToLatest();
104
+ const upResults = settle("resetUp", up.error, up.results, target);
105
+ return [...downResults, ...upResults];
106
+ }
107
+ /**
108
+ * The applied migration names recorded in the ledger — empty when the ledger table doesn't exist yet.
109
+ * Existence is checked against `sqlite_master` (a plain select D1 permits) rather than by catching the
110
+ * read's error, so a genuine read failure surfaces instead of being silently treated as "none applied".
111
+ */
112
+ async function appliedMigrationNames(db) {
113
+ if ((await sql`select name from sqlite_master where type = 'table' and name = ${"pithy_migrations"}`.execute(db)).rows.length === 0) return /* @__PURE__ */ new Set();
114
+ const { rows } = await sql`select name from ${sql.table(MIGRATION_TABLE)}`.execute(db);
115
+ return new Set(rows.map((row) => row.name));
116
+ }
117
+ /**
118
+ * Surgically drop **one capability's** migrations: run each of the provider's `down` functions in
119
+ * reverse order and delete only those ledger rows, leaving every other capability's tables and
120
+ * bookkeeping untouched. The seam behind `pithy remove --drop`. Kysely's stepwise `Migrator` refuses a
121
+ * provider that doesn't span the whole ledger (it reads a foreign row as corrupt state), so a
122
+ * per-capability drop can't go through it — this reverses the capability's own migrations directly.
123
+ * Only migrations recorded in the ledger are reversed; an absent ledger drops nothing.
124
+ */
125
+ async function dropMigrations(database, provider, target) {
126
+ const db = migrationKysely(database);
127
+ const migrations = await batchedProvider(provider, database).getMigrations();
128
+ const applied = await appliedMigrationNames(db);
129
+ const results = [];
130
+ for (const name of Object.keys(migrations).sort().reverse()) {
131
+ if (!applied.has(name)) continue;
132
+ const down = migrations[name]?.down;
133
+ if (!down) continue;
134
+ try {
135
+ await down(db);
136
+ await sql`delete from ${sql.table(MIGRATION_TABLE)} where name = ${name}`.execute(db);
137
+ results.push({
138
+ migrationName: name,
139
+ direction: "Down",
140
+ status: "Success"
141
+ });
142
+ } catch (error) {
143
+ const dropped = results.map((result) => `"${result.migrationName}"`);
144
+ throw new InternalError({
145
+ message: `Couldn't drop "${name}"${on(target)}. ${reasonOf(error)}`,
146
+ detail: `${where(target)}${dropped.length ? ` Dropped before the failure: ${dropped.join(", ")}.` : ""}`,
147
+ action: "Fix the migration's down, or drop its table by hand. Run pithy remove --drop again."
148
+ }, { cause: error });
149
+ }
150
+ }
151
+ return results;
152
+ }
153
+ /** Brand-voice problem and action lines per direction (docs/CLI.md §3.3). */
154
+ const VOICE = {
155
+ run: {
156
+ failed: (key) => `Couldn't apply "${key}"`,
157
+ fallback: "The migration run failed",
158
+ action: "Fix the migration. Run pithy migrate again."
159
+ },
160
+ rollback: {
161
+ failed: (key) => `Couldn't roll back "${key}"`,
162
+ fallback: "The rollback failed",
163
+ action: "Fix the migration. Run pithy migrate --rollback again."
164
+ },
165
+ resetDown: {
166
+ failed: (key) => `Couldn't roll back "${key}" during reset`,
167
+ fallback: "The schema reset failed while rolling back",
168
+ action: "Fix the migration. Run pithy seed --redo again."
169
+ },
170
+ resetUp: {
171
+ failed: (key) => `Couldn't reapply "${key}" during reset`,
172
+ fallback: "The schema reset failed while reapplying",
173
+ action: "Fix the migration. Run pithy seed --redo again."
174
+ }
175
+ };
176
+ /** ` on DB`, or nothing at all — every problem line here ends with this and then a period. */
177
+ function on(target) {
178
+ return target ? ` on ${target.binding}` : "";
179
+ }
180
+ /** The throw-site half of the same fact: the database name behind the binding, for `detail`. */
181
+ function where(target) {
182
+ return target ? `Database "${target.database}" on binding ${target.binding}.` : "";
183
+ }
184
+ /**
185
+ * The underlying failure, as a sentence — **in `message`, where it is actually rendered**.
186
+ *
187
+ * It used to go to `detail` alone, and `detail` is the field the terminal renderer never prints and the
188
+ * HTTP codec strips. So `pithy migrate` said *Migration run failed. Fix the migration.* and nothing
189
+ * else, over a Kysely error that had already named the migration and the reason (#282). A migration
190
+ * failure is D1 answering our own SQL — `no such column: tenant`, `corrupted migrations: previously
191
+ * executed migration X is missing` — and that sentence *is* the actionable content. Withholding it is
192
+ * not a security boundary, it is the bug.
193
+ *
194
+ * Deliberately not through `safeReason`: that filter drops anything over 160 characters, and a silent
195
+ * drop is what this whole path is being fixed for. Color codes come off, because they are formatting
196
+ * a runtime added.
197
+ */
198
+ function reasonOf(error) {
199
+ const reason = (causeMessage(error) ?? String(error)).trim();
200
+ return reason.endsWith(".") ? reason : `${reason}.`;
201
+ }
202
+ function settle(verb, error, results, target) {
203
+ if (error !== void 0) {
204
+ const voice = VOICE[verb];
205
+ const failed = results?.find((result) => result.status === "Error");
206
+ const applied = results?.filter((result) => result.status === "Success").map((result) => result.migrationName);
207
+ const problem = failed ? voice.failed(failed.migrationName) : voice.fallback;
208
+ const stuck = applied?.length ? `Applied before the failure: ${applied.map((name) => `"${name}"`).join(", ")}.` : "";
209
+ throw new InternalError({
210
+ message: `${problem}${on(target)}. ${reasonOf(error)}`,
211
+ detail: [where(target), stuck].filter(Boolean).join(" "),
212
+ action: voice.action
213
+ }, { cause: error });
214
+ }
215
+ return results ?? [];
216
+ }
217
+ //#endregion
218
+ export { dropMigrations, readMigrationLedger, resetMigrations, rollbackMigration, runMigrations };
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ /** The environments a domain may be declared for — every managed one, never `dev`. */
3
+ export declare const DOMAIN_ENVIRONMENTS: ("prod" | "staging")[];
4
+ /** One environment's public address for one Worker. */
5
+ export declare const WorkerDomain: z.ZodObject<{
6
+ pattern: z.ZodString;
7
+ zone: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export type WorkerDomain = z.infer<typeof WorkerDomain>;
10
+ /**
11
+ * A Worker's domains, keyed by environment.
12
+ *
13
+ * Every key optional: a project with no domain yet is legitimate, and so is one that has staging wired
14
+ * and production not. Adding one later is a config edit plus a deploy, never a rescaffold.
15
+ */
16
+ export declare const WorkerDomains: z.ZodObject<{
17
+ staging: z.ZodOptional<z.ZodObject<{
18
+ pattern: z.ZodString;
19
+ zone: z.ZodString;
20
+ }, z.core.$strip>>;
21
+ prod: z.ZodOptional<z.ZodObject<{
22
+ pattern: z.ZodString;
23
+ zone: z.ZodString;
24
+ }, z.core.$strip>>;
25
+ }, z.core.$strip>;
26
+ export type WorkerDomains = z.infer<typeof WorkerDomains>;
27
+ /**
28
+ * The declared domain for one environment, or null where none is declared (including every `dev`).
29
+ *
30
+ * `environment` may be `undefined`, which is what `compositionEnvironment()` answers when nothing stamped
31
+ * one. That is not a `dev` default — it is "this composition names no environment", and an environment
32
+ * nobody named has declared no domain, so the answer is the same `null` an undeclared one gets.
33
+ */
34
+ export declare function domainFor(domains: WorkerDomains | undefined, environment: string | undefined): WorkerDomain | null;
35
+ /** The base URL a declared domain implies. Always `https` — a custom domain on Cloudflare is TLS-terminated. */
36
+ export declare function baseUrlFor(domain: WorkerDomain): string;
37
+ /**
38
+ * The origin an environment with no declared domain resolves to.
39
+ *
40
+ * **No port, and that is the whole design.** Local dev's port is assigned per Worker per run from the
41
+ * feature's reserved block, so it is the one address nobody can write down — `@pithy-sh/auth` resolves
42
+ * dev's base URL from the host the request arrived at for exactly that reason, and a config-time value
43
+ * could only ever be a stale guess at it. So this is a *placeholder that fails closed*: a link built
44
+ * against it goes nowhere, which is useless rather than harmful.
45
+ *
46
+ * Compare what it replaces. Every capability that made an adopter write an origin got production's
47
+ * written into staging, and the failure was never "the link is broken" — it was a staging deploy mailing
48
+ * real users magic links **into production**, an unsubscribe from a staging test unsubscribing that
49
+ * person in production, and a Checkout return landing a staging payer in production on an account that
50
+ * had bought nothing. A fallback that goes nowhere is the only one that cannot do any of that.
51
+ */
52
+ export declare const LOCAL_ORIGIN = "http://localhost";
53
+ /** An environment's public origin, and whether the declaration is where it came from. */
54
+ export interface ResolvedOrigin {
55
+ /** The absolute origin — scheme and host, never a trailing slash. */
56
+ origin: string;
57
+ /** The hostname alone: a route pattern, and what Turnstile binds a widget to. */
58
+ hostname: string;
59
+ /** Whether `domains` declared it. `false` means {@link LOCAL_ORIGIN} was substituted. */
60
+ declared: boolean;
61
+ }
62
+ /**
63
+ * **Where is this Worker publicly reachable, in this environment?** — asked once, answered once.
64
+ *
65
+ * The two halves already existed ({@link domainFor} and {@link baseUrlFor}) and nothing composed them,
66
+ * so every caller that needed the answer did the two-step for itself and every *adopter* who needed it
67
+ * wrote a URL down. The first adopter's one Worker config carried `https://app.pithy.sh` three times —
68
+ * `auth.baseURL`, `email.baseUrl`, and the Stripe return URLs — each wrong for staging in its own way,
69
+ * each found on a different day. Fixing it inside one capability does not stop the next capability
70
+ * asking the same question, so the question gets one answer that anyone can call.
71
+ *
72
+ * **The fallback never reaches for another environment's origin.** An environment absent from `domains`
73
+ * is one that is not published, so it resolves to {@link LOCAL_ORIGIN} and nothing else. Falling back to
74
+ * production's is what the shape being replaced did, and it is the one behavior here that was actively
75
+ * dangerous rather than merely wrong.
76
+ *
77
+ * **A deployed environment must never *keep* that fallback**, and it is not this function's job to say
78
+ * so — `pithy deploy --env <name>` refuses an environment whose origin its config does not declare
79
+ * (#253), and `pithy doctor` reports it first. One rule in two halves: this one cannot invent another
80
+ * environment's origin, and deploy makes sure the placeholder never ships.
81
+ *
82
+ * **Not every origin should derive from this, and `controlplane.issuer` is the one to name.** That is an
83
+ * **identity**, not an address: a connection stores the issuer it was created with and verification
84
+ * checks that stored value, so a per-environment issuer would make a connection minted in staging
85
+ * unverifiable in production. That may be the better isolation, but it is a decision about trust rather
86
+ * than about reachability, and a helper whose job is "where am I reachable" must not sweep it up.
87
+ */
88
+ export declare function resolveOrigin(environment: string | undefined, domains: WorkerDomains | undefined): ResolvedOrigin;
89
+ /**
90
+ * The one line an adopter writes: this Worker's public origin for the environment it is running in.
91
+ *
92
+ * ```ts
93
+ * const DOMAINS = { staging: { … }, prod: { … } };
94
+ * const PUBLIC_ORIGIN = originFor(compositionEnvironment(), DOMAINS);
95
+ * ```
96
+ *
97
+ * That is the line `pithy init` scaffolds, so an adopter who never thinks about it gets it right and one
98
+ * who wants a literal can still write one. `compositionEnvironment()` is `string | undefined`, and it is
99
+ * taken as such rather than defaulted to `dev` at the call site: an unstamped composition has declared no
100
+ * domain either way, and a `?? "dev"` in every adopter's config would be a default nobody chose.
101
+ *
102
+ * Named for what it is rather than for who asked. The first adopter's own version was called
103
+ * `AUTH_BASE_URL`, which is part of why `email` and `payments` were missed for days: the constant read
104
+ * as auth's private business when it is the Worker's address, and every capability that needs an origin
105
+ * needs this one.
106
+ */
107
+ export declare function originFor(environment: string | undefined, domains: WorkerDomains | undefined): string;
108
+ //# sourceMappingURL=domains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/naming/domains.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAsCxB,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,wBAA8D,CAAC;AAE/F,uDAAuD;AACvD,eAAO,MAAM,YAAY;;;iBA+BrB,CAAC;AACL,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;iBAOvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAKlH;AAED,gHAAgH;AAChH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,aAAa,GAAG,SAAS,GAAG,cAAc,CAIjH;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,CAErG"}