@pithy-sh/core 0.1.2 → 0.1.3

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,104 @@
1
+ import { z } from "zod";
2
+ //#region src/data/cursor.ts
3
+ /**
4
+ * Keyset pagination, once, for every capability that lists an unbounded table.
5
+ *
6
+ * ## Why keyset and never offset
7
+ *
8
+ * `OFFSET` shifts under a client whenever a row is inserted, and every table these paginate — audit
9
+ * events, email jobs, users, ledger transactions — is written to constantly while somebody is reading
10
+ * it. With `OFFSET 25` a row inserted at the head pushes one row from page 1 onto page 2, so a client
11
+ * paging through sees it twice and misses nothing only by luck; a deletion drops a row entirely. A
12
+ * keyset cursor names the last row's sort position, so the next page starts exactly where the previous
13
+ * ended whatever happened in between.
14
+ *
15
+ * ## Why this is in core rather than copied
16
+ *
17
+ * `@pithy-sh/support` had the only implementation, and Part 3 of #89 needs the same thing in four more
18
+ * packages. Four copies of a security-adjacent decode — where the difference between "malformed cursor"
19
+ * and "500" is a caller's ability to probe — is four places for one of them to be wrong. Capabilities
20
+ * depend on core seams, which is exactly what this is.
21
+ *
22
+ * ## The shape of a page
23
+ *
24
+ * A caller asks for `limit`; the query fetches `limit + 1` and, when it gets them, drops the extra and
25
+ * returns a `nextCursor`. That is what makes "is there another page" answerable **without a count
26
+ * query**, which on an audit table is the difference between a page load and a table scan.
27
+ */
28
+ /** A sensible page when a caller names none. Small enough to render, large enough to be one request. */
29
+ const DEFAULT_PAGE_SIZE = 25;
30
+ /** The most a caller may ask for in one page, whatever they send. An unbounded page is a table scan. */
31
+ const MAX_PAGE_SIZE = 100;
32
+ /**
33
+ * The `limit` a query should actually use, clamped into range.
34
+ *
35
+ * Clamped rather than rejected: a client asking for 1000 wants "as many as you'll give me", and failing
36
+ * the request teaches it nothing a capped page does not. A caller asking for 0 or a negative gets one
37
+ * row rather than an empty page that looks like the end of the list.
38
+ */
39
+ function pageLimit(requested) {
40
+ return Math.min(Math.max(requested ?? 25, 1), 100);
41
+ }
42
+ /**
43
+ * An opaque position in a descending `(sort, id)` ordering.
44
+ *
45
+ * `sort` is the primary column's value — a ms-epoch for a date, a number for a monotonic id. `id` is the
46
+ * tiebreak that makes the position exact: without it, two rows sharing a timestamp straddle a page
47
+ * boundary and one of them is skipped or repeated.
48
+ */
49
+ const PageCursor = z.object({
50
+ sort: z.union([z.number(), z.string()]).describe("The sort column's value on the last row of the previous page — a ms-epoch for a date column, a number for a monotonic id, a string for an ISO-8601 date column. A string and a number compare differently in SQLite, so this carries whichever the column actually stores."),
51
+ id: z.union([z.number(), z.string()]).describe("That row's primary key, the tiebreak that makes the position exact. Two rows sharing a sort value would otherwise straddle a page boundary, and one of them would be skipped or returned twice.")
52
+ }).describe("An opaque position in a descending (sort, id) ordering — where the next page starts.");
53
+ /**
54
+ * Encode a cursor for the wire.
55
+ *
56
+ * Base64url over JSON, and **opaque by intent rather than by obscurity**: the point is that a client
57
+ * cannot construct one by hand and therefore cannot come to depend on its shape, so the ordering can
58
+ * change later without breaking every caller. It is not a secret — it holds a sort value and an id the
59
+ * caller was just given, both of which were in the page it came from.
60
+ */
61
+ function encodeCursor(cursor) {
62
+ const bytes = new TextEncoder().encode(JSON.stringify(cursor));
63
+ let binary = "";
64
+ for (const byte of bytes) binary += String.fromCharCode(byte);
65
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
66
+ }
67
+ /**
68
+ * Decode a cursor. Returns undefined for anything malformed.
69
+ *
70
+ * **A bad cursor is a first page, not a 500.** Cursors travel through URLs, get truncated by clients,
71
+ * and outlive deploys; treating a malformed one as an error turns an ordinary client bug into a page
72
+ * that never loads, and hands anyone probing the endpoint a way to tell a parse failure from a
73
+ * not-found. Every failure mode here — bad base64, bad JSON, wrong shape — collapses to the same
74
+ * undefined.
75
+ */
76
+ function decodeCursor(value) {
77
+ if (!value) return void 0;
78
+ try {
79
+ const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/"));
80
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
81
+ const parsed = PageCursor.safeParse(JSON.parse(new TextDecoder().decode(bytes)));
82
+ return parsed.success ? parsed.data : void 0;
83
+ } catch {
84
+ return;
85
+ }
86
+ }
87
+ /**
88
+ * Split an over-fetched result into the page and the cursor that follows it.
89
+ *
90
+ * Pass the rows a query returned when asked for `limit + 1`. When there are more than `limit`, the extra
91
+ * is dropped and `nextCursor` names the last row of the page; otherwise `nextCursor` is null and the
92
+ * client knows it has reached the end without a second request.
93
+ */
94
+ function toPage(rows, limit, position) {
95
+ const hasMore = rows.length > limit;
96
+ const items = hasMore ? rows.slice(0, limit) : [...rows];
97
+ const last = items[items.length - 1];
98
+ return {
99
+ items,
100
+ nextCursor: hasMore && last !== void 0 ? encodeCursor(position(last)) : null
101
+ };
102
+ }
103
+ //#endregion
104
+ export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PageCursor, decodeCursor, encodeCursor, pageLimit, toPage };
@@ -0,0 +1,55 @@
1
+ import type { Kysely } from "kysely";
2
+ import type { Migration } from "kysely/migration";
3
+ import type { z } from "zod";
4
+ import type { Capability } from "../capability/capability";
5
+ import { type BindingGroup } from "../capability/compose";
6
+ import { type DatabaseSchema, type SchemaMap } from "./db";
7
+ /**
8
+ * One named D1 database a capability contributes tables to: the `binding` it lives in plus this
9
+ * capability's slice of its schema. A production Worker commonly binds several D1 databases (app
10
+ * data, analytics, …); each is a named database here, and `createBackend` serves one typed `Kysely`
11
+ * per name on `c.var.db.<name>`. Capabilities targeting the same database name merge their slices.
12
+ */
13
+ export interface DatabaseSpec<Tables extends SchemaMap = SchemaMap> {
14
+ /** The D1 binding name in the Worker env this database lives in. */
15
+ binding: string;
16
+ /** This capability's slice of the database's schema (table name → Zod table schema). */
17
+ tables: Tables;
18
+ /**
19
+ * This capability's migrations for this database, by stable local key (e.g. "0001_init") —
20
+ * co-located with the tables they create, so the database association is declared once. The
21
+ * migration namespace is the capability's name; `pithy migrate` composes every capability's
22
+ * sets through `createMigrationRegistry` and runs them per database.
23
+ */
24
+ migrations?: Record<string, Migration>;
25
+ /** Sort order within this database relative to other capabilities (core low, app high). Required with `migrations`. */
26
+ migrationOrder?: number;
27
+ }
28
+ /** A capability's databases: database name → {@link DatabaseSpec}. */
29
+ export type DatabaseSpecMap = Record<string, DatabaseSpec>;
30
+ /**
31
+ * The typed database registry exposed on `c.var.db`: each named database becomes its live `Kysely`
32
+ * over that database's merged schema — so `c.var.db.app.selectFrom("…")` and
33
+ * `c.var.db.analytics.selectFrom("…")` are each typed to their own tables.
34
+ */
35
+ export type DbRegistry<Dbs extends DatabaseSpecMap> = {
36
+ [Name in keyof Dbs]: Dbs[Name] extends DatabaseSpec<infer Tables> ? Kysely<DatabaseSchema<Tables>> : never;
37
+ };
38
+ /** The merged databases: each database name → its binding and the union of every capability's tables. */
39
+ export type MergedDatabases = Record<string, BindingGroup<z.ZodType>>;
40
+ /**
41
+ * Merge every capability's `databases` into one map keyed by database name. Capabilities targeting
42
+ * the same name union their table slices (the project-wide schema, per database); a table claimed
43
+ * twice in one database, or a database name bound to two bindings, throws at assembly.
44
+ */
45
+ export declare function composeDatabases(capabilities: Capability[]): MergedDatabases;
46
+ /** A registry of live Kysely instances keyed by database name; the per-request value of `c.var.db`. */
47
+ type LiveDbRegistry = Record<string, Kysely<DatabaseSchema<SchemaMap>>>;
48
+ /**
49
+ * Build the per-request database registry from the merged databases and the request's env. Each
50
+ * `Kysely` is constructed lazily on first access from its D1 binding — a request that never touches
51
+ * `c.var.db.analytics` never builds it.
52
+ */
53
+ export declare function buildDbRegistry(env: Record<string, unknown>, databases: MergedDatabases): LiveDbRegistry;
54
+ export {};
55
+ //# sourceMappingURL=databases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databases.d.ts","sourceRoot":"","sources":["../../src/data/databases.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAA2C,MAAM,uBAAuB,CAAC;AACnG,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,MAAM,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,MAAM,SAAS,SAAS,GAAG,SAAS;IAChE,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,uHAAuH;IACvH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,GAAG,SAAS,eAAe,IAAI;KACnD,IAAI,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,YAAY,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK;CAC3G,CAAC;AAEF,yGAAyG;AACzG,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAEtE;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,eAAe,CAO5E;AAED,uGAAuG;AACvG,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAExE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,eAAe,GAAG,cAAc,CAaxG"}
@@ -0,0 +1,32 @@
1
+ import { createDatabase } from "./db.js";
2
+ import { bindingGroupsFrom, composeBindingGroups } from "../capability/compose.js";
3
+ //#region src/data/databases.ts
4
+ /**
5
+ * Merge every capability's `databases` into one map keyed by database name. Capabilities targeting
6
+ * the same name union their table slices (the project-wide schema, per database); a table claimed
7
+ * twice in one database, or a database name bound to two bindings, throws at assembly.
8
+ */
9
+ function composeDatabases(capabilities) {
10
+ return composeBindingGroups(capabilities, (cap) => bindingGroupsFrom(cap.databases, (db) => db.tables), "database", "table");
11
+ }
12
+ /**
13
+ * Build the per-request database registry from the merged databases and the request's env. Each
14
+ * `Kysely` is constructed lazily on first access from its D1 binding — a request that never touches
15
+ * `c.var.db.analytics` never builds it.
16
+ */
17
+ function buildDbRegistry(env, databases) {
18
+ const registry = {};
19
+ for (const [name, group] of Object.entries(databases)) {
20
+ let instance;
21
+ Object.defineProperty(registry, name, {
22
+ enumerable: true,
23
+ get() {
24
+ if (!instance) instance = createDatabase(env[group.binding], group.items);
25
+ return instance;
26
+ }
27
+ });
28
+ }
29
+ return registry;
30
+ }
31
+ //#endregion
32
+ export { buildDbRegistry, composeDatabases };
@@ -0,0 +1,42 @@
1
+ import type { D1Database } from "@cloudflare/workers-types";
2
+ import { type Generated, Kysely } from "kysely";
3
+ import type { z } from "zod";
4
+ /**
5
+ * The Kysely D1 database builder.
6
+ *
7
+ * One master map of table name → Zod table schema is the whole database
8
+ * definition. The database interface Kysely sees is derived from each schema's
9
+ * `z.input` side — the SQLite row shape — never a hand-written row interface.
10
+ * `CamelCasePlugin` is mandatory: tables use snake_case columns, query code
11
+ * uses camelCase, and the plugin bridges the two in both directions.
12
+ */
13
+ /** A master map of table name → that table's Zod schema. */
14
+ export type SchemaMap = Record<string, z.ZodType>;
15
+ /**
16
+ * Present a numeric `id` to Kysely as `Generated<number>`: optional on insert
17
+ * (D1 assigns it), a number on select. A table without a numeric `id` is left
18
+ * unchanged. Externally-exposed entities use a text id and so are untouched.
19
+ */
20
+ export type WithGeneratedId<Row> = Row extends {
21
+ id: number;
22
+ } ? Omit<Row, "id"> & {
23
+ id: Generated<number>;
24
+ } : Row;
25
+ /** The Kysely database interface derived from a schema map's `z.input` (row) side. */
26
+ export type DatabaseSchema<M extends SchemaMap> = {
27
+ [K in keyof M]: WithGeneratedId<z.input<M[K]>>;
28
+ };
29
+ /**
30
+ * Build a typed Kysely instance over D1 for `map`. The `map` argument carries
31
+ * the table types — it drives inference of the database interface; Kysely needs
32
+ * no values from it at runtime. `CamelCasePlugin` is always installed.
33
+ *
34
+ * So is `guardBoundParameters`. This is the one seam every Kysely instance in
35
+ * the repository comes from, so it is the one place the bound-parameter ceiling
36
+ * can be stated for all of them — including for query sites that have never
37
+ * heard of it. Five capabilities bound past the cap while the arithmetic to
38
+ * avoid it sat unimported in `boundParameters.ts` (#246, #250); a rule that
39
+ * every call site has to remember is a rule in the wrong place.
40
+ */
41
+ export declare function createDatabase<M extends SchemaMap>(d1: D1Database, map: M): Kysely<DatabaseSchema<M>>;
42
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/data/db.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B;;;;;;;;GAQG;AAEH,4DAA4D;AAC5D,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;AAElD;;;;GAIG;AACH,MAAM,MAAM,eAAe,CAAC,GAAG,IAAI,GAAG,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG,GAAG,CAAC;AAElH,sFAAsF;AACtF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,SAAS,IAAI;KAC/C,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAMrG"}
@@ -0,0 +1,24 @@
1
+ import { guardBoundParameters } from "./boundParameters.js";
2
+ import { CamelCasePlugin, Kysely } from "kysely";
3
+ import { D1Dialect } from "kysely-d1";
4
+ //#region src/data/db.ts
5
+ /**
6
+ * Build a typed Kysely instance over D1 for `map`. The `map` argument carries
7
+ * the table types — it drives inference of the database interface; Kysely needs
8
+ * no values from it at runtime. `CamelCasePlugin` is always installed.
9
+ *
10
+ * So is `guardBoundParameters`. This is the one seam every Kysely instance in
11
+ * the repository comes from, so it is the one place the bound-parameter ceiling
12
+ * can be stated for all of them — including for query sites that have never
13
+ * heard of it. Five capabilities bound past the cap while the arithmetic to
14
+ * avoid it sat unimported in `boundParameters.ts` (#246, #250); a rule that
15
+ * every call site has to remember is a rule in the wrong place.
16
+ */
17
+ function createDatabase(d1, map) {
18
+ return new Kysely({
19
+ dialect: new D1Dialect({ database: guardBoundParameters(d1) }),
20
+ plugins: [new CamelCasePlugin()]
21
+ });
22
+ }
23
+ //#endregion
24
+ export { createDatabase };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Retry a D1 write through transient faults. Ported from the CMS `withD1Retry`, adapted to
3
+ * Pithy: no injected logger (core has none), an optional `onRetry` notifier instead, and a
4
+ * documented idempotency guard. The shared helper every capability's D1 writes can wrap; the
5
+ * audit recorder is its first consumer.
6
+ *
7
+ * D1 surfaces a whole family of transient failures by message — a statement that exceeded its
8
+ * timeout, a database briefly locked/busy under contention, a dropped network connection to the
9
+ * backing store, a storage object reset mid-flight, or an opaque internal error. All of these are
10
+ * worth retrying with exponential backoff; a deterministic failure (a constraint violation, a SQL
11
+ * error) is not. The matchers key off the error message because that is all D1 exposes; a
12
+ * non-matching error is re-thrown untouched (this is a passthrough wrapper, never an originator — it
13
+ * does not wrap foreign throws in a `PithyError`; the caller's own layer owns that). By default
14
+ * **every** transient class is retried, so a caller gets robust coverage without enumerating D1's
15
+ * error vocabulary.
16
+ *
17
+ * The idempotency guard is always active and independent of `retryOn`: a `unique-constraint`
18
+ * failure **on a retry** (attempt > 0) means the prior attempt already committed the row before its
19
+ * transport hiccup — the retry is the duplicate, not a real conflict — so the wrapper returns rather
20
+ * than throwing. A unique-constraint never *initiates* a retry (it is not a transient fault), so on
21
+ * the **first** attempt it is a genuine conflict and propagates. This is what makes a broad retry
22
+ * policy safe for a write: pair it with a unique idempotency key (the audit recorder's `eventId`) and
23
+ * a post-commit retry can never double-write.
24
+ */
25
+ /** A retryable D1 fault class, matched against the error message. */
26
+ export declare const D1RetryableError: {
27
+ /** A statement exceeded its execution timeout (`exceeded timeout`, `storage timeout`, `Timed out`). */
28
+ readonly TIMEOUT: "timeout";
29
+ /** The database was locked or busy under contention (`database is locked/busy`, `SQLITE_BUSY`). */
30
+ readonly DATABASE_BUSY: "database-busy";
31
+ /** The connection to the backing store dropped mid-request (`Network connection lost`, `connection … reset`). */
32
+ readonly NETWORK: "network";
33
+ /** The storage object was reset mid-flight (`reset because its code was updated`, `caused object to be reset`). */
34
+ readonly STORAGE_RESET: "storage-reset";
35
+ /** An opaque D1/storage internal error (`internal error`, `Cannot resolve D1`). */
36
+ readonly INTERNAL: "internal";
37
+ /** A UNIQUE constraint failed — never a retry initiator; drives only the idempotency guard (see module doc). */
38
+ readonly UNIQUE_CONSTRAINT: "unique-constraint";
39
+ };
40
+ export type D1RetryableError = (typeof D1RetryableError)[keyof typeof D1RetryableError];
41
+ /** Every transient (retry-initiating) fault class — the default `retryOn`. Excludes the guard-only `unique-constraint`. */
42
+ export declare const D1_TRANSIENT_ERRORS: readonly D1RetryableError[];
43
+ /** One D1 retry attempt's context, handed to {@link D1RetryOptions.onRetry}. */
44
+ export interface D1RetryInfo {
45
+ /** The fault class that triggered the retry. */
46
+ error: D1RetryableError;
47
+ /** The attempt that just failed (0-based). */
48
+ attempt: number;
49
+ /** The configured maximum number of retries. */
50
+ maxRetries: number;
51
+ /** How long the wrapper will wait before the next attempt, in ms. */
52
+ delayMs: number;
53
+ }
54
+ /** Options for {@link withD1Retry}. All optional; the defaults retry timeouts three times. */
55
+ export interface D1RetryOptions {
56
+ /**
57
+ * Which transient fault classes initiate a retry. Defaults to {@link D1_TRANSIENT_ERRORS} — every
58
+ * transient class. Narrow it only when a caller deliberately wants to ignore some. Listing
59
+ * `unique-constraint` here has no effect — it is never a retry initiator; it only ever drives the
60
+ * always-on idempotency guard (see module doc).
61
+ */
62
+ retryOn?: readonly D1RetryableError[];
63
+ /** Maximum retries after the initial attempt. Defaults to `3`. */
64
+ maxRetries?: number;
65
+ /** Backoff base delay, in ms. Defaults to `200`. */
66
+ initialDelayMs?: number;
67
+ /** Backoff growth factor. Defaults to `2` (200ms, 400ms, 800ms…). */
68
+ backoffMultiplier?: number;
69
+ /** Notified before each backoff wait — for an optional log line. Never throws into the retry. */
70
+ onRetry?: (info: D1RetryInfo) => void;
71
+ }
72
+ /**
73
+ * The transient D1 fault class an error is, or `null` when D1 did not report one of them.
74
+ *
75
+ * The same matchers this wrapper retries on, exported because a second layer has to ask the same
76
+ * question: a Workflow step decides whether to re-drive a body that raised, and "is this D1 fault
77
+ * transient" must have exactly one answer in this kit (pithy-sh/pithy#338). A step that classified D1
78
+ * for itself would be a second vocabulary — and the one that drifted would be the one nobody read.
79
+ *
80
+ * `unique-constraint` is deliberately not reported: it is the idempotency guard's signal, never a
81
+ * transient fault, so a caller asking "should this be retried" is told no.
82
+ */
83
+ export declare function d1TransientFault(error: unknown): D1RetryableError | null;
84
+ /**
85
+ * Run `fn`, retrying it through the configured transient D1 faults with exponential backoff.
86
+ * Returns `fn`'s result; on the idempotency-guard path (a unique-constraint on a retry) returns
87
+ * `undefined` cast to `T`, since the prior attempt already produced the effect. A non-retryable
88
+ * error, or exhausted retries, throws the last error untouched.
89
+ */
90
+ export declare function withD1Retry<T>(fn: () => Promise<T>, options?: D1RetryOptions): Promise<T>;
91
+ //# sourceMappingURL=withD1Retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withD1Retry.d.ts","sourceRoot":"","sources":["../../src/data/withD1Retry.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,qEAAqE;AACrE,eAAO,MAAM,gBAAgB;IAC3B,uGAAuG;aACvG,OAAO,EAAE,SAAS;IAClB,mGAAmG;aACnG,aAAa,EAAE,eAAe;IAC9B,iHAAiH;aACjH,OAAO,EAAE,SAAS;IAClB,mHAAmH;aACnH,aAAa,EAAE,eAAe;IAC9B,mFAAmF;aACnF,QAAQ,EAAE,UAAU;IACpB,gHAAgH;aAChH,iBAAiB,EAAE,mBAAmB;CAC9B,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAExF,2HAA2H;AAC3H,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAM1D,CAAC;AAEF,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,KAAK,EAAE,gBAAgB,CAAC;IACxB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iGAAiG;IACjG,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;CACvC;AA0CD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAExE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,CA6B/F"}
@@ -0,0 +1,152 @@
1
+ //#region src/data/withD1Retry.ts
2
+ /**
3
+ * Retry a D1 write through transient faults. Ported from the CMS `withD1Retry`, adapted to
4
+ * Pithy: no injected logger (core has none), an optional `onRetry` notifier instead, and a
5
+ * documented idempotency guard. The shared helper every capability's D1 writes can wrap; the
6
+ * audit recorder is its first consumer.
7
+ *
8
+ * D1 surfaces a whole family of transient failures by message — a statement that exceeded its
9
+ * timeout, a database briefly locked/busy under contention, a dropped network connection to the
10
+ * backing store, a storage object reset mid-flight, or an opaque internal error. All of these are
11
+ * worth retrying with exponential backoff; a deterministic failure (a constraint violation, a SQL
12
+ * error) is not. The matchers key off the error message because that is all D1 exposes; a
13
+ * non-matching error is re-thrown untouched (this is a passthrough wrapper, never an originator — it
14
+ * does not wrap foreign throws in a `PithyError`; the caller's own layer owns that). By default
15
+ * **every** transient class is retried, so a caller gets robust coverage without enumerating D1's
16
+ * error vocabulary.
17
+ *
18
+ * The idempotency guard is always active and independent of `retryOn`: a `unique-constraint`
19
+ * failure **on a retry** (attempt > 0) means the prior attempt already committed the row before its
20
+ * transport hiccup — the retry is the duplicate, not a real conflict — so the wrapper returns rather
21
+ * than throwing. A unique-constraint never *initiates* a retry (it is not a transient fault), so on
22
+ * the **first** attempt it is a genuine conflict and propagates. This is what makes a broad retry
23
+ * policy safe for a write: pair it with a unique idempotency key (the audit recorder's `eventId`) and
24
+ * a post-commit retry can never double-write.
25
+ */
26
+ /** A retryable D1 fault class, matched against the error message. */
27
+ const D1RetryableError = {
28
+ /** A statement exceeded its execution timeout (`exceeded timeout`, `storage timeout`, `Timed out`). */
29
+ TIMEOUT: "timeout",
30
+ /** The database was locked or busy under contention (`database is locked/busy`, `SQLITE_BUSY`). */
31
+ DATABASE_BUSY: "database-busy",
32
+ /** The connection to the backing store dropped mid-request (`Network connection lost`, `connection … reset`). */
33
+ NETWORK: "network",
34
+ /** The storage object was reset mid-flight (`reset because its code was updated`, `caused object to be reset`). */
35
+ STORAGE_RESET: "storage-reset",
36
+ /** An opaque D1/storage internal error (`internal error`, `Cannot resolve D1`). */
37
+ INTERNAL: "internal",
38
+ /** A UNIQUE constraint failed — never a retry initiator; drives only the idempotency guard (see module doc). */
39
+ UNIQUE_CONSTRAINT: "unique-constraint"
40
+ };
41
+ /** Every transient (retry-initiating) fault class — the default `retryOn`. Excludes the guard-only `unique-constraint`. */
42
+ const D1_TRANSIENT_ERRORS = [
43
+ D1RetryableError.TIMEOUT,
44
+ D1RetryableError.DATABASE_BUSY,
45
+ D1RetryableError.NETWORK,
46
+ D1RetryableError.STORAGE_RESET,
47
+ D1RetryableError.INTERNAL
48
+ ];
49
+ /**
50
+ * Lowercased message substrings that identify each fault class. Matching is case-insensitive (the
51
+ * message is lowered first) and substring-based, because D1 wraps the same underlying fault in
52
+ * varying envelopes (`D1_ERROR: …`, a bare SQLite message, a Workers runtime message). Each list is
53
+ * the set of signatures Cloudflare is known to surface for that class.
54
+ */
55
+ const errorMatchers = {
56
+ timeout: [
57
+ "exceeded timeout",
58
+ "storage timeout",
59
+ "timed out"
60
+ ],
61
+ "database-busy": [
62
+ "database is locked",
63
+ "database is busy",
64
+ "sqlite_busy"
65
+ ],
66
+ network: [
67
+ "network connection lost",
68
+ "connection was reset",
69
+ "connection reset",
70
+ "connection lost"
71
+ ],
72
+ "storage-reset": [
73
+ "reset because its code was updated",
74
+ "caused object to be reset",
75
+ "durable object reset"
76
+ ],
77
+ internal: [
78
+ "internal error",
79
+ "cannot resolve d1",
80
+ "an unknown error occurred"
81
+ ],
82
+ "unique-constraint": ["unique constraint failed"]
83
+ };
84
+ /** Whether a lowercased message matches any signature of `fault`. */
85
+ function matchesFault(message, fault) {
86
+ return errorMatchers[fault].some((signature) => message.includes(signature));
87
+ }
88
+ /** Whether an error is a UNIQUE-constraint failure — the idempotency-guard signal. */
89
+ function isUniqueConstraint(error) {
90
+ return error instanceof Error && matchesFault(error.message.toLowerCase(), D1RetryableError.UNIQUE_CONSTRAINT);
91
+ }
92
+ /**
93
+ * The transient fault class an error matches within `retryOn`, or `null` if none. `unique-constraint`
94
+ * is excluded from initiation — it is handled only by the idempotency guard — so listing it in
95
+ * `retryOn` is a no-op.
96
+ */
97
+ function matchTransient(error, retryOn) {
98
+ if (!(error instanceof Error)) return null;
99
+ const message = error.message.toLowerCase();
100
+ for (const fault of retryOn) {
101
+ if (fault === D1RetryableError.UNIQUE_CONSTRAINT) continue;
102
+ if (matchesFault(message, fault)) return fault;
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * The transient D1 fault class an error is, or `null` when D1 did not report one of them.
108
+ *
109
+ * The same matchers this wrapper retries on, exported because a second layer has to ask the same
110
+ * question: a Workflow step decides whether to re-drive a body that raised, and "is this D1 fault
111
+ * transient" must have exactly one answer in this kit (pithy-sh/pithy#338). A step that classified D1
112
+ * for itself would be a second vocabulary — and the one that drifted would be the one nobody read.
113
+ *
114
+ * `unique-constraint` is deliberately not reported: it is the idempotency guard's signal, never a
115
+ * transient fault, so a caller asking "should this be retried" is told no.
116
+ */
117
+ function d1TransientFault(error) {
118
+ return matchTransient(error, D1_TRANSIENT_ERRORS);
119
+ }
120
+ /**
121
+ * Run `fn`, retrying it through the configured transient D1 faults with exponential backoff.
122
+ * Returns `fn`'s result; on the idempotency-guard path (a unique-constraint on a retry) returns
123
+ * `undefined` cast to `T`, since the prior attempt already produced the effect. A non-retryable
124
+ * error, or exhausted retries, throws the last error untouched.
125
+ */
126
+ async function withD1Retry(fn, options) {
127
+ const retryOn = options?.retryOn ?? D1_TRANSIENT_ERRORS;
128
+ const maxRetries = options?.maxRetries ?? 3;
129
+ const initialDelayMs = options?.initialDelayMs ?? 200;
130
+ const backoffMultiplier = options?.backoffMultiplier ?? 2;
131
+ let lastError;
132
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
133
+ return await fn();
134
+ } catch (error) {
135
+ lastError = error;
136
+ if (attempt > 0 && isUniqueConstraint(error)) return void 0;
137
+ const fault = matchTransient(error, retryOn);
138
+ if (!fault) throw error;
139
+ if (attempt >= maxRetries) break;
140
+ const delayMs = initialDelayMs * backoffMultiplier ** attempt;
141
+ options?.onRetry?.({
142
+ error: fault,
143
+ attempt,
144
+ maxRetries,
145
+ delayMs
146
+ });
147
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
148
+ }
149
+ throw lastError;
150
+ }
151
+ //#endregion
152
+ export { D1RetryableError, D1_TRANSIENT_ERRORS, d1TransientFault, withD1Retry };
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ export declare const EntitlementKey: z.ZodString;
3
+ export type EntitlementKey = z.output<typeof EntitlementKey>;
4
+ /**
5
+ * One entitlement the caller holds, as the seam exposes it. This is the resolved read model, not a table:
6
+ * `@pithy-sh/payments` materializes a row per `(holder, entitlement)` and decodes it into this shape.
7
+ *
8
+ * **Who the holder is stays out of this shape, deliberately.** A payments project bills either a person or
9
+ * an organization, and its rows are keyed on that pair — but the seam resolves for *one* caller acting for
10
+ * *one* holder, so the pair is constant across a single `list()` and every element of the result would
11
+ * repeat it. Carrying it here would put a comparison within reach of {@link requireEntitlement} that the
12
+ * gate has no correct way to make (see {@link EntitlementResolver}), and would hand every non-payments
13
+ * provider two fields it cannot fill.
14
+ *
15
+ * `active` and `expiresAt` are both present, and both matter. The flag is an optimization written by
16
+ * the projection; the timestamp is the truth. {@link entitlementGrantsAccess} applies both, so a
17
+ * subscription that lapsed with no notification arriving stops granting access on the read path
18
+ * without waiting for a write.
19
+ */
20
+ export declare const Entitlement: z.ZodObject<{
21
+ key: z.ZodString;
22
+ active: z.ZodBoolean;
23
+ expiresAt: z.ZodNullable<z.ZodDate>;
24
+ source: z.ZodNullable<z.ZodString>;
25
+ }, z.core.$strip>;
26
+ export type Entitlement = z.infer<typeof Entitlement>;
27
+ /**
28
+ * The read-time rule, in one place: an entitlement grants access when the projection marked it active
29
+ * **and** it has not expired. Applied by {@link requireEntitlement} rather than left to each provider,
30
+ * so the recheck is a property of the seam instead of something a provider must remember.
31
+ *
32
+ * A read never writes. Repairing a stale row is the reconciliation Workflow's job, which keeps the hot
33
+ * path a pure read.
34
+ */
35
+ export declare function entitlementGrantsAccess(entitlement: Entitlement, now: Date): boolean;
36
+ /** Every key in `entitlements` that grants access at `now`, deduped. The set a gate is checked against. */
37
+ export declare function grantedEntitlementKeys(entitlements: readonly Entitlement[], now: Date): Set<string>;
38
+ /**
39
+ * The resolver seam on the request context (`c.var.entitlements`). One method: every entitlement the
40
+ * **current caller** holds. The resolver is built per request by the provider's middleware, so it
41
+ * already knows who is asking — a gate never passes a holder, and so can never gate on the wrong one.
42
+ *
43
+ * **That property is why the seam still takes no argument, now that a provider's holder can be an
44
+ * organization rather than a person.** `@pithy-sh/payments` decides which subject the caller is acting
45
+ * for *before* it constructs the resolver: the resolution happens once, in its middleware, against the
46
+ * adopter's own membership model, and the resolver closes over the answer. So a gate cannot pass the
47
+ * wrong holder because it passes none, and the alternative — a `list(holder)` — would put that choice at
48
+ * every call site, where a route reaching for `c.var.auth.userId` under organization billing looks
49
+ * exactly like correct code and quietly checks a company's plan against one employee.
50
+ *
51
+ * `provider` names the capability answering, or is `null` when none is composed. It exists so a denial
52
+ * can say *which* of the two reasons it was — genuinely unentitled, or nothing wired — in `detail`,
53
+ * where an operator sees it and a client never does.
54
+ */
55
+ export interface EntitlementResolver {
56
+ /** The capability resolving entitlements (`payments`), or null when none is composed. */
57
+ readonly provider: string | null;
58
+ /** Every entitlement the current caller holds, granting or not. The gate applies the read-time rule. */
59
+ list(): Promise<readonly Entitlement[]>;
60
+ /**
61
+ * Who {@link list} answered for, **for the log and the audit trail only**. Optional: a resolver that
62
+ * cannot say omits it, and every gate behaves exactly as it did before this existed.
63
+ *
64
+ * See {@link EntitlementHolder} for why this is a label and a tenant rather than the holder itself.
65
+ */
66
+ holder?(): Promise<EntitlementHolder | undefined>;
67
+ }
68
+ /**
69
+ * How a resolver's holder appears in a denial's `detail` and on the denial's audit row — and **nothing a
70
+ * gate may compare**.
71
+ *
72
+ * A provider's holder stopped being a person when `@pithy-sh/payments` learned to bill organizations, and
73
+ * that left a denial unable to say which of two things had happened: a company that has bought nothing,
74
+ * and a caller acting for **no** company at all — the ordinary state of somebody signed in with no
75
+ * organization selected. Both produced `payments resolved []`. They are different problems: one is a sale,
76
+ * the other is a subject resolver returning nothing. The same gap left the denial's audit row with no
77
+ * `tenant`, so a trail could not answer "which of our customers is hitting the paywall" — `actorId` cannot,
78
+ * because one person acts in two tenants.
79
+ *
80
+ * **So why not put the holder on the seam?** Because {@link EntitlementResolver} deliberately takes no
81
+ * holder and returns none: the provider decides who the caller acts for *once*, in its own middleware,
82
+ * before it builds the resolver, and a gate that never receives a holder can never check the wrong one.
83
+ * A field carrying the holder itself would put that choice back within reach — a route comparing a resolved
84
+ * subject against `c.var.auth.userId` looks like careful code and quietly checks a company's plan against
85
+ * one employee.
86
+ *
87
+ * The shape is what keeps that from happening. Neither field is the holder:
88
+ *
89
+ * - **`label`** is display text for one log line. It is not parsed, not matched, and has no format this
90
+ * package defines — a provider renders it however reads best (`@pithy-sh/payments` uses `user:ada` and
91
+ * `organization:acme`, its own encoding).
92
+ * - **`tenant`** is the audit dimension and nothing else, opaque exactly as `AuditEvent.tenant` is. `null`
93
+ * means *the holder is not a tenant* — which is the honest answer under per-person billing, where the
94
+ * holder is the actor and a tenant echoing `actorId` would be a dimension the app does not have.
95
+ *
96
+ * There is nothing here to compare a caller against, because there is no caller-shaped value: `label` is
97
+ * prose and `tenant` is a dimension. `require.test.ts` pins the property directly — the gate's decision is
98
+ * identical for every holder, including none.
99
+ */
100
+ export interface EntitlementHolder {
101
+ /** Display text naming the holder, for a denial's `detail`. Never parsed, never matched. */
102
+ readonly label: string;
103
+ /** The tenant this was resolved for, or null when the holder is not a tenant. `AuditEvent.tenant`. */
104
+ readonly tenant: string | null;
105
+ }
106
+ /**
107
+ * The default resolver when no provider is composed: hold nothing. Every gate denies, which is the
108
+ * fail-closed default the seam exists to guarantee. `createBackend` seeds it on every request, and a
109
+ * provider's middleware replaces it.
110
+ */
111
+ export declare const noEntitlementProvider: EntitlementResolver;
112
+ /**
113
+ * The audit action a denied entitlement gate records, through the core `emit()` seam. Part of the
114
+ * federated `domain/reason` taxonomy — core owns this one because core owns the gate. A blocked
115
+ * attempt is a first-class audit record (`outcome: "denied"`), not only a 403.
116
+ */
117
+ export declare const ENTITLEMENT_DENIED_ACTION = "entitlement/denied";
118
+ //# sourceMappingURL=entitlement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitlement.d.ts","sourceRoot":"","sources":["../../src/entitlement/entitlement.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA2BxB,eAAO,MAAM,cAAc,aAUxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW;;;;;iBAqBuF,CAAC;AAChH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAGpF;AAED,2GAA2G;AAC3G,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAMnG;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,wGAAwG;IACxG,IAAI,IAAI,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;IACxC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sGAAsG;IACtG,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,mBAGnC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,uBAAuB,CAAC"}