@pithy-sh/core 0.1.1 → 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,55 @@
1
+ import "./locale.js";
2
+ //#region src/i18n/acceptLanguage.ts
3
+ /**
4
+ * The most `Accept-Language` entries this parser will read from one header.
5
+ *
6
+ * A browser sends a handful. A caller that sends ten thousand is not negotiating, and the matcher
7
+ * walks every entry against every supported locale — so the bound is on the input, where it costs
8
+ * nothing, rather than on the loop, where it would have to be argued about at each call site.
9
+ */
10
+ const MAX_ACCEPT_LANGUAGE_ENTRIES = 32;
11
+ /** The most characters this parser will read from one `Accept-Language` header, for the same reason. */
12
+ const MAX_ACCEPT_LANGUAGE_LENGTH = 4096;
13
+ /** Whether a q-weight parameter parses to a usable number. `q=` and `q=abc` are neither, and drop out. */
14
+ function quality(parameter) {
15
+ const match = /^q=(\d(?:\.\d{0,3})?)$/.exec(parameter);
16
+ if (!match?.[1]) return null;
17
+ const value = Number.parseFloat(match[1]);
18
+ return Number.isFinite(value) && value >= 0 && value <= 1 ? value : null;
19
+ }
20
+ /**
21
+ * An `Accept-Language` header as q-weighted ranges, most-wanted first.
22
+ *
23
+ * **Tokenize, strip q-weights, then validate — in that order, and all of it before anything reaches
24
+ * `Intl`.** The header is caller-supplied and its real-world contents include `*`, `en_US`, empty
25
+ * tokens, and tags still carrying `;q=0.9`; `new Intl.Locale()` throws a `RangeError` on every one of
26
+ * them. A header that cannot be read is an empty list, never a throw, so the resolver chain falls
27
+ * through to its next link instead of 500-ing.
28
+ *
29
+ * The **full** weighted list is returned, not the first entry: `pt-PT;q=1.0, es;q=0.8, en;q=0.5` from a
30
+ * reader who has no Portuguese is a request for Spanish, and reading only the head answers English.
31
+ * Equal weights keep header order, which is what a browser means by listing them that way.
32
+ */
33
+ function parseAcceptLanguage(header) {
34
+ if (!header || header.length > MAX_ACCEPT_LANGUAGE_LENGTH) return [];
35
+ const ranges = [];
36
+ for (const entry of header.split(",")) {
37
+ if (ranges.length >= MAX_ACCEPT_LANGUAGE_ENTRIES) break;
38
+ const [rawRange, ...parameters] = entry.split(";");
39
+ const range = rawRange?.trim().toLowerCase() ?? "";
40
+ if (range !== "*" && !/^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/.test(range)) continue;
41
+ if (range.length > 64) continue;
42
+ let weight = 1;
43
+ for (const parameter of parameters) {
44
+ const parsed = quality(parameter.trim().toLowerCase());
45
+ if (parsed !== null) weight = parsed;
46
+ }
47
+ if (weight > 0) ranges.push({
48
+ range,
49
+ quality: weight
50
+ });
51
+ }
52
+ return ranges.sort((left, right) => right.quality - left.quality);
53
+ }
54
+ //#endregion
55
+ export { parseAcceptLanguage };
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ /** A catalog key — `<domain>/<path>`, where `<domain>` is the contributing capability's name. */
3
+ export declare const MessageKey: z.ZodString;
4
+ export type MessageKey = z.infer<typeof MessageKey>;
5
+ /** The values a message may interpolate. Scalars only — a catalog renders text, never a structure. */
6
+ export declare const MessageParams: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
7
+ export type MessageParams = z.infer<typeof MessageParams>;
8
+ /** One locale's words: key → message. */
9
+ export declare const MessageCatalog: z.ZodRecord<z.ZodString, z.ZodString>;
10
+ export type MessageCatalog = z.infer<typeof MessageCatalog>;
11
+ /** A set of catalogs, keyed by locale — what a capability contributes and what an adopter overrides with. */
12
+ export declare const LocaleCatalogs: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>;
13
+ export type LocaleCatalogs = z.infer<typeof LocaleCatalogs>;
14
+ /** The domain of a catalog key — everything before the `/`. */
15
+ export declare function messageDomain(key: string): string;
16
+ /**
17
+ * `template` with every `{placeholder}` replaced by its parameter.
18
+ *
19
+ * A placeholder with no parameter is **left as written**, not blanked. A missing value is a bug in the
20
+ * call site or a typo in the catalog, and `Renews {date}.` on the screen says which; `Renews .` says
21
+ * nothing and reads like finished copy.
22
+ *
23
+ * Substitution is textual and performs no escaping, because the two consumers escape differently and
24
+ * both do it better than this could: React escapes what it renders, and the email engine precompiles
25
+ * `subject` and `text` with `noEscape` deliberately. A catalog value reaching an unescaped surface is
26
+ * therefore a fact about that surface — stated in `docs/I18N.md`, and the reason kit catalogs carry no
27
+ * markup.
28
+ */
29
+ export declare function interpolate(template: string, params?: MessageParams): string;
30
+ /**
31
+ * The first layer that has `key`, or `null` when none does.
32
+ *
33
+ * **Per key, never per catalog.** An adopter who translates one sentence passes one entry, and every
34
+ * key they did not mention keeps flowing from the package — which is what makes an override a merge
35
+ * rather than a fork. Layer order is the caller's: `@pithy-sh/i18n` walks adopter-locale, then
36
+ * adopter-default, then kit-locale, then kit-default.
37
+ */
38
+ export declare function lookupMessage(layers: readonly (MessageCatalog | undefined)[], key: string): string | null;
39
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/i18n/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA2BxB,iGAAiG;AACjG,eAAO,MAAM,UAAU,aAI+D,CAAC;AACvF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,sGAAsG;AACtG,eAAO,MAAM,aAAa,yFAO8C,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,yCAAyC;AACzC,eAAO,MAAM,cAAc,uCAEsC,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,6GAA6G;AAC7G,eAAO,MAAM,cAAc,iEAEiF,CAAC;AAC7G,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAW5E;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,cAAc,GAAG,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWzG"}
@@ -0,0 +1,57 @@
1
+ import { Locale } from "./locale.js";
2
+ import { z } from "zod";
3
+ /** A catalog key — `<domain>/<path>`, where `<domain>` is the contributing capability's name. */
4
+ const MessageKey = z.string().max(129).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*\/[a-z0-9]+(?:_[a-z0-9]+)*(?:\.[a-z0-9]+(?:_[a-z0-9]+)*)*$/, { message: "A message key is `<domain>/<path>`, where the domain is a capability name." }).describe("A catalog key, `<domain>/<path>` — for an error, the error code itself.");
5
+ /** The values a message may interpolate. Scalars only — a catalog renders text, never a structure. */
6
+ const MessageParams = z.record(z.string().describe("The placeholder name, as written between braces in the message."), z.union([
7
+ z.string(),
8
+ z.number(),
9
+ z.boolean()
10
+ ]).describe("The value substituted for that placeholder. Scalar, so a message stays a sentence.")).describe("Values a message interpolates, keyed by placeholder name.");
11
+ /** One locale's words: key → message. */
12
+ const MessageCatalog = z.record(MessageKey.describe("The key this message answers."), z.string().describe("The message, in this locale.")).describe("One locale's messages, keyed by `<domain>/<path>`.");
13
+ /** A set of catalogs, keyed by locale — what a capability contributes and what an adopter overrides with. */
14
+ const LocaleCatalogs = z.record(Locale.describe("The locale these messages are written in."), MessageCatalog).describe("Catalogs keyed by locale — a capability's `messages` contribution, or an adopter's overrides.");
15
+ /** The domain of a catalog key — everything before the `/`. */
16
+ function messageDomain(key) {
17
+ const cut = key.indexOf("/");
18
+ return cut < 0 ? key : key.slice(0, cut);
19
+ }
20
+ /**
21
+ * `template` with every `{placeholder}` replaced by its parameter.
22
+ *
23
+ * A placeholder with no parameter is **left as written**, not blanked. A missing value is a bug in the
24
+ * call site or a typo in the catalog, and `Renews {date}.` on the screen says which; `Renews .` says
25
+ * nothing and reads like finished copy.
26
+ *
27
+ * Substitution is textual and performs no escaping, because the two consumers escape differently and
28
+ * both do it better than this could: React escapes what it renders, and the email engine precompiles
29
+ * `subject` and `text` with `noEscape` deliberately. A catalog value reaching an unescaped surface is
30
+ * therefore a fact about that surface — stated in `docs/I18N.md`, and the reason kit catalogs carry no
31
+ * markup.
32
+ */
33
+ function interpolate(template, params) {
34
+ if (!params) return template;
35
+ return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (whole, name) => {
36
+ if (!Object.hasOwn(params, name)) return whole;
37
+ const value = params[name];
38
+ return value === void 0 ? whole : String(value);
39
+ });
40
+ }
41
+ /**
42
+ * The first layer that has `key`, or `null` when none does.
43
+ *
44
+ * **Per key, never per catalog.** An adopter who translates one sentence passes one entry, and every
45
+ * key they did not mention keeps flowing from the package — which is what makes an override a merge
46
+ * rather than a fork. Layer order is the caller's: `@pithy-sh/i18n` walks adopter-locale, then
47
+ * adopter-default, then kit-locale, then kit-default.
48
+ */
49
+ function lookupMessage(layers, key) {
50
+ for (const layer of layers) if (layer && Object.hasOwn(layer, key)) {
51
+ const message = layer[key];
52
+ if (message !== void 0) return message;
53
+ }
54
+ return null;
55
+ }
56
+ //#endregion
57
+ export { LocaleCatalogs, MessageCatalog, MessageKey, MessageParams, interpolate, lookupMessage, messageDomain };
@@ -0,0 +1,74 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The longest language tag this kit accepts. RFC 5646's own grammar allows far more, but a tag that
4
+ * long is not a locale anyone serves — it is a header a caller filled with something else. The bound
5
+ * exists so a hostile `Accept-Language` cannot make the matcher walk a megabyte per request.
6
+ */
7
+ export declare const MAX_LOCALE_TAG_LENGTH = 64;
8
+ /**
9
+ * A BCP-47 language tag — the identity of a locale everywhere in the kit.
10
+ *
11
+ * One string, two jobs, and only one of them falls back: the tag that picks a **catalog** is the words
12
+ * we have written, and the tag that drives **formatting** is whatever the reader asked for. See
13
+ * {@link LocaleContext} for why collapsing them is the bug where an Argentine reads Spanish and sees
14
+ * `1,234.56`.
15
+ */
16
+ export declare const Locale: z.ZodString;
17
+ export type Locale = z.infer<typeof Locale>;
18
+ /** Which way a locale's script runs. Drives the `dir` attribute on the document and the email shell. */
19
+ export declare const TextDirection: z.ZodEnum<{
20
+ ltr: "ltr";
21
+ rtl: "rtl";
22
+ }>;
23
+ export type TextDirection = z.infer<typeof TextDirection>;
24
+ /**
25
+ * The per-request locale seam, published on `c.var.locale` by `@pithy-sh/i18n` and `null` in a Worker
26
+ * that does not compose it. Core defines the shape; one capability populates it — the same split
27
+ * `AuthContext` uses, and for the same reason: `@pithy-sh/email` and the error module both need a
28
+ * locale and neither may import an i18n capability.
29
+ */
30
+ export declare const LocaleContext: z.ZodObject<{
31
+ catalogLocale: z.ZodString;
32
+ formattingLocale: z.ZodString;
33
+ direction: z.ZodEnum<{
34
+ ltr: "ltr";
35
+ rtl: "rtl";
36
+ }>;
37
+ }, z.core.$strip>;
38
+ export type LocaleContext = z.infer<typeof LocaleContext>;
39
+ /**
40
+ * `tag` as an `Intl.Locale`, or `null` when it is not one.
41
+ *
42
+ * **Every construction on header- or adopter-supplied input goes through this.** `new Intl.Locale()`
43
+ * throws `RangeError` on `*`, on `en_US`, on the empty string, and on any token still carrying
44
+ * `;q=0.9` — so a malformed `Accept-Language` is an uncaught 500 rather than a fallback, unless the
45
+ * throw is caught somewhere. It is caught here, once, and nowhere else.
46
+ */
47
+ export declare function parseLocale(tag: string): Intl.Locale | null;
48
+ /**
49
+ * The part of `tag` that may be handed to `Intl` as a **formatting** locale, or `null` when it is not a
50
+ * tag at all: language, script, region and variants, with every extension subtag stripped.
51
+ *
52
+ * **A `-u-` extension is not a locale, it is an instruction**, and the range a formatting locale is
53
+ * built from is caller-supplied. `?lang=en-u-nu-hanidec` truncates to `en`, matches a project that
54
+ * ships English, and — kept whole — reaches `Intl.NumberFormat` verbatim, where it renders `1,234` as
55
+ * `一,二三四`. `-u-ca-islamic` does the same to every date on the page. Nothing is disclosed and nothing
56
+ * persists, so it is spoofing rather than a breach; it is also a link somebody can send, and the whole
57
+ * point of resolving against `supportedLocales` is that a reader cannot pick a rendering the project
58
+ * did not offer.
59
+ *
60
+ * `baseName` is exactly the right cut, and is why this is a helper rather than a regex: `es-AR`,
61
+ * `zh-Hant-TW`, `es-419` and `de-DE-1996` all survive whole, because none of them is an extension.
62
+ */
63
+ export declare function formattingLocaleOf(tag: string): string | null;
64
+ /** Whether `tag` is a language tag `Intl` will accept — the predicate half of {@link parseLocale}. */
65
+ export declare function isLocale(tag: string): tag is Locale;
66
+ /**
67
+ * Which way `tag`'s script runs, feature-detected across both spellings and falling back to a small
68
+ * right-to-left language set when a runtime exposes neither.
69
+ *
70
+ * Total: an unparseable tag is `ltr`, because a document still needs a `dir` and guessing the other way
71
+ * mirrors a page nobody asked to mirror.
72
+ */
73
+ export declare function localeDirection(tag: string): TextDirection;
74
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/i18n/locale.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAaxC;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,aAoBkF,CAAC;AACtG,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE5C,wGAAwG;AACxG,eAAO,MAAM,aAAa;;;EAEyD,CAAC;AACpF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;iBAM8E,CAAC;AACzG,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAO3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAED,sGAAsG;AACtG,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,MAAM,CAEnD;AAuBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAQ1D"}
@@ -0,0 +1,109 @@
1
+ import { z } from "zod";
2
+ //#region src/i18n/locale.ts
3
+ /**
4
+ * The longest language tag this kit accepts. RFC 5646's own grammar allows far more, but a tag that
5
+ * long is not a locale anyone serves — it is a header a caller filled with something else. The bound
6
+ * exists so a hostile `Accept-Language` cannot make the matcher walk a megabyte per request.
7
+ */
8
+ const MAX_LOCALE_TAG_LENGTH = 64;
9
+ /**
10
+ * The shape of a well-formed language tag, checked before `Intl.Locale` ever sees it.
11
+ *
12
+ * Deliberately looser than RFC 5646 and deliberately stricter than "any string". Looser, because the
13
+ * authority on whether `es-419` or `zh-Hans-CN` is meaningful is ICU, not a regular expression we would
14
+ * have to keep in step with it. Stricter, because {@link https://tc39.es/ecma402 `Intl.Locale`} throws a
15
+ * `RangeError` rather than returning a signal, and the four things it throws on — `*`, `en_US`, the empty
16
+ * string, and a token still carrying `;q=0.9` — all appear in real `Accept-Language` headers.
17
+ */
18
+ const LANGUAGE_TAG = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
19
+ /**
20
+ * A BCP-47 language tag — the identity of a locale everywhere in the kit.
21
+ *
22
+ * One string, two jobs, and only one of them falls back: the tag that picks a **catalog** is the words
23
+ * we have written, and the tag that drives **formatting** is whatever the reader asked for. See
24
+ * {@link LocaleContext} for why collapsing them is the bug where an Argentine reads Spanish and sees
25
+ * `1,234.56`.
26
+ */
27
+ const Locale = z.string().min(2).max(64).regex(LANGUAGE_TAG, { message: "A locale is a BCP-47 tag like `en`, `es`, or `es-AR`." }).refine((tag) => parseLocale(tag) !== null, { message: "That is not a language tag `Intl` accepts. `en-x` and `en-a-bbb-a-ccc` are the usual shapes that miss." }).describe("A BCP-47 language tag (`en`, `es`, `es-AR`) — the identity of a locale across the kit.");
28
+ /** Which way a locale's script runs. Drives the `dir` attribute on the document and the email shell. */
29
+ const TextDirection = z.enum(["ltr", "rtl"]).describe("Which way a locale's script runs — the value of the `dir` attribute.");
30
+ /**
31
+ * The per-request locale seam, published on `c.var.locale` by `@pithy-sh/i18n` and `null` in a Worker
32
+ * that does not compose it. Core defines the shape; one capability populates it — the same split
33
+ * `AuthContext` uses, and for the same reason: `@pithy-sh/email` and the error module both need a
34
+ * locale and neither may import an i18n capability.
35
+ */
36
+ const LocaleContext = z.object({
37
+ catalogLocale: Locale.describe("The locale whose catalog answers `t()` — the words the kit actually has."),
38
+ formattingLocale: Locale.describe("The locale handed to `Intl` — what the reader asked for, region and all."),
39
+ direction: TextDirection.describe("The catalog locale's text direction, for `lang`/`dir` on the document.")
40
+ }).describe("Per-request resolved locale; the seam capabilities read instead of negotiating their own.");
41
+ /**
42
+ * `tag` as an `Intl.Locale`, or `null` when it is not one.
43
+ *
44
+ * **Every construction on header- or adopter-supplied input goes through this.** `new Intl.Locale()`
45
+ * throws `RangeError` on `*`, on `en_US`, on the empty string, and on any token still carrying
46
+ * `;q=0.9` — so a malformed `Accept-Language` is an uncaught 500 rather than a fallback, unless the
47
+ * throw is caught somewhere. It is caught here, once, and nowhere else.
48
+ */
49
+ function parseLocale(tag) {
50
+ if (!LANGUAGE_TAG.test(tag) || tag.length > 64) return null;
51
+ try {
52
+ return new Intl.Locale(tag);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * The part of `tag` that may be handed to `Intl` as a **formatting** locale, or `null` when it is not a
59
+ * tag at all: language, script, region and variants, with every extension subtag stripped.
60
+ *
61
+ * **A `-u-` extension is not a locale, it is an instruction**, and the range a formatting locale is
62
+ * built from is caller-supplied. `?lang=en-u-nu-hanidec` truncates to `en`, matches a project that
63
+ * ships English, and — kept whole — reaches `Intl.NumberFormat` verbatim, where it renders `1,234` as
64
+ * `一,二三四`. `-u-ca-islamic` does the same to every date on the page. Nothing is disclosed and nothing
65
+ * persists, so it is spoofing rather than a breach; it is also a link somebody can send, and the whole
66
+ * point of resolving against `supportedLocales` is that a reader cannot pick a rendering the project
67
+ * did not offer.
68
+ *
69
+ * `baseName` is exactly the right cut, and is why this is a helper rather than a regex: `es-AR`,
70
+ * `zh-Hant-TW`, `es-419` and `de-DE-1996` all survive whole, because none of them is an extension.
71
+ */
72
+ function formattingLocaleOf(tag) {
73
+ return parseLocale(tag)?.baseName ?? null;
74
+ }
75
+ /** Whether `tag` is a language tag `Intl` will accept — the predicate half of {@link parseLocale}. */
76
+ function isLocale(tag) {
77
+ return parseLocale(tag) !== null;
78
+ }
79
+ /**
80
+ * The languages written right-to-left, for the runtimes that expose neither text-info accessor.
81
+ *
82
+ * A last resort, not the answer: `getTextInfo()` knows about scripts this list cannot name, and it is
83
+ * asked first. The five here are the ones a fallback has to get right.
84
+ */
85
+ const RTL_LANGUAGES = /* @__PURE__ */ new Set([
86
+ "ar",
87
+ "he",
88
+ "fa",
89
+ "ur",
90
+ "ps"
91
+ ]);
92
+ /**
93
+ * Which way `tag`'s script runs, feature-detected across both spellings and falling back to a small
94
+ * right-to-left language set when a runtime exposes neither.
95
+ *
96
+ * Total: an unparseable tag is `ltr`, because a document still needs a `dir` and guessing the other way
97
+ * mirrors a page nobody asked to mirror.
98
+ */
99
+ function localeDirection(tag) {
100
+ const locale = parseLocale(tag);
101
+ if (!locale) return "ltr";
102
+ const withTextInfo = locale;
103
+ const info = typeof withTextInfo.getTextInfo === "function" ? withTextInfo.getTextInfo() : withTextInfo.textInfo;
104
+ if (info?.direction === "rtl") return "rtl";
105
+ if (info?.direction === "ltr") return "ltr";
106
+ return RTL_LANGUAGES.has(locale.language) ? "rtl" : "ltr";
107
+ }
108
+ //#endregion
109
+ export { Locale, LocaleContext, MAX_LOCALE_TAG_LENGTH, TextDirection, formattingLocaleOf, isLocale, localeDirection, parseLocale };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The locale tag this source declares in its head, or `null` if it declares none.
3
+ *
4
+ * `null` is the ordinary answer: almost every file in a tree declares nothing, and a file that does is saying
5
+ * something about its own values, not asking to be skipped. See this module's docblock for what a caller owes
6
+ * the answer — in particular that `en` comes back like any other tag and exempts nothing.
7
+ */
8
+ export declare function localeDeclared(source: string): string | null;
9
+ /**
10
+ * Every span on one line whose quoted content is a value rather than prose — as `[open, close)` offsets.
11
+ *
12
+ * `open` is the first character inside the quote and `close` is the closing quote, so a match at or after
13
+ * `open` and ending at or before `close` sits inside a value. Left to right, in whichever of the three quote
14
+ * characters opened it.
15
+ *
16
+ * By default a run holding whitespace is a sentence, not a value, and **a span that turns out to hold one is
17
+ * passed over rather than skipped**: the scan keeps reading inside it, which is how a backticked token inside
18
+ * a prose string literal still counts — this repository names wire values that way in a `.describe()`.
19
+ *
20
+ * `everyQuoted` widens that to include the sentences, for one case and one only: a line in a file that has
21
+ * declared itself written in another language, where the quoted runs are the translation. It is the mechanism
22
+ * the marker drives, and it is why the marker exempts a file's values without exempting the file.
23
+ *
24
+ * A run is one line's worth. A quote that never closes on its line opens no value, so the inner lines of a
25
+ * multi-line template literal are read as prose — deliberately, since that is where a long English argument
26
+ * would otherwise hide.
27
+ */
28
+ export declare function valueSpans(line: string, everyQuoted?: boolean): [number, number][];
29
+ //# sourceMappingURL=localeMarker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localeMarker.d.ts","sourceRoot":"","sources":["../../src/i18n/localeMarker.ts"],"names":[],"mappings":"AAmEA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAchF"}
@@ -0,0 +1,112 @@
1
+ //#region src/i18n/localeMarker.ts
2
+ /**
3
+ * Reading the locale a source file declares in its head — the reader for the marker `docs/I18N.md` publishes.
4
+ *
5
+ * A prose census reads every committed file as text and refuses the spellings a project does not write. This
6
+ * repository runs one (`packages/cli/src/ci/americanEnglish.test.ts`: the words this project writes are
7
+ * American), and any project that ships translated copy eventually needs the same thing, because a catalog
8
+ * written in Spanish is prose that is *supposed* not to be English. A path list would answer that and is
9
+ * exactly what a census must not grow — a list is right on the day it is written, wrong afterwards, and the
10
+ * way it goes wrong is that the gate keeps passing. So the file says what it is instead, in one marker, in
11
+ * its head, where a reader meets it: the **locale** it is written in, and that the copy is an **unreviewed
12
+ * first pass**. Both facts are load-bearing and neither is checkable from the outside.
13
+ *
14
+ * ## The half that is easy to get backwards
15
+ *
16
+ * **The marker exempts the file's quoted values, not the file.** A catalog is not written in Spanish — its
17
+ * *values* are. Everything around them is a docblock, an import and an identifier: the project's own English
18
+ * prose, and exactly as subject to the rule as any other file's. So a declaring file widens what counts as a
19
+ * value — every quoted run, whole sentences included, rather than only the whitespace-free tokens exempt
20
+ * everywhere else — and every character outside those quotes is censused as it would be anywhere.
21
+ *
22
+ * Reading the marker as *skip this file* is the port that goes wrong, and it goes wrong quietly. It made this
23
+ * repository's own `es/errors.ts` a place where a twenty-seven-line English argument went unread, which is a
24
+ * gate that cannot fail on most of what it is looking at, and it costs an adopter the docblocks of every
25
+ * translated catalog they ever add.
26
+ *
27
+ * **`en` and `en-*` are a declaration and never an exemption**, which is why {@link localeDeclared} hands back
28
+ * the tag rather than a yes or a no. The words a project writes *in English* are the ones a census is about,
29
+ * and a marker cannot become a way to opt English prose out of it. The caller decides what a tag means;
30
+ * treating `en` as translated is the same mistake as treating `es` as skippable.
31
+ *
32
+ * ## Why it lives in `core/src/i18n`
33
+ *
34
+ * The folder is the import path, so it is named for the caller. The marker is a locale declaration — the same
35
+ * thing `./locale` and `./catalog` are about, one artifact earlier — and an adopter reaching for it is asking
36
+ * a question about translated copy, not about linting. It is deliberately not in the CLI: the CLI is a
37
+ * development tool an adopter's own test suite has no reason to depend on, and this is the kit's precedent for
38
+ * a pure textual scanner that a repository's tests want back (`../entitlement/gateScan`, which exports
39
+ * `withoutComments` for the same reason). No `node:` import, no filesystem: the caller brings the text,
40
+ * whether from a walked directory or from `import.meta.glob(…, "?raw")` inside a Workers-typed program.
41
+ *
42
+ * The census that owns the rest of the rule stays where it is. What ships here is the pair a port gets wrong.
43
+ */
44
+ /**
45
+ * What a file written in a language other than English declares in its head.
46
+ *
47
+ * The exact spelling is published in `docs/I18N.md` — `LOCALE <tag>`, an em dash, and the unreviewed-first-pass
48
+ * sentence — and `packages/cli/src/ci/americanEnglish.test.ts` reads the line out of that document and holds it
49
+ * to this pattern, so an adopter copying the published marker cannot copy one that matches nothing. The tag is
50
+ * captured, never merely detected, because the caller's decision turns on which language it names.
51
+ */
52
+ const LOCALE_DECLARED = /\bLOCALE ([a-z]{2,8}(?:-[A-Za-z0-9]{1,8})*) — an unreviewed first pass\./;
53
+ /**
54
+ * How far into a file the marker has to be, so a file mentioning it in prose is still read.
55
+ *
56
+ * A documented number rather than a tuning knob: `docs/I18N.md` publishes this window to adopters, and
57
+ * `packages/cli/src/ci/americanEnglish.test.ts` holds the count that document states to where this reader
58
+ * actually stops — so moving the number here fails the build until the document moves with it. This module's
59
+ * own test pins both sides of the boundary. It is an implementation detail of {@link localeDeclared} and is
60
+ * not exported — a caller hands over a whole source and gets an answer.
61
+ */
62
+ const HEAD_LINES = 25;
63
+ /**
64
+ * The locale tag this source declares in its head, or `null` if it declares none.
65
+ *
66
+ * `null` is the ordinary answer: almost every file in a tree declares nothing, and a file that does is saying
67
+ * something about its own values, not asking to be skipped. See this module's docblock for what a caller owes
68
+ * the answer — in particular that `en` comes back like any other tag and exempts nothing.
69
+ */
70
+ function localeDeclared(source) {
71
+ for (const line of source.split("\n").slice(0, HEAD_LINES)) {
72
+ const found = LOCALE_DECLARED.exec(line);
73
+ if (found !== null) return found[1];
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Every span on one line whose quoted content is a value rather than prose — as `[open, close)` offsets.
79
+ *
80
+ * `open` is the first character inside the quote and `close` is the closing quote, so a match at or after
81
+ * `open` and ending at or before `close` sits inside a value. Left to right, in whichever of the three quote
82
+ * characters opened it.
83
+ *
84
+ * By default a run holding whitespace is a sentence, not a value, and **a span that turns out to hold one is
85
+ * passed over rather than skipped**: the scan keeps reading inside it, which is how a backticked token inside
86
+ * a prose string literal still counts — this repository names wire values that way in a `.describe()`.
87
+ *
88
+ * `everyQuoted` widens that to include the sentences, for one case and one only: a line in a file that has
89
+ * declared itself written in another language, where the quoted runs are the translation. It is the mechanism
90
+ * the marker drives, and it is why the marker exempts a file's values without exempting the file.
91
+ *
92
+ * A run is one line's worth. A quote that never closes on its line opens no value, so the inner lines of a
93
+ * multi-line template literal are read as prose — deliberately, since that is where a long English argument
94
+ * would otherwise hide.
95
+ */
96
+ function valueSpans(line, everyQuoted = false) {
97
+ const spans = [];
98
+ for (let index = 0; index < line.length; index += 1) {
99
+ const quote = line[index];
100
+ if (quote !== "\"" && quote !== "'" && quote !== "`") continue;
101
+ const close = line.indexOf(quote, index + 1);
102
+ if (close === -1) continue;
103
+ const inner = line.slice(index + 1, close);
104
+ if (inner.length === 0) continue;
105
+ if (!everyQuoted && /\s/.test(inner)) continue;
106
+ spans.push([index + 1, close]);
107
+ index = close;
108
+ }
109
+ return spans;
110
+ }
111
+ //#endregion
112
+ export { localeDeclared, valueSpans };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A declared exception map: a language range the truncation walk would miss, and the supported locale
3
+ * it means. `{ nb: "no", tl: "fil" }` — the pairs no algorithm derives, because they are historical
4
+ * rather than structural.
5
+ */
6
+ export type LocaleExceptions = Readonly<Record<string, string>>;
7
+ /** How a match was reached, so a caller can tell an answer from a default. */
8
+ export interface LocaleMatch {
9
+ /** The supported locale that answered, exactly as the caller spelled it in `supported`. */
10
+ readonly locale: string;
11
+ /** The range that matched it, lower-cased — `*` when a wildcard took the first supported locale. */
12
+ readonly range: string;
13
+ }
14
+ /**
15
+ * The first supported locale any of `desired` asks for, most-wanted first, or `null` when none does.
16
+ *
17
+ * `desired` is already in preference order — {@link parseAcceptLanguage} sorts by q-weight, and a
18
+ * browser resolver chain builds it from the reader's explicit choice downward. A `*` range takes the
19
+ * first supported locale, which is what "anything you have" means; it is checked in place rather than
20
+ * up front, so a reader who wrote `de, *` is answered in German when German ships.
21
+ *
22
+ * Total, and never throws: every `Intl` construction on the way through is guarded, so a header full
23
+ * of `en_US` and `;q=0.9` fragments falls through to `null` and the caller's default.
24
+ */
25
+ export declare function matchLocale(desired: readonly string[], supported: readonly string[], exceptions?: LocaleExceptions): LocaleMatch | null;
26
+ //# sourceMappingURL=match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/i18n/match.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhE,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oGAAoG;IACpG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AA+DD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,UAAU,GAAE,gBAAqB,GAChC,WAAW,GAAG,IAAI,CAcpB"}
@@ -0,0 +1,88 @@
1
+ import { parseLocale } from "./locale.js";
2
+ //#region src/i18n/match.ts
3
+ /** Lower-cased index of the supported list, so matching is case-insensitive without lower-casing answers. */
4
+ function index(supported) {
5
+ const map = /* @__PURE__ */ new Map();
6
+ for (const locale of supported) {
7
+ const key = locale.toLowerCase();
8
+ if (!map.has(key)) map.set(key, locale);
9
+ }
10
+ return map;
11
+ }
12
+ /**
13
+ * RFC 4647 §3.4 lookup: `tag`, then `tag` with its right-most subtag removed, and so on.
14
+ *
15
+ * A trailing single-character subtag is removed with the one before it, as the RFC requires —
16
+ * `en-a-bbb` truncates to `en`, never to the meaningless `en-a`.
17
+ */
18
+ function truncationWalk(tag, supported) {
19
+ let candidate = tag.toLowerCase();
20
+ while (candidate.length > 0) {
21
+ const hit = supported.get(candidate);
22
+ if (hit) return hit;
23
+ const cut = candidate.lastIndexOf("-");
24
+ if (cut < 0) return null;
25
+ candidate = candidate.slice(0, cut);
26
+ const tail = candidate.lastIndexOf("-");
27
+ if (tail >= 0 && candidate.length - tail === 2) candidate = candidate.slice(0, tail);
28
+ }
29
+ return null;
30
+ }
31
+ /**
32
+ * The supported locale that best answers one range, or `null`.
33
+ *
34
+ * Three passes, in the order that makes each one earn its place: the declared exception map first,
35
+ * because it exists to override what follows; then the truncation walk on the range as written, which
36
+ * answers `es-AR` with `es`; then the walk again over the **maximized** form, which answers `zh-TW`
37
+ * with `zh-Hant` by way of `zh-Hant-TW`.
38
+ *
39
+ * **Maximizing is the third step, not the first.** `Intl.Locale` throws on input the first step handles
40
+ * fine, and maximizing a range before trying it as written turns `es` into `es-Latn-ES` and then walks
41
+ * back down to `es` — the same answer, three constructions later, and a `RangeError` away from a 500.
42
+ */
43
+ function lookupRange(range, supported, exceptions) {
44
+ const key = range.toLowerCase();
45
+ const declared = Object.hasOwn(exceptions, key) ? exceptions[key] : void 0;
46
+ if (declared) {
47
+ const hit = supported.get(declared.toLowerCase());
48
+ if (hit) return hit;
49
+ }
50
+ const direct = truncationWalk(range, supported);
51
+ if (direct) return direct;
52
+ const maximized = parseLocale(range)?.maximize().toString();
53
+ return maximized ? truncationWalk(maximized, supported) : null;
54
+ }
55
+ /**
56
+ * The first supported locale any of `desired` asks for, most-wanted first, or `null` when none does.
57
+ *
58
+ * `desired` is already in preference order — {@link parseAcceptLanguage} sorts by q-weight, and a
59
+ * browser resolver chain builds it from the reader's explicit choice downward. A `*` range takes the
60
+ * first supported locale, which is what "anything you have" means; it is checked in place rather than
61
+ * up front, so a reader who wrote `de, *` is answered in German when German ships.
62
+ *
63
+ * Total, and never throws: every `Intl` construction on the way through is guarded, so a header full
64
+ * of `en_US` and `;q=0.9` fragments falls through to `null` and the caller's default.
65
+ */
66
+ function matchLocale(desired, supported, exceptions = {}) {
67
+ if (supported.length === 0) return null;
68
+ const lookup = index(supported);
69
+ for (const range of desired) {
70
+ const normalized = range.trim().toLowerCase();
71
+ if (normalized.length === 0) continue;
72
+ if (normalized === "*") {
73
+ const first = supported[0];
74
+ return first ? {
75
+ locale: first,
76
+ range: normalized
77
+ } : null;
78
+ }
79
+ const hit = lookupRange(normalized, lookup, exceptions);
80
+ if (hit) return {
81
+ locale: hit,
82
+ range: normalized
83
+ };
84
+ }
85
+ return null;
86
+ }
87
+ //#endregion
88
+ export { matchLocale };
@@ -0,0 +1,25 @@
1
+ import type { Capability } from "../capability/capability";
2
+ import { type LocaleCatalogs, type MessageCatalog } from "./catalog";
3
+ /**
4
+ * Every composed capability's `messages`, merged into one set of catalogs keyed by locale.
5
+ *
6
+ * **A capability may only declare keys under its own domain**, and the merge refuses anything else.
7
+ * That is the same rule as `pithy_<capability>_<table>` and `auth/invalid_token`, enforced for the same
8
+ * reason: the domain segment is what makes two capabilities' contributions incapable of colliding, so
9
+ * merge order stops being a thing anyone has to reason about. It applies to the adopter's own `app`
10
+ * capability exactly as it applies to a kit package — `board/nav.settings` is theirs to declare, and
11
+ * `auth/sign_in.title` is not.
12
+ *
13
+ * Library-before-app, like the migration registry: capabilities are merged in composition order, so a
14
+ * later contribution wins a key an earlier one wrote. With the domain rule in force that can only
15
+ * happen between two capabilities sharing a name, which `createBackend` already refuses.
16
+ */
17
+ export declare function composeMessages(capabilities: readonly Capability[]): LocaleCatalogs;
18
+ /**
19
+ * The catalog for `locale`, or the default locale's, or an empty one.
20
+ *
21
+ * The fallback that makes the seam optional: a Worker composing no i18n capability reads the baked
22
+ * English and behaves byte for byte as it did before any of this landed.
23
+ */
24
+ export declare function catalogFor(catalogs: LocaleCatalogs, locale: string): MessageCatalog;
25
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/i18n/registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAiB,MAAM,WAAW,CAAC;AAWpF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,GAAG,cAAc,CAqCnF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAEnF"}