@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,56 @@
1
+ import type { Ed25519PublicJwk } from "../data/connection";
2
+ /**
3
+ * Mint a control-plane token — the management client's half of the seam.
4
+ *
5
+ * **This ships because the seam is MIT and is never gated by tier.** "You can build your own client
6
+ * against your own Worker" has to be true, and it is not true if the only way to produce a token the
7
+ * Worker accepts is to reverse-engineer the verifier. So the minting side lives here, beside the
8
+ * verifying side, and the two are tested against each other.
9
+ *
10
+ * It is also what the hosted dashboard would use, and what every test in this package uses to sign a
11
+ * real token rather than assert against a fixture.
12
+ *
13
+ * **Nothing here touches a private key store.** The caller supplies an already-imported `CryptoKey`.
14
+ * Core holds no key material, has no opinion about where a private key lives, and this file would be
15
+ * the wrong place to acquire one.
16
+ */
17
+ /** One token's worth of claims, minus the parts {@link mintControlPlaneToken} computes. */
18
+ export interface MintControlPlaneToken {
19
+ /** The signing key, imported for `Ed25519` with `["sign"]` usage. */
20
+ privateKey: CryptoKey;
21
+ /** The `kid` naming which registered public key verifies this token. */
22
+ keyId: string;
23
+ /** The management client's origin — must equal the `issuer` stored on the connection. */
24
+ issuer: string;
25
+ /** The connection this token addresses. */
26
+ connectionId: string;
27
+ /** Who, in the management client's own id space, is acting. */
28
+ subject: string;
29
+ /** The single operation this token authorizes. One call, one scope. */
30
+ scope: string;
31
+ /** The raw request body these claims are bound to. Empty for a request that carries none. */
32
+ body?: Uint8Array;
33
+ /** Seconds this token is good for. The Worker caps it, so asking for more only wastes the request. */
34
+ lifetimeSeconds?: number;
35
+ /** The clock, injected so a caller can mint a token at a chosen instant. */
36
+ now?: () => Date;
37
+ /** The token's unique id. Generated when absent; supply one only to test a replay. */
38
+ tokenId?: string;
39
+ }
40
+ /**
41
+ * Build and sign a compact JWS the seam will accept.
42
+ *
43
+ * The body digest is computed here rather than accepted from the caller, so a client cannot mint a
44
+ * token whose digest disagrees with the bytes it is about to send. `bodySha256` is null exactly when
45
+ * the body is empty, which is the same bijection the verifier enforces.
46
+ */
47
+ export declare function mintControlPlaneToken(input: MintControlPlaneToken): Promise<string>;
48
+ /**
49
+ * Export a generated key pair's public half as the JWK the adopter registers.
50
+ *
51
+ * `crv` and `kty` are re-asserted from our own constants rather than trusted from the export, so a
52
+ * runtime that spelled them differently produces a key this seam still recognizes — or fails here,
53
+ * where it is obvious, rather than at the first verification.
54
+ */
55
+ export declare function exportPublicJwk(publicKey: CryptoKey): Promise<Ed25519PublicJwk>;
56
+ //# sourceMappingURL=mint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mint.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/token/mint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D;;;;;;;;;;;;;;GAcG;AAEH,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,UAAU,EAAE,SAAS,CAAC;IACtB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,6FAA6F;IAC7F,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sGAAsG;IACtG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAOD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBzF;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAIrF"}
@@ -0,0 +1,55 @@
1
+ import { ControlPlaneClaims } from "./claims.js";
2
+ import { base64UrlEncode } from "./base64url.js";
3
+ import { sha256Base64Url } from "./digest.js";
4
+ //#region src/controlPlane/token/mint.ts
5
+ /** Base64url a UTF-8 JSON serialization — the encoding both JWS segments use. */
6
+ function encodeSegment(value) {
7
+ return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value)));
8
+ }
9
+ /**
10
+ * Build and sign a compact JWS the seam will accept.
11
+ *
12
+ * The body digest is computed here rather than accepted from the caller, so a client cannot mint a
13
+ * token whose digest disagrees with the bytes it is about to send. `bodySha256` is null exactly when
14
+ * the body is empty, which is the same bijection the verifier enforces.
15
+ */
16
+ async function mintControlPlaneToken(input) {
17
+ const body = input.body ?? /* @__PURE__ */ new Uint8Array(0);
18
+ const issuedAt = Math.floor((input.now?.() ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
19
+ const header = {
20
+ alg: "EdDSA",
21
+ typ: "JWT",
22
+ kid: input.keyId
23
+ };
24
+ const claims = ControlPlaneClaims.parse({
25
+ iss: input.issuer,
26
+ aud: input.connectionId,
27
+ sub: input.subject,
28
+ scope: input.scope,
29
+ jti: input.tokenId ?? crypto.randomUUID(),
30
+ iat: issuedAt,
31
+ exp: issuedAt + (input.lifetimeSeconds ?? 60),
32
+ bodySha256: body.byteLength === 0 ? null : await sha256Base64Url(body)
33
+ });
34
+ const signingInput = `${encodeSegment(header)}.${encodeSegment(claims)}`;
35
+ const signature = await crypto.subtle.sign("Ed25519", input.privateKey, new TextEncoder().encode(signingInput));
36
+ return `${signingInput}.${base64UrlEncode(new Uint8Array(signature))}`;
37
+ }
38
+ /**
39
+ * Export a generated key pair's public half as the JWK the adopter registers.
40
+ *
41
+ * `crv` and `kty` are re-asserted from our own constants rather than trusted from the export, so a
42
+ * runtime that spelled them differently produces a key this seam still recognizes — or fails here,
43
+ * where it is obvious, rather than at the first verification.
44
+ */
45
+ async function exportPublicJwk(publicKey) {
46
+ const exported = await crypto.subtle.exportKey("jwk", publicKey);
47
+ if (!exported.x) throw new TypeError("Ed25519 public key export carried no `x` coordinate.");
48
+ return {
49
+ kty: "OKP",
50
+ crv: "Ed25519",
51
+ x: exported.x
52
+ };
53
+ }
54
+ //#endregion
55
+ export { exportPublicJwk, mintControlPlaneToken };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The control-plane wire contract: the names both ends of the seam must spell identically, and the one
3
+ * rule for reading what they carry.
4
+ *
5
+ * **This module imports nothing, and that is its whole job.** Per `docs/CONTROL-PLANE.md` §4 a browser
6
+ * calls the adopter's Worker *directly* — the management client's server mints the token, the browser
7
+ * sends it — so a DOM-typed module has to name the request header, and has to read the version header
8
+ * off the response to notice a customer deploying mid-session. Reaching these constants through
9
+ * `http/verify` drags the verifier in with them: WebCrypto, the replay guard, the seam config. A
10
+ * program compiled against `lib.dom` then fails on `token/digest.ts` and `token/jws.ts`, where a
11
+ * `Uint8Array<ArrayBufferLike>` is not the `BufferSource` the DOM lib declares — the Workers and DOM
12
+ * type libraries meeting, which no amount of care at the call site resolves.
13
+ *
14
+ * The alternative was to let each client re-declare the strings. That is what the dashboard did, with
15
+ * a comment and an assertion holding the copy honest, and two copies of a wire constant is exactly the
16
+ * drift a constant exists to prevent. `wire.test.ts` asserts the module graph here stays empty, because
17
+ * the property is only true until someone adds the first convenient import.
18
+ *
19
+ * {@link workerBuildChanged} is here for the same reason one level up. The dashboard did not only copy
20
+ * the header name — it wrote its own rule for reading the value, "invalidate when the id differs", and
21
+ * that rule was wrong in a way a copied constant could never have caught. A rule both ends must agree on
22
+ * belongs beside the names both ends must spell.
23
+ */
24
+ /**
25
+ * The header a control-plane token is presented on.
26
+ *
27
+ * Not `Authorization`, deliberately: `@pithy-sh/auth` builds a Better Auth instance for any request
28
+ * carrying that header, so a management call would drag the whole auth stack in. See `http/verify.ts`.
29
+ */
30
+ export declare const CONTROL_PLANE_HEADER = "pithy-control-plane";
31
+ /**
32
+ * The response header carrying the build that answered a control-plane call.
33
+ *
34
+ * Set on every control-plane response — allowed and denied alike, and on every capability's admin
35
+ * routes rather than only the seam's own. A management client reads it to pin each recorded action to
36
+ * the exact build it hit, and to notice a version changing mid-session, which is the moment a rendered
37
+ * pane has quietly gone out of date. Absent where the `CF_VERSION_METADATA` binding is, which reads as
38
+ * "this Worker cannot say" rather than as a value to trust.
39
+ *
40
+ * **Carries the id and nothing else, permanently.** Its companion below is a second header rather than
41
+ * a second field in this one for one reason: a client already deployed compares this whole string. Fold
42
+ * anything into it and the day the adopter upgrades the kit reads as a version change that never
43
+ * happened — a false invalidation, which is the failure the pair exists to prevent, arriving from the
44
+ * fix for it.
45
+ */
46
+ export declare const CONTROL_PLANE_VERSION_HEADER = "pithy-worker-version";
47
+ /**
48
+ * The response header carrying **when the running version was uploaded** — ISO-8601, verbatim, off the
49
+ * same `CF_VERSION_METADATA` binding as the id.
50
+ *
51
+ * Read beside {@link CONTROL_PLANE_VERSION_HEADER}, never folded into it, so a client can compare two
52
+ * values separately. {@link workerBuildChanged} is the rule; do not hand-write one.
53
+ *
54
+ * **Cloudflare has two objects, and this is the first of them.** A *version* is an immutable upload of
55
+ * code and config — id, created timestamp, tag, fixed at upload and never again. A *deployment* points
56
+ * at one or more versions with traffic percentages, and is its own object with its own id and time. The
57
+ * `CF_VERSION_METADATA` binding reports the version, and **the runtime hands a Worker no binding for the
58
+ * deployment**. So this header is the upload moment, and it is fixed for as long as that version exists.
59
+ *
60
+ * That mechanism predicts the behavior rather than merely recording it: **a rollback creates a new
61
+ * deployment aimed at an existing version, so nothing here moves** — the binding is not stale and not
62
+ * ambiguous, the thing that changed was a different object. It answers the next question too. A traffic
63
+ * split, a gradual rollout, which deployment is serving: none of it is visible from inside a Worker.
64
+ *
65
+ * Measured on a real account, 2026-08-10, which is what made it a fact rather than a reading of the
66
+ * docs: version `A` uploaded at `22:28:56.762349Z`, `B` four seconds later, `wrangler rollback` to `A`
67
+ * at ~`22:29:20`. Reading at `22:31:37` returned `A`'s id **and `A`'s original timestamp**, unmoved,
68
+ * while wrangler's own output named the new deployment's version list. Two details from the same run
69
+ * that a client will meet:
70
+ *
71
+ * - **Every `wrangler deploy` mints a new version**, even for a one-character change — that is why `B`
72
+ * exists. An ordinary redeploy therefore always moves the id, and is never invisible.
73
+ * - **Propagation lags.** Twenty seconds after wrangler reported the rollback at 100%, the URL still
74
+ * answered from the older version. A client watching closely sees the pair flip and settle; that is
75
+ * the platform converging, not two deploys.
76
+ */
77
+ export declare const CONTROL_PLANE_VERSION_CREATED_HEADER = "pithy-worker-version-created";
78
+ /**
79
+ * The pair a client reads off one control-plane response: two `headers.get()` calls, each `string | null`.
80
+ */
81
+ export interface WorkerBuild {
82
+ /** {@link CONTROL_PLANE_VERSION_HEADER}, or `null` where the response carried none. */
83
+ version: string | null;
84
+ /** {@link CONTROL_PLANE_VERSION_CREATED_HEADER}, or `null` where the response carried none. */
85
+ createdAt: string | null;
86
+ }
87
+ /**
88
+ * Did the Worker answering now differ from the Worker that answered before?
89
+ *
90
+ * **The rule, total over the pair.** A client compares field by field and only where both sides carried
91
+ * a value; anything that differs is a change. Four states:
92
+ *
93
+ * 1. nothing differs — the same build, still answering. Say nothing.
94
+ * 2. `version` differs — a different build is live, and what is rendered came from one that no longer
95
+ * serves.
96
+ * 3. `version` the same, `createdAt` differs — **the same build was deployed again.** Same consequence.
97
+ * 4. either side silent on a field — that field says nothing.
98
+ *
99
+ * **State 3 is not reachable on today's platform, and it stays anyway.** A version's timestamp
100
+ * is fixed at upload and a rollback only makes a new deployment, so the pair cannot show a moved time
101
+ * against an unmoved id (see {@link CONTROL_PLANE_VERSION_CREATED_HEADER}). It is here because a total
102
+ * comparison over the pair is the correct shape, not because a rollback was expected to trip it — **a
103
+ * comparison that enumerates which fields are allowed to move is wrong the day the platform moves a
104
+ * different one**, and it is wrong silently, as "nothing changed". It costs one branch.
105
+ *
106
+ * Direction is *not* interpreted. A time that moved is a fact; "it moved backwards, therefore a
107
+ * rollback" is not, and would be wrong twice over — a rollback moves neither field, and propagation lag
108
+ * makes an older version answer for a while after a deploy.
109
+ *
110
+ * **What no state here reaches: a deployment.** Rollbacks, traffic splits, gradual rollouts — a Worker
111
+ * has no binding for the object that carries them, so this is a boundary of what the runtime exposes
112
+ * rather than a gap in the seam. A client that needs a deployment reads Cloudflare's deployments API.
113
+ *
114
+ * **Absence is never change**, in every direction: a Worker that declares no `version_metadata` sends
115
+ * neither header, a call that failed before its headers were read has none, and a client that has not
116
+ * looked yet holds nulls. A blank value counts as absent too — the seam never sends one, but a proxy in
117
+ * between can blank a header it does not understand, and an empty string differs from every real id. The
118
+ * failure this shape refuses is the one that costs an adopter trust: invalidating a rendered pane on a
119
+ * deploy that never happened.
120
+ */
121
+ export declare function workerBuildChanged(before: WorkerBuild, after: WorkerBuild): boolean;
122
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/controlPlane/wire.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,yBAAyB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,oCAAoC,iCAAiC,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uFAAuF;IACvF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAEnF"}
@@ -0,0 +1,123 @@
1
+ //#region src/controlPlane/wire.ts
2
+ /**
3
+ * The control-plane wire contract: the names both ends of the seam must spell identically, and the one
4
+ * rule for reading what they carry.
5
+ *
6
+ * **This module imports nothing, and that is its whole job.** Per `docs/CONTROL-PLANE.md` §4 a browser
7
+ * calls the adopter's Worker *directly* — the management client's server mints the token, the browser
8
+ * sends it — so a DOM-typed module has to name the request header, and has to read the version header
9
+ * off the response to notice a customer deploying mid-session. Reaching these constants through
10
+ * `http/verify` drags the verifier in with them: WebCrypto, the replay guard, the seam config. A
11
+ * program compiled against `lib.dom` then fails on `token/digest.ts` and `token/jws.ts`, where a
12
+ * `Uint8Array<ArrayBufferLike>` is not the `BufferSource` the DOM lib declares — the Workers and DOM
13
+ * type libraries meeting, which no amount of care at the call site resolves.
14
+ *
15
+ * The alternative was to let each client re-declare the strings. That is what the dashboard did, with
16
+ * a comment and an assertion holding the copy honest, and two copies of a wire constant is exactly the
17
+ * drift a constant exists to prevent. `wire.test.ts` asserts the module graph here stays empty, because
18
+ * the property is only true until someone adds the first convenient import.
19
+ *
20
+ * {@link workerBuildChanged} is here for the same reason one level up. The dashboard did not only copy
21
+ * the header name — it wrote its own rule for reading the value, "invalidate when the id differs", and
22
+ * that rule was wrong in a way a copied constant could never have caught. A rule both ends must agree on
23
+ * belongs beside the names both ends must spell.
24
+ */
25
+ /**
26
+ * The header a control-plane token is presented on.
27
+ *
28
+ * Not `Authorization`, deliberately: `@pithy-sh/auth` builds a Better Auth instance for any request
29
+ * carrying that header, so a management call would drag the whole auth stack in. See `http/verify.ts`.
30
+ */
31
+ const CONTROL_PLANE_HEADER = "pithy-control-plane";
32
+ /**
33
+ * The response header carrying the build that answered a control-plane call.
34
+ *
35
+ * Set on every control-plane response — allowed and denied alike, and on every capability's admin
36
+ * routes rather than only the seam's own. A management client reads it to pin each recorded action to
37
+ * the exact build it hit, and to notice a version changing mid-session, which is the moment a rendered
38
+ * pane has quietly gone out of date. Absent where the `CF_VERSION_METADATA` binding is, which reads as
39
+ * "this Worker cannot say" rather than as a value to trust.
40
+ *
41
+ * **Carries the id and nothing else, permanently.** Its companion below is a second header rather than
42
+ * a second field in this one for one reason: a client already deployed compares this whole string. Fold
43
+ * anything into it and the day the adopter upgrades the kit reads as a version change that never
44
+ * happened — a false invalidation, which is the failure the pair exists to prevent, arriving from the
45
+ * fix for it.
46
+ */
47
+ const CONTROL_PLANE_VERSION_HEADER = "pithy-worker-version";
48
+ /**
49
+ * The response header carrying **when the running version was uploaded** — ISO-8601, verbatim, off the
50
+ * same `CF_VERSION_METADATA` binding as the id.
51
+ *
52
+ * Read beside {@link CONTROL_PLANE_VERSION_HEADER}, never folded into it, so a client can compare two
53
+ * values separately. {@link workerBuildChanged} is the rule; do not hand-write one.
54
+ *
55
+ * **Cloudflare has two objects, and this is the first of them.** A *version* is an immutable upload of
56
+ * code and config — id, created timestamp, tag, fixed at upload and never again. A *deployment* points
57
+ * at one or more versions with traffic percentages, and is its own object with its own id and time. The
58
+ * `CF_VERSION_METADATA` binding reports the version, and **the runtime hands a Worker no binding for the
59
+ * deployment**. So this header is the upload moment, and it is fixed for as long as that version exists.
60
+ *
61
+ * That mechanism predicts the behavior rather than merely recording it: **a rollback creates a new
62
+ * deployment aimed at an existing version, so nothing here moves** — the binding is not stale and not
63
+ * ambiguous, the thing that changed was a different object. It answers the next question too. A traffic
64
+ * split, a gradual rollout, which deployment is serving: none of it is visible from inside a Worker.
65
+ *
66
+ * Measured on a real account, 2026-08-10, which is what made it a fact rather than a reading of the
67
+ * docs: version `A` uploaded at `22:28:56.762349Z`, `B` four seconds later, `wrangler rollback` to `A`
68
+ * at ~`22:29:20`. Reading at `22:31:37` returned `A`'s id **and `A`'s original timestamp**, unmoved,
69
+ * while wrangler's own output named the new deployment's version list. Two details from the same run
70
+ * that a client will meet:
71
+ *
72
+ * - **Every `wrangler deploy` mints a new version**, even for a one-character change — that is why `B`
73
+ * exists. An ordinary redeploy therefore always moves the id, and is never invisible.
74
+ * - **Propagation lags.** Twenty seconds after wrangler reported the rollback at 100%, the URL still
75
+ * answered from the older version. A client watching closely sees the pair flip and settle; that is
76
+ * the platform converging, not two deploys.
77
+ */
78
+ const CONTROL_PLANE_VERSION_CREATED_HEADER = "pithy-worker-version-created";
79
+ /**
80
+ * Did the Worker answering now differ from the Worker that answered before?
81
+ *
82
+ * **The rule, total over the pair.** A client compares field by field and only where both sides carried
83
+ * a value; anything that differs is a change. Four states:
84
+ *
85
+ * 1. nothing differs — the same build, still answering. Say nothing.
86
+ * 2. `version` differs — a different build is live, and what is rendered came from one that no longer
87
+ * serves.
88
+ * 3. `version` the same, `createdAt` differs — **the same build was deployed again.** Same consequence.
89
+ * 4. either side silent on a field — that field says nothing.
90
+ *
91
+ * **State 3 is not reachable on today's platform, and it stays anyway.** A version's timestamp
92
+ * is fixed at upload and a rollback only makes a new deployment, so the pair cannot show a moved time
93
+ * against an unmoved id (see {@link CONTROL_PLANE_VERSION_CREATED_HEADER}). It is here because a total
94
+ * comparison over the pair is the correct shape, not because a rollback was expected to trip it — **a
95
+ * comparison that enumerates which fields are allowed to move is wrong the day the platform moves a
96
+ * different one**, and it is wrong silently, as "nothing changed". It costs one branch.
97
+ *
98
+ * Direction is *not* interpreted. A time that moved is a fact; "it moved backwards, therefore a
99
+ * rollback" is not, and would be wrong twice over — a rollback moves neither field, and propagation lag
100
+ * makes an older version answer for a while after a deploy.
101
+ *
102
+ * **What no state here reaches: a deployment.** Rollbacks, traffic splits, gradual rollouts — a Worker
103
+ * has no binding for the object that carries them, so this is a boundary of what the runtime exposes
104
+ * rather than a gap in the seam. A client that needs a deployment reads Cloudflare's deployments API.
105
+ *
106
+ * **Absence is never change**, in every direction: a Worker that declares no `version_metadata` sends
107
+ * neither header, a call that failed before its headers were read has none, and a client that has not
108
+ * looked yet holds nulls. A blank value counts as absent too — the seam never sends one, but a proxy in
109
+ * between can blank a header it does not understand, and an empty string differs from every real id. The
110
+ * failure this shape refuses is the one that costs an adopter trust: invalidating a rendered pane on a
111
+ * deploy that never happened.
112
+ */
113
+ function workerBuildChanged(before, after) {
114
+ return differs(before.version, after.version) || differs(before.createdAt, after.createdAt);
115
+ }
116
+ /** One field of the pair: a difference only where both sides actually said something. */
117
+ function differs(before, after) {
118
+ if (typeof before !== "string" || before.trim() === "") return false;
119
+ if (typeof after !== "string" || after.trim() === "") return false;
120
+ return before !== after;
121
+ }
122
+ //#endregion
123
+ export { CONTROL_PLANE_HEADER, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, workerBuildChanged };
@@ -0,0 +1,58 @@
1
+ import { Hono } from "hono";
2
+ import type { Capability, MergedDatabases, MergedKvNamespaces, MergedWorkflowParams, PithyVars } from "./capability/capability";
3
+ import { type DbRegistry } from "./data/databases";
4
+ import { type KvRegistry } from "./kv/namespaces";
5
+ import type { Logger } from "./logger/logger";
6
+ import { type WorkflowDispatcher } from "./workflow/dispatch";
7
+ /** Inputs to {@link createBackend}: the capabilities to assemble, plus the app's own capability. */
8
+ export interface CreateBackendOptions<Caps extends readonly Capability[], App extends Capability> {
9
+ /** Library capabilities (core + each `@pithy-sh/*`), composed in order. */
10
+ capabilities: Caps;
11
+ /**
12
+ * The app itself, as a `Capability` — principle 4: the app is just another capability. Its
13
+ * databases, KV namespaces, middleware, routes, and bindings compose with the rest; routes last.
14
+ */
15
+ app?: App;
16
+ /**
17
+ * The base logger bound to `c.var.log`, set **once** here at assembly (the per-request `env`
18
+ * constraint is honored: correlation fields are bound per request, not at module load). Defaults to
19
+ * the CF-native Mode 2 worker logger (`info`, structured records to Workers Logs). Pass a configured
20
+ * one to change the level or attach a tail/Logpush transport.
21
+ */
22
+ logger?: Logger;
23
+ /**
24
+ * Fallback environment name (`dev` | `staging` | `production`) for the log correlation `env` field.
25
+ * The primary signal is the per-request `ENVIRONMENT` var (the convention `@pithy-sh/secrets` and
26
+ * `@pithy-sh/email` already stamp into each deployed Worker); this is only used where that var is
27
+ * absent — e.g. a non-provisioned local run. Falls through to `unknown` when neither is present.
28
+ */
29
+ env?: string;
30
+ }
31
+ /** The Hono env `createBackend` returns — `db`/`kv`/`workflows` typed precisely from the merged capabilities. */
32
+ type BackendEnv<Caps extends readonly Capability[]> = {
33
+ Bindings: Record<string, unknown>;
34
+ Variables: Omit<PithyVars, "db" | "kv" | "workflows"> & {
35
+ db: DbRegistry<MergedDatabases<Caps>>;
36
+ kv: KvRegistry<MergedKvNamespaces<Caps>>;
37
+ workflows: WorkflowDispatcher<MergedWorkflowParams<Caps>>;
38
+ };
39
+ };
40
+ /**
41
+ * Assemble capabilities into a deployable Hono app — a valid Worker `fetch` handler.
42
+ *
43
+ * Serves `GET /health`; merges every capability's `databases` and `kvNamespaces` into registries
44
+ * (tables/stores unioned per group); validates that every required binding is present; composes
45
+ * each capability's middleware; and mounts the capability (then app) routes. On every request
46
+ * `c.var.db` is the typed database registry — one `Kysely` per named database (`c.var.db.app`) —
47
+ * and `c.var.kv` the typed namespace registry — one `TypedKv` per store (`c.var.kv.cms.pages`).
48
+ * `PithyError`s become their declared HTTP status via {@link pithyErrorHandler}.
49
+ *
50
+ * Binding validation runs **once on the first request**, not at module load: in Workers `env` is
51
+ * per-request, so there is no env to check until a request arrives. The result is memoized.
52
+ *
53
+ * The return is typed precisely from the capabilities array — `createBackend({ capabilities }).get(
54
+ * "/x", (c) => c.var.db.app.selectFrom("…"))` gets autocomplete on every registered table.
55
+ */
56
+ export declare function createBackend<const Caps extends readonly Capability[], const App extends Capability = Capability<Record<never, never>, Record<never, never>>>(options: CreateBackendOptions<Caps, App>): Hono<BackendEnv<readonly [...Caps, App]>>;
57
+ export {};
58
+ //# sourceMappingURL=createBackend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createBackend.d.ts","sourceRoot":"","sources":["../src/createBackend.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EAEpB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAKjC,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMtF,OAAO,EAA8B,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAK9C,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGvF,oGAAoG;AACpG,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS,SAAS,UAAU,EAAE,EAAE,GAAG,SAAS,UAAU;IAC9F,2EAA2E;IAC3E,YAAY,EAAE,IAAI,CAAC;IACnB;;;OAGG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC;IACV;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,iHAAiH;AACjH,KAAK,UAAU,CAAC,IAAI,SAAS,SAAS,UAAU,EAAE,IAAI;IACpD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,WAAW,CAAC,GAAG;QACtD,EAAE,EAAE,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,EAAE,EAAE,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QACzC,SAAS,EAAE,kBAAkB,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;KAC3D,CAAC;CACH,CAAC;AAuCF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAC3B,KAAK,CAAC,IAAI,SAAS,SAAS,UAAU,EAAE,EACxC,KAAK,CAAC,GAAG,SAAS,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EACrF,OAAO,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAuKrF"}
@@ -0,0 +1,170 @@
1
+ import { noopEmit } from "./audit/recorder.js";
2
+ import { BindingSpec } from "./capability/bindings.js";
3
+ import { ValidationError } from "./error/pithyError.js";
4
+ import { recordComposition } from "./capability/composition.js";
5
+ import { validateBindings } from "./capability/validateBindings.js";
6
+ import { workerVersion } from "./worker/identity.js";
7
+ import { isControlPlaneCapability } from "./controlPlane/capability.js";
8
+ import { allowedOriginSet, corsSurface, registerControlPlaneCors } from "./controlPlane/http/cors.js";
9
+ import { buildDbRegistry, composeDatabases } from "./data/databases.js";
10
+ import { noEntitlementProvider } from "./entitlement/entitlement.js";
11
+ import { pithyErrorHandler } from "./error/http.js";
12
+ import { bakedTranslator } from "./i18n/translator.js";
13
+ import { catalogFor, composeMessages } from "./i18n/registry.js";
14
+ import { buildKvRegistry, composeKv } from "./kv/namespaces.js";
15
+ import { bindRequestContext, createWorkerLogger } from "./logger/worker.js";
16
+ import { HEALTH_PATH } from "./worker/health.js";
17
+ import { registeredWorkflowBinding } from "./workflow/bindings.js";
18
+ import { buildWorkflowDispatcher } from "./workflow/dispatch.js";
19
+ import { composeWorkflows } from "./workflow/register.js";
20
+ import { Hono } from "hono";
21
+ //#region src/createBackend.ts
22
+ /**
23
+ * Collapse duplicate bindings (same `type:name`) — capability and derived specs overlap. A binding
24
+ * required by **any** source stays required: a derived database/namespace binding (structurally
25
+ * required — the request context builds a handle on it) must not be masked by an author marking the
26
+ * same binding `optional`. So `optional` is the AND of every occurrence.
27
+ */
28
+ function dedupeBindings(specs) {
29
+ const byKey = /* @__PURE__ */ new Map();
30
+ for (const spec of specs) {
31
+ const key = `${spec.type}:${spec.name}`;
32
+ const existing = byKey.get(key);
33
+ if (!existing) byKey.set(key, { ...spec });
34
+ else if (!spec.optional) existing.optional = false;
35
+ }
36
+ return [...byKey.values()];
37
+ }
38
+ /**
39
+ * The deployed Worker version id, for the log record's `version` correlation field.
40
+ *
41
+ * Delegates to `workerVersion` rather than re-reading the binding: this used to be a private copy here,
42
+ * and a second reader of one binding is how the name and the declaration drift apart. `undefined` rather
43
+ * than `null` because the logger omits an undefined field.
44
+ */
45
+ function versionOf(env) {
46
+ return workerVersion(env) ?? void 0;
47
+ }
48
+ /**
49
+ * The environment name for log correlation, resolved from context: the per-request `ENVIRONMENT` var
50
+ * (the signal `@pithy-sh/secrets`/`@pithy-sh/email` stamp into each deployed Worker), else the explicit
51
+ * `createBackend({ env })` fallback, else `unknown`. No caller effort where `ENVIRONMENT` is present.
52
+ */
53
+ function envNameOf(env, fallback) {
54
+ return typeof env.ENVIRONMENT === "string" && env.ENVIRONMENT.length > 0 ? env.ENVIRONMENT : fallback ?? "unknown";
55
+ }
56
+ /**
57
+ * Assemble capabilities into a deployable Hono app — a valid Worker `fetch` handler.
58
+ *
59
+ * Serves `GET /health`; merges every capability's `databases` and `kvNamespaces` into registries
60
+ * (tables/stores unioned per group); validates that every required binding is present; composes
61
+ * each capability's middleware; and mounts the capability (then app) routes. On every request
62
+ * `c.var.db` is the typed database registry — one `Kysely` per named database (`c.var.db.app`) —
63
+ * and `c.var.kv` the typed namespace registry — one `TypedKv` per store (`c.var.kv.cms.pages`).
64
+ * `PithyError`s become their declared HTTP status via {@link pithyErrorHandler}.
65
+ *
66
+ * Binding validation runs **once on the first request**, not at module load: in Workers `env` is
67
+ * per-request, so there is no env to check until a request arrives. The result is memoized.
68
+ *
69
+ * The return is typed precisely from the capabilities array — `createBackend({ capabilities }).get(
70
+ * "/x", (c) => c.var.db.app.selectFrom("…"))` gets autocomplete on every registered table.
71
+ */
72
+ function createBackend(options) {
73
+ const all = options.app ? [...options.capabilities, options.app] : [...options.capabilities];
74
+ const present = new Set(all.map((cap) => cap.name));
75
+ for (const cap of all) for (const dep of cap.dependsOn ?? []) if (!present.has(dep)) throw new ValidationError({
76
+ message: `Capability "${cap.name}" requires the "${dep}" capability, which is not composed.`,
77
+ action: `Add ${dep}() to createBackend's capabilities (run \`pithy add ${dep}\`).`
78
+ });
79
+ for (const cap of all) cap.compose?.({ capabilities: all });
80
+ recordComposition(all);
81
+ const databases = composeDatabases(all);
82
+ const messages = composeMessages(all);
83
+ const defaultTranslator = bakedTranslator(catalogFor(messages, "en"));
84
+ const namespaces = composeKv(all);
85
+ const workflows = composeWorkflows(all);
86
+ const required = dedupeBindings([
87
+ ...all.flatMap((cap) => cap.requiredBindings),
88
+ ...Object.values(databases).map((db) => BindingSpec.parse({
89
+ type: "d1",
90
+ name: db.binding
91
+ })),
92
+ ...Object.values(namespaces).map((ns) => BindingSpec.parse({
93
+ type: "kv",
94
+ name: ns.binding
95
+ })),
96
+ ...Object.values(workflows).map((entry) => BindingSpec.parse(registeredWorkflowBinding(entry)))
97
+ ]);
98
+ const app = new Hono();
99
+ app.onError(pithyErrorHandler);
100
+ const baseLogger = options.logger ?? createWorkerLogger();
101
+ const controlPlane = all.find(isControlPlaneCapability);
102
+ if (controlPlane) {
103
+ const cors = controlPlane.controlPlaneConfig;
104
+ registerControlPlaneCors(app, corsSurface(all), allowedOriginSet(cors), cors.corsMaxAgeSeconds);
105
+ }
106
+ let validated = false;
107
+ app.use("*", async (c, next) => {
108
+ const env = c.env;
109
+ if (!validated) {
110
+ validateBindings(env, required);
111
+ validated = true;
112
+ }
113
+ if (c.get("auth") === void 0) c.set("auth", null);
114
+ if (c.get("locale") === void 0) c.set("locale", null);
115
+ if (c.get("t") === void 0) c.set("t", defaultTranslator);
116
+ if (c.get("controlPlane") === void 0) c.set("controlPlane", null);
117
+ if (c.get("controlPlaneVerifier") === void 0) c.set("controlPlaneVerifier", null);
118
+ if (c.get("sameOrigin") === void 0) c.set("sameOrigin", null);
119
+ if (c.get("emit") === void 0) c.set("emit", noopEmit);
120
+ if (c.get("entitlements") === void 0) c.set("entitlements", noEntitlementProvider);
121
+ if (c.get("log") === void 0) c.set("log", bindRequestContext(baseLogger, {
122
+ request: c.req.header("cf-ray") ?? crypto.randomUUID(),
123
+ method: c.req.method,
124
+ path: c.req.path,
125
+ env: envNameOf(env, options.env),
126
+ version: versionOf(env)
127
+ }));
128
+ if (c.get("db") === void 0) c.set("db", buildDbRegistry(env, databases));
129
+ if (c.get("kv") === void 0) c.set("kv", buildKvRegistry(env, namespaces, c.var.log));
130
+ if (c.get("workflows") === void 0) c.set("workflows", buildWorkflowDispatcher(env, workflows, c.var.log));
131
+ const start = Date.now();
132
+ await next();
133
+ c.var.log.info("request", {
134
+ status: c.res.status,
135
+ elapsed: Date.now() - start
136
+ });
137
+ });
138
+ /**
139
+ * `GET {@link HEALTH_PATH}` — liveness, and **which build is answering**.
140
+ *
141
+ * The path is `worker/health.ts`'s, not a literal here. Three other sites need the same string — the
142
+ * CLI's route allowlist, `pithy deploy`'s post-deploy probe, and the bare home screen a no-auth
143
+ * scaffold seeds — and that last one is in somebody else's repository, rendering nothing but this
144
+ * request's answer (#400).
145
+ *
146
+ * The version is what turns `pithy deploy`'s post-deploy check from a liveness probe into an
147
+ * assertion. `status: "ok"` at the declared domain proves *a* Worker is there; it does not prove it is
148
+ * the one just shipped, and the old version answering happily is exactly the failure worth catching —
149
+ * a deploy that landed somewhere else while the declared domain kept serving what was already on it.
150
+ * With the id here, `deploy` compares what it shipped against what replies.
151
+ *
152
+ * **Public, deliberately.** A Cloudflare version id is an opaque UUID carrying no version semantics
153
+ * and no exploitable detail, and most platforms expose a build identifier. The alternative — reporting
154
+ * it only through the authenticated control-plane manifest — is better for privacy and useless for
155
+ * deploy verification, since `deploy` holds no control-plane credential.
156
+ *
157
+ * `null` where the binding is absent, which is honest rather than misleading: a project scaffolded
158
+ * before `version_metadata` was declared reports "I cannot tell you", and `deploy` reports the check
159
+ * as inconclusive instead of failing it.
160
+ */
161
+ app.get(HEALTH_PATH, (c) => c.json({
162
+ status: "ok",
163
+ version: workerVersion(c.env)
164
+ }));
165
+ for (const cap of all) for (const middleware of cap.middleware ?? []) middleware(app);
166
+ for (const cap of all) cap.routes?.(app);
167
+ return app;
168
+ }
169
+ //#endregion
170
+ export { createBackend };