@toa.io/extensions.configuration 1.0.0-alpha.273 → 1.0.0-alpha.274

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 (521) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/components/configuration.values/manifest.toa.yaml +158 -0
  3. package/components/configuration.values/source/create.ts +56 -0
  4. package/components/configuration.values/source/fetch.ts +24 -0
  5. package/components/configuration.values/source/get.ts +33 -0
  6. package/components/configuration.values/source/lib/map.test.ts +34 -0
  7. package/components/configuration.values/source/lib/map.ts +34 -0
  8. package/components/configuration.values/source/lib/resolve.ts +54 -0
  9. package/components/configuration.values/source/list.ts +19 -0
  10. package/components/configuration.values/tsconfig.json +9 -0
  11. package/cucumber.js +8 -0
  12. package/features/site/_app/immutable/asset.js +1 -0
  13. package/features/site/index.html +6 -0
  14. package/features/steps/UI.ts +88 -0
  15. package/features/steps/tsconfig.json +9 -0
  16. package/features/ui.feature +85 -0
  17. package/package.json +12 -5
  18. package/readme.md +150 -34
  19. package/schemas/annotation.cos.yaml +27 -2
  20. package/source/Aspect.test.ts +84 -8
  21. package/source/Aspect.ts +59 -5
  22. package/source/Client.test.ts +104 -0
  23. package/source/Client.ts +258 -0
  24. package/source/Composition.ts +57 -0
  25. package/source/Factory.ts +28 -5
  26. package/source/Secret.test.ts +29 -0
  27. package/source/Secret.ts +29 -0
  28. package/source/UI.ts +197 -0
  29. package/source/configuration.test.ts +84 -52
  30. package/source/configuration.ts +28 -9
  31. package/source/const.ts +17 -0
  32. package/source/deployment.test.ts +80 -1
  33. package/source/deployment.ts +73 -23
  34. package/source/epoch.test.ts +26 -0
  35. package/source/epoch.ts +21 -0
  36. package/source/index.ts +2 -1
  37. package/transpiled/Aspect.d.ts +18 -4
  38. package/transpiled/Aspect.js +47 -3
  39. package/transpiled/Aspect.js.map +1 -1
  40. package/transpiled/Aspect.test.js +59 -9
  41. package/transpiled/Aspect.test.js.map +1 -1
  42. package/transpiled/Client.d.ts +61 -0
  43. package/transpiled/Client.js +173 -0
  44. package/transpiled/Client.js.map +1 -0
  45. package/transpiled/Client.test.d.ts +1 -0
  46. package/transpiled/Client.test.js +77 -0
  47. package/transpiled/Client.test.js.map +1 -0
  48. package/transpiled/Composition.d.ts +14 -0
  49. package/transpiled/Composition.js +43 -0
  50. package/transpiled/Composition.js.map +1 -0
  51. package/transpiled/Factory.d.ts +8 -2
  52. package/transpiled/Factory.js +17 -2
  53. package/transpiled/Factory.js.map +1 -1
  54. package/transpiled/Secret.d.ts +13 -0
  55. package/transpiled/Secret.js +26 -0
  56. package/transpiled/Secret.js.map +1 -0
  57. package/transpiled/Secret.test.d.ts +1 -0
  58. package/transpiled/Secret.test.js +26 -0
  59. package/transpiled/Secret.test.js.map +1 -0
  60. package/transpiled/UI.d.ts +25 -0
  61. package/transpiled/UI.js +186 -0
  62. package/transpiled/UI.js.map +1 -0
  63. package/transpiled/configuration.d.ts +7 -2
  64. package/transpiled/configuration.js +30 -12
  65. package/transpiled/configuration.js.map +1 -1
  66. package/transpiled/configuration.test.js +77 -51
  67. package/transpiled/configuration.test.js.map +1 -1
  68. package/transpiled/const.d.ts +11 -0
  69. package/transpiled/const.js +14 -0
  70. package/transpiled/const.js.map +1 -0
  71. package/transpiled/deployment.d.ts +10 -4
  72. package/transpiled/deployment.js +57 -20
  73. package/transpiled/deployment.js.map +1 -1
  74. package/transpiled/deployment.test.js +60 -0
  75. package/transpiled/deployment.test.js.map +1 -1
  76. package/transpiled/epoch.d.ts +2 -0
  77. package/transpiled/epoch.js +21 -0
  78. package/transpiled/epoch.js.map +1 -0
  79. package/transpiled/epoch.test.d.ts +1 -0
  80. package/transpiled/epoch.test.js +22 -0
  81. package/transpiled/epoch.test.js.map +1 -0
  82. package/transpiled/index.d.ts +2 -1
  83. package/transpiled/index.js +4 -1
  84. package/transpiled/index.js.map +1 -1
  85. package/transpiled/tsconfig.tsbuildinfo +1 -1
  86. package/ui/.agents/skills/antcn/SKILL.md +91 -0
  87. package/ui/.agents/skills/antcn/references/analytics.md +78 -0
  88. package/ui/.agents/skills/antcn/references/bridge.md +41 -0
  89. package/ui/.agents/skills/antcn/references/clipboard.md +23 -0
  90. package/ui/.agents/skills/antcn/references/dismissable.md +75 -0
  91. package/ui/.agents/skills/antcn/references/dropdown.md +153 -0
  92. package/ui/.agents/skills/antcn/references/fragments.md +29 -0
  93. package/ui/.agents/skills/antcn/references/fullscreen.md +157 -0
  94. package/ui/.agents/skills/antcn/references/geo.md +27 -0
  95. package/ui/.agents/skills/antcn/references/globals.md +31 -0
  96. package/ui/.agents/skills/antcn/references/grammatical-form.md +69 -0
  97. package/ui/.agents/skills/antcn/references/hints.md +44 -0
  98. package/ui/.agents/skills/antcn/references/hold.md +34 -0
  99. package/ui/.agents/skills/antcn/references/iam.md +117 -0
  100. package/ui/.agents/skills/antcn/references/input-form.md +34 -0
  101. package/ui/.agents/skills/antcn/references/locale.md +66 -0
  102. package/ui/.agents/skills/antcn/references/net.md +33 -0
  103. package/ui/.agents/skills/antcn/references/panel.md +44 -0
  104. package/ui/.agents/skills/antcn/references/passkeys.md +27 -0
  105. package/ui/.agents/skills/antcn/references/payments.md +99 -0
  106. package/ui/.agents/skills/antcn/references/realtime.md +69 -0
  107. package/ui/.agents/skills/antcn/references/scrollable.md +39 -0
  108. package/ui/.agents/skills/antcn/references/selector.md +71 -0
  109. package/ui/.agents/skills/antcn/references/shake.md +36 -0
  110. package/ui/.agents/skills/antcn/references/share.md +28 -0
  111. package/ui/.agents/skills/antcn/references/shell.md +139 -0
  112. package/ui/.agents/skills/antcn/references/stack.md +53 -0
  113. package/ui/.agents/skills/antcn/references/system.md +149 -0
  114. package/ui/.agents/skills/antcn/references/taptap.md +28 -0
  115. package/ui/.agents/skills/antcn/references/tools.md +68 -0
  116. package/ui/.agents/skills/antcn/references/transmission.md +96 -0
  117. package/ui/.agents/skills/shadcn-svelte/SKILL.md +227 -0
  118. package/ui/.agents/skills/shadcn-svelte/agents/openai.yml +5 -0
  119. package/ui/.agents/skills/shadcn-svelte/assets/shadcn-svelte-small.png +0 -0
  120. package/ui/.agents/skills/shadcn-svelte/assets/shadcn-svelte.png +0 -0
  121. package/ui/.agents/skills/shadcn-svelte/cli.md +166 -0
  122. package/ui/.agents/skills/shadcn-svelte/customization.md +213 -0
  123. package/ui/.agents/skills/shadcn-svelte/evals/evals.json +47 -0
  124. package/ui/.agents/skills/shadcn-svelte/rules/composition.md +244 -0
  125. package/ui/.agents/skills/shadcn-svelte/rules/forms.md +234 -0
  126. package/ui/.agents/skills/shadcn-svelte/rules/icons.md +107 -0
  127. package/ui/.agents/skills/shadcn-svelte/rules/styling.md +195 -0
  128. package/ui/.agents/skills/svas/SKILL.md +169 -0
  129. package/ui/.agents/skills/svas/references/patterns.md +95 -0
  130. package/ui/.agents/skills/svintl/SKILL.md +176 -0
  131. package/ui/.claude/settings.json +15 -0
  132. package/ui/.codex/hooks.json +15 -0
  133. package/ui/.cursor/hooks.json +11 -0
  134. package/ui/.env.example +1 -0
  135. package/ui/.prettierrc +9 -0
  136. package/ui/.vscode/extensions.json +7 -0
  137. package/ui/.vscode/settings.json +27 -0
  138. package/ui/AGENTS.md +51 -0
  139. package/ui/architecture.md +390 -0
  140. package/ui/components.json +20 -0
  141. package/ui/eslint.config.js +152 -0
  142. package/ui/features/base.ts +2 -0
  143. package/ui/features/boot.feature +5 -0
  144. package/ui/features/steps/authentication.ts +34 -0
  145. package/ui/features/steps/navigaton.ts +37 -0
  146. package/ui/features/steps/typing.ts +24 -0
  147. package/ui/package-lock.json +8173 -0
  148. package/ui/package.json +76 -0
  149. package/ui/playwright.config.ts +13 -0
  150. package/ui/skills-lock.json +30 -0
  151. package/ui/src/@/configuration/index.ts +1 -0
  152. package/ui/src/@/configuration/svc/create.ts +29 -0
  153. package/ui/src/@/configuration/svc/index.ts +3 -0
  154. package/ui/src/@/configuration/svc/net/Configuration.ts +18 -0
  155. package/ui/src/@/configuration/svc/net/index.ts +2 -0
  156. package/ui/src/@/configuration/svc/net/interface.ts +35 -0
  157. package/ui/src/@/configuration/svc/store.ts +14 -0
  158. package/ui/src/@/configuration/ui/Configurations.svelte +85 -0
  159. package/ui/src/@/configuration/ui/Configurations.ts +5 -0
  160. package/ui/src/@/configuration/ui/Create.svelte +64 -0
  161. package/ui/src/@/configuration/ui/Create.ts +7 -0
  162. package/ui/src/@/configuration/ui/Form.svelte +75 -0
  163. package/ui/src/@/configuration/ui/Form.ts +79 -0
  164. package/ui/src/@/configuration/ui/Row.svelte +44 -0
  165. package/ui/src/@/configuration/ui/Row.ts +9 -0
  166. package/ui/src/@/configuration/ui/Value.svelte +33 -0
  167. package/ui/src/@/configuration/ui/Value.ts +6 -0
  168. package/ui/src/@/configuration/ui/index.ts +9 -0
  169. package/ui/src/@/configuration/ui/intl/built.js +38 -0
  170. package/ui/src/@/configuration/ui/intl/context.yaml +51 -0
  171. package/ui/src/@/configuration/ui/intl/en-US.yaml +21 -0
  172. package/ui/src/@/configuration/ui/intl/index.ts +9 -0
  173. package/ui/src/@/configuration/ui/intl/types.ts +31 -0
  174. package/ui/src/@/configuration/ui/read.test.ts +75 -0
  175. package/ui/src/@/configuration/ui/read.ts +102 -0
  176. package/ui/src/@/configuration/ui/ui.test.ts +69 -0
  177. package/ui/src/@/configuration/ui/ui.ts +115 -0
  178. package/ui/src/@/iam/index.ts +1 -0
  179. package/ui/src/@/iam/rc.ts +22 -0
  180. package/ui/src/@/iam/svc/authenticated.ts +17 -0
  181. package/ui/src/@/iam/svc/basic.ts +23 -0
  182. package/ui/src/@/iam/svc/index.ts +10 -0
  183. package/ui/src/@/iam/svc/logout.ts +5 -0
  184. package/ui/src/@/iam/svc/named.ts +16 -0
  185. package/ui/src/@/iam/svc/net/Echo.ts +9 -0
  186. package/ui/src/@/iam/svc/net/basic/index.ts +1 -0
  187. package/ui/src/@/iam/svc/net/basic/interface.ts +15 -0
  188. package/ui/src/@/iam/svc/net/federation/index.ts +1 -0
  189. package/ui/src/@/iam/svc/net/federation/interface.ts +12 -0
  190. package/ui/src/@/iam/svc/net/index.ts +6 -0
  191. package/ui/src/@/iam/svc/net/interface.ts +17 -0
  192. package/ui/src/@/iam/svc/net/otp/index.ts +1 -0
  193. package/ui/src/@/iam/svc/net/otp/interface.ts +14 -0
  194. package/ui/src/@/iam/svc/net/passkeys/index.ts +1 -0
  195. package/ui/src/@/iam/svc/net/passkeys/interface.ts +12 -0
  196. package/ui/src/@/iam/svc/oidc/apple.ts +47 -0
  197. package/ui/src/@/iam/svc/oidc/authenticate.ts +62 -0
  198. package/ui/src/@/iam/svc/oidc/google.ts +45 -0
  199. package/ui/src/@/iam/svc/oidc/hello.ts +82 -0
  200. package/ui/src/@/iam/svc/oidc/index.ts +3 -0
  201. package/ui/src/@/iam/svc/oidc/nonce.ts +5 -0
  202. package/ui/src/@/iam/svc/oidc/providers.ts +28 -0
  203. package/ui/src/@/iam/svc/oidc/standard.ts +28 -0
  204. package/ui/src/@/iam/svc/otp.ts +18 -0
  205. package/ui/src/@/iam/svc/passkeys/create.ts +20 -0
  206. package/ui/src/@/iam/svc/passkeys/index.ts +2 -0
  207. package/ui/src/@/iam/svc/passkeys/login.ts +21 -0
  208. package/ui/src/@/iam/svc/store.ts +36 -0
  209. package/ui/src/@/iam/svc/sync.ts +18 -0
  210. package/ui/src/@/iam/svc/transfer.ts +11 -0
  211. package/ui/src/@/iam/svc/update.ts +8 -0
  212. package/ui/src/@/iam/ui/AccountLike.ts +4 -0
  213. package/ui/src/@/iam/ui/Authenticated.svelte +37 -0
  214. package/ui/src/@/iam/ui/Authenticated.ts +10 -0
  215. package/ui/src/@/iam/ui/Authentication.svelte +50 -0
  216. package/ui/src/@/iam/ui/Authentication.ts +9 -0
  217. package/ui/src/@/iam/ui/Authorized.svelte +32 -0
  218. package/ui/src/@/iam/ui/Authorized.ts +65 -0
  219. package/ui/src/@/iam/ui/Card.svelte +14 -0
  220. package/ui/src/@/iam/ui/Card.ts +7 -0
  221. package/ui/src/@/iam/ui/Refresh.svelte +39 -0
  222. package/ui/src/@/iam/ui/credentials/Credentials.svelte +26 -0
  223. package/ui/src/@/iam/ui/credentials/Credentials.ts +5 -0
  224. package/ui/src/@/iam/ui/credentials/Provider.svelte +24 -0
  225. package/ui/src/@/iam/ui/credentials/Provider.ts +5 -0
  226. package/ui/src/@/iam/ui/credentials/index.ts +1 -0
  227. package/ui/src/@/iam/ui/credentials/passkeys/Key.svelte +42 -0
  228. package/ui/src/@/iam/ui/credentials/passkeys/Key.ts +29 -0
  229. package/ui/src/@/iam/ui/credentials/passkeys/Manager.svelte +83 -0
  230. package/ui/src/@/iam/ui/credentials/passkeys/Manager.ts +5 -0
  231. package/ui/src/@/iam/ui/credentials/passkeys/Passkeys.svelte +39 -0
  232. package/ui/src/@/iam/ui/credentials/passkeys/Summary.svelte +27 -0
  233. package/ui/src/@/iam/ui/credentials/passkeys/Summary.ts +5 -0
  234. package/ui/src/@/iam/ui/credentials/passkeys/index.ts +1 -0
  235. package/ui/src/@/iam/ui/credentials/providers.ts +15 -0
  236. package/ui/src/@/iam/ui/credentials/transfer/Qr.svelte +24 -0
  237. package/ui/src/@/iam/ui/credentials/transfer/Qr.ts +3 -0
  238. package/ui/src/@/iam/ui/credentials/transfer/Transfer.svelte +48 -0
  239. package/ui/src/@/iam/ui/credentials/transfer/Transfer.ts +5 -0
  240. package/ui/src/@/iam/ui/credentials/transfer/index.ts +1 -0
  241. package/ui/src/@/iam/ui/index.ts +3 -0
  242. package/ui/src/@/iam/ui/intl/built.js +79 -0
  243. package/ui/src/@/iam/ui/intl/context.yaml +91 -0
  244. package/ui/src/@/iam/ui/intl/en-US.yaml +64 -0
  245. package/ui/src/@/iam/ui/intl/index.ts +10 -0
  246. package/ui/src/@/iam/ui/intl/types.ts +70 -0
  247. package/ui/src/@/iam/ui/oidc/Authentication.svelte +16 -0
  248. package/ui/src/@/iam/ui/oidc/Authentication.ts +5 -0
  249. package/ui/src/@/iam/ui/oidc/Button.svelte +36 -0
  250. package/ui/src/@/iam/ui/oidc/Button.ts +11 -0
  251. package/ui/src/@/iam/ui/oidc/Refresh.svelte +16 -0
  252. package/ui/src/@/iam/ui/oidc/icons/Apple.svelte +19 -0
  253. package/ui/src/@/iam/ui/oidc/icons/Google.svelte +31 -0
  254. package/ui/src/@/iam/ui/oidc/icons/index.ts +11 -0
  255. package/ui/src/@/iam/ui/oidc/index.ts +2 -0
  256. package/ui/src/@/iam/ui/passkey/Authentication.svelte +64 -0
  257. package/ui/src/@/iam/ui/passkey/Authentication.ts +8 -0
  258. package/ui/src/@/iam/ui/passkey/Create.svelte +53 -0
  259. package/ui/src/@/iam/ui/passkey/Create.ts +7 -0
  260. package/ui/src/@/iam/ui/passkey/Login.svelte +29 -0
  261. package/ui/src/@/iam/ui/passkey/Refresh.svelte +29 -0
  262. package/ui/src/@/iam/ui/passkey/index.ts +2 -0
  263. package/ui/src/@/iam/ui/password/Authentication.svelte +19 -0
  264. package/ui/src/@/iam/ui/password/Authentication.ts +8 -0
  265. package/ui/src/@/iam/ui/password/Form.svelte +151 -0
  266. package/ui/src/@/iam/ui/password/Form.ts +6 -0
  267. package/ui/src/@/iam/ui/password/Password.svelte +39 -0
  268. package/ui/src/@/iam/ui/password/Password.ts +6 -0
  269. package/ui/src/@/iam/ui/password/Refresh.svelte +5 -0
  270. package/ui/src/@/iam/ui/password/index.ts +2 -0
  271. package/ui/src/@/net/index.ts +1 -0
  272. package/ui/src/@/net/svc/index.ts +3 -0
  273. package/ui/src/@/net/svc/origin.ts +8 -0
  274. package/ui/src/@/net/svc/query.test.ts +45 -0
  275. package/ui/src/@/net/svc/query.ts +25 -0
  276. package/ui/src/@/passkeys/index.ts +1 -0
  277. package/ui/src/@/passkeys/svc/add.ts +15 -0
  278. package/ui/src/@/passkeys/svc/create.ts +58 -0
  279. package/ui/src/@/passkeys/svc/credentials/convert.ts +32 -0
  280. package/ui/src/@/passkeys/svc/credentials/create.ts +34 -0
  281. package/ui/src/@/passkeys/svc/credentials/get.ts +14 -0
  282. package/ui/src/@/passkeys/svc/credentials/index.ts +3 -0
  283. package/ui/src/@/passkeys/svc/credentials/key.ts +10 -0
  284. package/ui/src/@/passkeys/svc/get.ts +10 -0
  285. package/ui/src/@/passkeys/svc/index.ts +6 -0
  286. package/ui/src/@/passkeys/svc/net/CreationResponse.ts +15 -0
  287. package/ui/src/@/passkeys/svc/net/Passkey.ts +8 -0
  288. package/ui/src/@/passkeys/svc/net/RequestResponse.ts +12 -0
  289. package/ui/src/@/passkeys/svc/net/challenges/Options.ts +21 -0
  290. package/ui/src/@/passkeys/svc/net/challenges/index.ts +2 -0
  291. package/ui/src/@/passkeys/svc/net/challenges/interface.ts +16 -0
  292. package/ui/src/@/passkeys/svc/net/index.ts +4 -0
  293. package/ui/src/@/passkeys/svc/net/interface.ts +29 -0
  294. package/ui/src/@/passkeys/svc/remove.ts +13 -0
  295. package/ui/src/@/passkeys/svc/request.ts +50 -0
  296. package/ui/src/@/passkeys/svc/store.ts +13 -0
  297. package/ui/src/@/passkeys/svc/supported.ts +1 -0
  298. package/ui/src/@/passkeys/ui/aaguid.json +150 -0
  299. package/ui/src/@/realtime/events.ts +1 -0
  300. package/ui/src/@/realtime/index.ts +1 -0
  301. package/ui/src/@/realtime/rc.ts +13 -0
  302. package/ui/src/@/realtime/svc/connect.ts +67 -0
  303. package/ui/src/@/realtime/svc/events.ts +17 -0
  304. package/ui/src/@/realtime/svc/index.ts +5 -0
  305. package/ui/src/@/realtime/svc/net/Message.ts +6 -0
  306. package/ui/src/@/realtime/svc/net/index.ts +2 -0
  307. package/ui/src/@/realtime/svc/net/interface.ts +13 -0
  308. package/ui/src/@/realtime/svc/reset.ts +5 -0
  309. package/ui/src/@/realtime/svc/status.ts +12 -0
  310. package/ui/src/@/realtime/svc/store.ts +30 -0
  311. package/ui/src/@/realtime/svc/time.ts +15 -0
  312. package/ui/src/app.d.ts +20 -0
  313. package/ui/src/app.html +13 -0
  314. package/ui/src/config/index.ts +70 -0
  315. package/ui/src/lib/assets/favicon.svg +1 -0
  316. package/ui/src/lib/components/clipboard/Clipboard.svelte +63 -0
  317. package/ui/src/lib/components/clipboard/Clipboard.ts +12 -0
  318. package/ui/src/lib/components/clipboard/index.ts +1 -0
  319. package/ui/src/lib/components/dismissable/Dismissable.svelte +81 -0
  320. package/ui/src/lib/components/dismissable/Dismissable.ts +7 -0
  321. package/ui/src/lib/components/dismissable/index.ts +1 -0
  322. package/ui/src/lib/components/dropdown/Back.svelte +14 -0
  323. package/ui/src/lib/components/dropdown/Back.ts +3 -0
  324. package/ui/src/lib/components/dropdown/Content.svelte +76 -0
  325. package/ui/src/lib/components/dropdown/Content.ts +13 -0
  326. package/ui/src/lib/components/dropdown/Context.ts +18 -0
  327. package/ui/src/lib/components/dropdown/Group.svelte +24 -0
  328. package/ui/src/lib/components/dropdown/Group.ts +8 -0
  329. package/ui/src/lib/components/dropdown/GroupContext.ts +15 -0
  330. package/ui/src/lib/components/dropdown/Item.svelte +31 -0
  331. package/ui/src/lib/components/dropdown/Item.ts +5 -0
  332. package/ui/src/lib/components/dropdown/Layer.svelte +11 -0
  333. package/ui/src/lib/components/dropdown/Layer.ts +7 -0
  334. package/ui/src/lib/components/dropdown/Root.svelte +88 -0
  335. package/ui/src/lib/components/dropdown/Root.ts +6 -0
  336. package/ui/src/lib/components/dropdown/Separator.svelte +13 -0
  337. package/ui/src/lib/components/dropdown/Separator.ts +5 -0
  338. package/ui/src/lib/components/dropdown/Trigger.svelte +33 -0
  339. package/ui/src/lib/components/dropdown/Trigger.ts +5 -0
  340. package/ui/src/lib/components/dropdown/index.ts +8 -0
  341. package/ui/src/lib/components/fullscreen/Fullscreen.svelte +97 -0
  342. package/ui/src/lib/components/fullscreen/Fullscreen.ts +18 -0
  343. package/ui/src/lib/components/fullscreen/Overlay.svelte +15 -0
  344. package/ui/src/lib/components/fullscreen/Overlay.ts +7 -0
  345. package/ui/src/lib/components/fullscreen/index.ts +2 -0
  346. package/ui/src/lib/components/hold/Hold.svelte +133 -0
  347. package/ui/src/lib/components/hold/Hold.ts +23 -0
  348. package/ui/src/lib/components/hold/index.ts +1 -0
  349. package/ui/src/lib/components/scrollable/Scrollable.svelte +49 -0
  350. package/ui/src/lib/components/scrollable/Scrollable.ts +117 -0
  351. package/ui/src/lib/components/scrollable/index.ts +2 -0
  352. package/ui/src/lib/components/shake/Shake.svelte +62 -0
  353. package/ui/src/lib/components/shake/Shake.ts +7 -0
  354. package/ui/src/lib/components/shake/index.ts +1 -0
  355. package/ui/src/lib/components/share/Share.svelte +58 -0
  356. package/ui/src/lib/components/share/Share.ts +9 -0
  357. package/ui/src/lib/components/share/index.ts +1 -0
  358. package/ui/src/lib/components/shell/Attention.svelte +11 -0
  359. package/ui/src/lib/components/shell/Attention.ts +6 -0
  360. package/ui/src/lib/components/shell/Header.svelte +18 -0
  361. package/ui/src/lib/components/shell/Header.ts +7 -0
  362. package/ui/src/lib/components/shell/Screen.svelte +28 -0
  363. package/ui/src/lib/components/shell/Screen.ts +11 -0
  364. package/ui/src/lib/components/shell/Sticky.svelte +23 -0
  365. package/ui/src/lib/components/shell/Sticky.ts +8 -0
  366. package/ui/src/lib/components/shell/Underlay.svelte +21 -0
  367. package/ui/src/lib/components/shell/Underlay.ts +6 -0
  368. package/ui/src/lib/components/shell/index.ts +6 -0
  369. package/ui/src/lib/components/shell/nav/Actions.svelte +31 -0
  370. package/ui/src/lib/components/shell/nav/Actions.ts +16 -0
  371. package/ui/src/lib/components/shell/nav/Back.svelte +20 -0
  372. package/ui/src/lib/components/shell/nav/Back.ts +9 -0
  373. package/ui/src/lib/components/shell/nav/Bar.svelte +24 -0
  374. package/ui/src/lib/components/shell/nav/Bar.ts +3 -0
  375. package/ui/src/lib/components/shell/nav/Button.svelte +64 -0
  376. package/ui/src/lib/components/shell/nav/Button.ts +7 -0
  377. package/ui/src/lib/components/shell/nav/Nav.svelte +62 -0
  378. package/ui/src/lib/components/shell/nav/Nav.ts +35 -0
  379. package/ui/src/lib/components/shell/nav/Return.svelte +29 -0
  380. package/ui/src/lib/components/shell/nav/Return.ts +15 -0
  381. package/ui/src/lib/components/shell/nav/Sections.svelte +58 -0
  382. package/ui/src/lib/components/shell/nav/Sections.ts +6 -0
  383. package/ui/src/lib/components/shell/nav/Toolbar.svelte +73 -0
  384. package/ui/src/lib/components/shell/nav/Toolbar.ts +3 -0
  385. package/ui/src/lib/components/shell/nav/history.ts +36 -0
  386. package/ui/src/lib/components/shell/nav/index.ts +5 -0
  387. package/ui/src/lib/components/shell/nav/store.ts +24 -0
  388. package/ui/src/lib/components/ui/alert/alert-action.svelte +20 -0
  389. package/ui/src/lib/components/ui/alert/alert-description.svelte +23 -0
  390. package/ui/src/lib/components/ui/alert/alert-title.svelte +23 -0
  391. package/ui/src/lib/components/ui/alert/alert.svelte +43 -0
  392. package/ui/src/lib/components/ui/alert/index.ts +17 -0
  393. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  394. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  395. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
  396. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  397. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  398. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  399. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
  400. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
  401. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  402. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  403. package/ui/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  404. package/ui/src/lib/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  405. package/ui/src/lib/components/ui/alert-dialog/index.ts +40 -0
  406. package/ui/src/lib/components/ui/badge/badge.svelte +49 -0
  407. package/ui/src/lib/components/ui/badge/index.ts +2 -0
  408. package/ui/src/lib/components/ui/button/button.svelte +82 -0
  409. package/ui/src/lib/components/ui/button/index.ts +17 -0
  410. package/ui/src/lib/components/ui/button-group/button-group-separator.svelte +23 -0
  411. package/ui/src/lib/components/ui/button-group/button-group-text.svelte +28 -0
  412. package/ui/src/lib/components/ui/button-group/button-group.svelte +46 -0
  413. package/ui/src/lib/components/ui/button-group/index.ts +15 -0
  414. package/ui/src/lib/components/ui/card/card-action.svelte +23 -0
  415. package/ui/src/lib/components/ui/card/card-content.svelte +20 -0
  416. package/ui/src/lib/components/ui/card/card-description.svelte +20 -0
  417. package/ui/src/lib/components/ui/card/card-footer.svelte +20 -0
  418. package/ui/src/lib/components/ui/card/card-header.svelte +23 -0
  419. package/ui/src/lib/components/ui/card/card-title.svelte +20 -0
  420. package/ui/src/lib/components/ui/card/card.svelte +22 -0
  421. package/ui/src/lib/components/ui/card/index.ts +25 -0
  422. package/ui/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
  423. package/ui/src/lib/components/ui/checkbox/index.ts +6 -0
  424. package/ui/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
  425. package/ui/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
  426. package/ui/src/lib/components/ui/collapsible/collapsible.svelte +20 -0
  427. package/ui/src/lib/components/ui/collapsible/index.ts +13 -0
  428. package/ui/src/lib/components/ui/input/index.ts +7 -0
  429. package/ui/src/lib/components/ui/input/input.svelte +48 -0
  430. package/ui/src/lib/components/ui/input-otp/index.ts +15 -0
  431. package/ui/src/lib/components/ui/input-otp/input-otp-group.svelte +20 -0
  432. package/ui/src/lib/components/ui/input-otp/input-otp-separator.svelte +27 -0
  433. package/ui/src/lib/components/ui/input-otp/input-otp-slot.svelte +31 -0
  434. package/ui/src/lib/components/ui/input-otp/input-otp.svelte +23 -0
  435. package/ui/src/lib/components/ui/item/index.ts +34 -0
  436. package/ui/src/lib/components/ui/item/item-actions.svelte +20 -0
  437. package/ui/src/lib/components/ui/item/item-content.svelte +23 -0
  438. package/ui/src/lib/components/ui/item/item-description.svelte +23 -0
  439. package/ui/src/lib/components/ui/item/item-footer.svelte +20 -0
  440. package/ui/src/lib/components/ui/item/item-group.svelte +21 -0
  441. package/ui/src/lib/components/ui/item/item-header.svelte +20 -0
  442. package/ui/src/lib/components/ui/item/item-media.svelte +42 -0
  443. package/ui/src/lib/components/ui/item/item-separator.svelte +19 -0
  444. package/ui/src/lib/components/ui/item/item-title.svelte +20 -0
  445. package/ui/src/lib/components/ui/item/item.svelte +61 -0
  446. package/ui/src/lib/components/ui/kbd/index.ts +10 -0
  447. package/ui/src/lib/components/ui/kbd/kbd-group.svelte +20 -0
  448. package/ui/src/lib/components/ui/kbd/kbd.svelte +23 -0
  449. package/ui/src/lib/components/ui/label/index.ts +7 -0
  450. package/ui/src/lib/components/ui/label/label.svelte +20 -0
  451. package/ui/src/lib/components/ui/progress/index.ts +7 -0
  452. package/ui/src/lib/components/ui/progress/progress.svelte +27 -0
  453. package/ui/src/lib/components/ui/select/index.ts +37 -0
  454. package/ui/src/lib/components/ui/select/select-content.svelte +45 -0
  455. package/ui/src/lib/components/ui/select/select-group-heading.svelte +21 -0
  456. package/ui/src/lib/components/ui/select/select-group.svelte +17 -0
  457. package/ui/src/lib/components/ui/select/select-item.svelte +40 -0
  458. package/ui/src/lib/components/ui/select/select-label.svelte +20 -0
  459. package/ui/src/lib/components/ui/select/select-portal.svelte +7 -0
  460. package/ui/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
  461. package/ui/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
  462. package/ui/src/lib/components/ui/select/select-separator.svelte +18 -0
  463. package/ui/src/lib/components/ui/select/select-trigger.svelte +29 -0
  464. package/ui/src/lib/components/ui/select/select.svelte +11 -0
  465. package/ui/src/lib/components/ui/separator/index.ts +7 -0
  466. package/ui/src/lib/components/ui/separator/separator.svelte +23 -0
  467. package/ui/src/lib/components/ui/spinner/index.ts +1 -0
  468. package/ui/src/lib/components/ui/spinner/spinner.svelte +18 -0
  469. package/ui/src/lib/components/ui/tabs/index.ts +18 -0
  470. package/ui/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  471. package/ui/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
  472. package/ui/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
  473. package/ui/src/lib/components/ui/tabs/tabs.svelte +19 -0
  474. package/ui/src/lib/components/ui/textarea/index.ts +7 -0
  475. package/ui/src/lib/components/ui/textarea/textarea.svelte +22 -0
  476. package/ui/src/lib/intl/bcp.ts +25 -0
  477. package/ui/src/lib/intl/built.js +18 -0
  478. package/ui/src/lib/intl/context.yaml +18 -0
  479. package/ui/src/lib/intl/en-US.yaml +6 -0
  480. package/ui/src/lib/intl/index.ts +65 -0
  481. package/ui/src/lib/intl/types.ts +19 -0
  482. package/ui/src/lib/tools/ago.ts +10 -0
  483. package/ui/src/lib/tools/assert.ts +3 -0
  484. package/ui/src/lib/tools/cap.ts +5 -0
  485. package/ui/src/lib/tools/clicks.ts +25 -0
  486. package/ui/src/lib/tools/contextmenu.ts +19 -0
  487. package/ui/src/lib/tools/convert.ts +32 -0
  488. package/ui/src/lib/tools/currency.test.ts +10 -0
  489. package/ui/src/lib/tools/currency.ts +55 -0
  490. package/ui/src/lib/tools/date.test.ts +21 -0
  491. package/ui/src/lib/tools/date.ts +35 -0
  492. package/ui/src/lib/tools/delay.ts +7 -0
  493. package/ui/src/lib/tools/image.ts +39 -0
  494. package/ui/src/lib/tools/index.ts +20 -0
  495. package/ui/src/lib/tools/location.ts +41 -0
  496. package/ui/src/lib/tools/metronome.ts +11 -0
  497. package/ui/src/lib/tools/mq.ts +84 -0
  498. package/ui/src/lib/tools/onsubmit.ts +21 -0
  499. package/ui/src/lib/tools/random.ts +15 -0
  500. package/ui/src/lib/tools/search.ts +25 -0
  501. package/ui/src/lib/tools/timeout.ts +26 -0
  502. package/ui/src/lib/tools/transition.ts +207 -0
  503. package/ui/src/lib/tools/types.ts +3 -0
  504. package/ui/src/lib/tools/uuid.test.ts +28 -0
  505. package/ui/src/lib/tools/uuid.ts +9 -0
  506. package/ui/src/lib/typeset.css +502 -0
  507. package/ui/src/lib/utils.ts +12 -0
  508. package/ui/src/routes/(private)/+layout.svelte +112 -0
  509. package/ui/src/routes/(private)/+page.svelte +7 -0
  510. package/ui/src/routes/(private)/[component]/+page.svelte +77 -0
  511. package/ui/src/routes/+layout.svelte +25 -0
  512. package/ui/src/routes/+layout.ts +14 -0
  513. package/ui/src/routes/layout.css +232 -0
  514. package/ui/static/robots.txt +3 -0
  515. package/ui/svelte.config.js +36 -0
  516. package/ui/tools/feat.js +43 -0
  517. package/ui/tools/hooks/README.md +13 -0
  518. package/ui/tools/hooks/guard-intl.sh +40 -0
  519. package/ui/tools/shad.js +7 -0
  520. package/ui/tsconfig.json +20 -0
  521. package/ui/vite.config.ts +8 -0
package/source/UI.ts ADDED
@@ -0,0 +1,197 @@
1
+ import * as fs from 'node:fs'
2
+ import * as http from 'node:http'
3
+ import * as path from 'node:path'
4
+ import { Connector } from '@toa.io/core'
5
+ import { console } from 'openspan'
6
+ import { UI_PATH } from './const'
7
+
8
+ /**
9
+ * Serves the configuration UI: the directory `ui` builds, and nothing else.
10
+ *
11
+ * The page is a single-page application whose mount path is baked into the bundle,
12
+ * so this server routes relative to `UI_PATH` — which is also what the ingress
13
+ * forwards. `/configuration/*` is left alone: that is the component's own API.
14
+ */
15
+ export class UI extends Connector {
16
+ private readonly server: http.Server = http.createServer()
17
+ private readonly port: number
18
+ private readonly root: string
19
+
20
+ public constructor (port: number, root: string = SITE) {
21
+ super()
22
+
23
+ this.port = port
24
+ this.root = root
25
+ this.server.on('request', (request, response) => {
26
+ this.listen(request, response)
27
+ })
28
+ }
29
+
30
+ protected override async open (): Promise<void> {
31
+ /*
32
+ * A taken port is a real error: uniqueness across services is settled at export
33
+ * time, so nothing here has to negotiate for one.
34
+ */
35
+ await new Promise<void>((resolve, reject) => {
36
+ const failed = (error: Error): void => {
37
+ this.server.off('listening', listening)
38
+ reject(error)
39
+ }
40
+
41
+ const listening = (): void => {
42
+ this.server.off('error', failed)
43
+ resolve()
44
+ }
45
+
46
+ this.server.once('error', failed)
47
+ this.server.once('listening', listening)
48
+ this.server.listen(this.port)
49
+ })
50
+
51
+ if (!isFile(path.join(this.root, 'index.html')))
52
+ console.warn('Configuration UI is not built', { root: this.root })
53
+
54
+ console.info('Configuration UI started', { port: this.port, path: UI_PATH + '/' })
55
+ }
56
+
57
+ protected override async close (): Promise<void> {
58
+ const closed = new Promise<void>((resolve) => this.server.once('close', () => {
59
+ resolve()
60
+ }))
61
+
62
+ this.server.close()
63
+ this.server.closeAllConnections()
64
+
65
+ await closed
66
+ }
67
+
68
+ private listen (request: http.IncomingMessage, response: http.ServerResponse): void {
69
+ void this.respond(request, response).catch((error: Error) => {
70
+ console.error('Configuration UI failure', { message: error.message })
71
+
72
+ if (!response.writableEnded)
73
+ response.writeHead(500).end()
74
+ })
75
+ }
76
+
77
+ private async respond (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> {
78
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
79
+ response.writeHead(405, { allow: 'GET, HEAD' }).end()
80
+
81
+ return
82
+ }
83
+
84
+ const pathname = decode(request.url ?? '/')
85
+
86
+ if (pathname === null) {
87
+ response.writeHead(400).end()
88
+
89
+ return
90
+ }
91
+
92
+ // the port is the explorer's alone, so the root is the way in
93
+ if (pathname === '/') {
94
+ response.writeHead(302, { location: UI_PATH + '/' }).end()
95
+
96
+ return
97
+ }
98
+
99
+ const file = this.resolve(pathname)
100
+
101
+ if (file === null)
102
+ response.writeHead(404).end()
103
+ else
104
+ await this.send(file, request, response)
105
+ }
106
+
107
+ /**
108
+ * The file a request lands on, or `null` when nothing does. A path that exists is
109
+ * served as it is; anything else that could be a route falls back to the page,
110
+ * because the client router — not this server — knows what routes there are.
111
+ */
112
+ private resolve (pathname: string): string | null {
113
+ if (!pathname.startsWith(UI_PATH))
114
+ return null
115
+
116
+ const relative = pathname.slice(UI_PATH.length)
117
+
118
+ if (relative !== '' && !relative.startsWith('/'))
119
+ return null
120
+
121
+ const file = path.join(this.root, relative)
122
+
123
+ if (file !== this.root && !file.startsWith(this.root + path.sep))
124
+ return null
125
+
126
+ if (isFile(file))
127
+ return file
128
+
129
+ /*
130
+ * A missing asset is missing, but a route can look like one: `identity.passkeys` is a
131
+ * component, not a file with an extension. What this server would have served is what
132
+ * it knows how to serve, so anything else is a route and falls back to the page — as
133
+ * does anything ending in a slash, which is no name for a file at all.
134
+ */
135
+ const asset = !relative.endsWith('/') && path.extname(relative) in TYPES
136
+
137
+ return asset ? null : path.join(this.root, 'index.html')
138
+ }
139
+
140
+ private async send (file: string, request: http.IncomingMessage, response: http.ServerResponse): Promise<void> {
141
+ const stats = await fs.promises.stat(file).catch(() => null)
142
+
143
+ if (stats === null) {
144
+ response.writeHead(503, { 'content-type': 'text/plain' })
145
+ .end('The configuration UI is not built. Run `npm run build:ui`.\n')
146
+
147
+ return
148
+ }
149
+
150
+ response.writeHead(200, {
151
+ 'content-type': TYPES[path.extname(file)] ?? 'application/octet-stream',
152
+ 'content-length': stats.size,
153
+ 'cache-control': file.startsWith(path.join(this.root, IMMUTABLE)) ? FOREVER : 'no-cache'
154
+ })
155
+
156
+ if (request.method === 'HEAD')
157
+ response.end()
158
+ else
159
+ fs.createReadStream(file).pipe(response)
160
+ }
161
+ }
162
+
163
+ function decode (url: string): string | null {
164
+ try {
165
+ return decodeURIComponent(url.split('?')[0])
166
+ } catch {
167
+ return null
168
+ }
169
+ }
170
+
171
+ function isFile (file: string): boolean {
172
+ return fs.existsSync(file) && fs.statSync(file).isFile()
173
+ }
174
+
175
+ /** Where `npm run build:ui` puts the page, from both `source` and `transpiled`. */
176
+ const SITE = path.resolve(__dirname, '..', 'ui', 'dist')
177
+
178
+ /** Assets under this prefix carry their build hash in the name. */
179
+ const IMMUTABLE = path.join('_app', 'immutable')
180
+ const FOREVER = 'public, max-age=31536000, immutable'
181
+
182
+ const TYPES: Record<string, string> = {
183
+ '.css': 'text/css; charset=utf-8',
184
+ '.html': 'text/html; charset=utf-8',
185
+ '.ico': 'image/x-icon',
186
+ '.jpg': 'image/jpeg',
187
+ '.js': 'text/javascript; charset=utf-8',
188
+ '.json': 'application/json; charset=utf-8',
189
+ '.map': 'application/json; charset=utf-8',
190
+ '.png': 'image/png',
191
+ '.svg': 'image/svg+xml',
192
+ '.txt': 'text/plain; charset=utf-8',
193
+ '.webmanifest': 'application/manifest+json',
194
+ '.webp': 'image/webp',
195
+ '.woff': 'font/woff',
196
+ '.woff2': 'font/woff2'
197
+ }
@@ -1,6 +1,7 @@
1
1
  import { Locator } from '@toa.io/core'
2
2
  import { generate } from 'randomstring'
3
- import { get } from './configuration'
3
+ import { fit, local, overridden } from './configuration'
4
+ import { Secret } from './Secret'
4
5
  import { type Manifest } from './manifest'
5
6
 
6
7
  let locator: Locator
@@ -23,79 +24,110 @@ afterEach(() => {
23
24
  used = []
24
25
  })
25
26
 
26
- it('should read value', async () => {
27
- manifest.schema = {
28
- type: 'object',
29
- properties: { foo: { type: 'string' } }
30
- }
27
+ describe('overridden', () => {
28
+ it('should be false without the variable', async () => {
29
+ expect(overridden(locator)).toStrictEqual(false)
30
+ })
31
31
 
32
- const value: object = { foo: generate() }
32
+ it('should be true with the variable', async () => {
33
+ set({})
33
34
 
34
- set(value)
35
+ expect(overridden(locator)).toStrictEqual(true)
36
+ })
37
+ })
35
38
 
36
- const result = get(locator, manifest)
39
+ describe('local', () => {
40
+ it('should read value', async () => {
41
+ const value: object = { foo: generate() }
37
42
 
38
- expect(result).toStrictEqual(value)
39
- })
43
+ set(value)
40
44
 
41
- it('should return empty object if no value set', async () => {
42
- expect(get(locator, manifest)).toStrictEqual({})
43
- })
45
+ expect(local(locator, manifest)).toStrictEqual(value)
46
+ })
44
47
 
45
- it('should substitute secrets', async () => {
46
- const value: object = { foo: '$BAR' }
48
+ it('should return empty object if no value set', async () => {
49
+ expect(local(locator, manifest)).toStrictEqual({})
50
+ })
47
51
 
48
- set(value)
49
- set('bar', '_BAR')
52
+ it('should substitute secrets', async () => {
53
+ set({ foo: '$BAR' })
54
+ set('bar', '_BAR')
50
55
 
51
- const result = get(locator, manifest)
56
+ const { foo } = local(locator, manifest)
52
57
 
53
- expect(result).toStrictEqual({ foo: 'bar' })
54
- })
58
+ expect(foo).toBeInstanceOf(Secret)
59
+ expect((foo as Secret).unwrap()).toStrictEqual('bar')
60
+ expect(String(foo)).toStrictEqual('<REDACTED>')
61
+ })
55
62
 
56
- it('should use defaults', async () => {
57
- manifest.schema = {
58
- type: 'object',
59
- properties: {
60
- foo: { type: 'string' },
61
- bar: { type: 'array', items: { type: 'number' } },
62
- baz: { type: 'string' }
63
- },
64
- required: ['foo', 'bar']
65
- }
66
- manifest.defaults = { foo: 'bar', bar: [1] }
63
+ it('should substitute secrets in defaults', async () => {
64
+ manifest.defaults = { foo: '$BAR' }
67
65
 
68
- const values = { bar: [2], baz: 'foo' }
66
+ set('bar', '_BAR')
69
67
 
70
- set(values)
68
+ expect((local(locator, manifest).foo as Secret).unwrap()).toStrictEqual('bar')
69
+ })
71
70
 
72
- const result = get(locator, manifest)
71
+ it('should use defaults', async () => {
72
+ manifest.schema = {
73
+ type: 'object',
74
+ properties: {
75
+ foo: { type: 'string' },
76
+ bar: { type: 'array', items: { type: 'number' } },
77
+ baz: { type: 'string' }
78
+ },
79
+ required: ['foo', 'bar']
80
+ }
81
+ manifest.defaults = { foo: 'bar', bar: [1] }
73
82
 
74
- expect(result).toStrictEqual({
75
- foo: 'bar',
76
- bar: [2],
77
- baz: 'foo'
83
+ set({ bar: [2], baz: 'foo' })
84
+
85
+ expect(local(locator, manifest)).toStrictEqual({ foo: 'bar', bar: [2], baz: 'foo' })
86
+ })
87
+
88
+ it('should validate', async () => {
89
+ manifest.schema = {
90
+ type: 'object',
91
+ properties: {
92
+ foo: { type: 'string', default: 'hello' },
93
+ bar: { type: 'number' }
94
+ }
95
+ }
96
+
97
+ set({ bar: 5 })
98
+
99
+ expect(local(locator, manifest)).toStrictEqual({ foo: 'hello', bar: 5 })
78
100
  })
79
101
  })
80
102
 
81
- it('should validate', async () => {
82
- manifest.schema = {
83
- type: 'object',
84
- properties: {
85
- foo: { type: 'string', default: 'hello' },
86
- bar: { type: 'number' }
103
+ describe('fit', () => {
104
+ it('should substitute secrets and apply the schema', async () => {
105
+ manifest.schema = {
106
+ type: 'object',
107
+ properties: {
108
+ foo: { type: 'string' },
109
+ bar: { type: 'number', default: 1 }
110
+ }
87
111
  }
88
- }
89
112
 
90
- const values = { bar: 5 }
113
+ set('secret', '_FOO')
114
+
115
+ const raw = { foo: '$FOO' }
116
+ const values = fit(raw, manifest)
91
117
 
92
- set(values)
118
+ expect((values.foo as Secret).unwrap()).toStrictEqual('secret')
119
+ expect(values.bar).toStrictEqual(1)
120
+ expect(raw).toStrictEqual({ foo: '$FOO' }) // untouched
121
+ })
122
+
123
+ it('should not apply the manifest defaults', async () => {
124
+ manifest.defaults = { foo: 'hello' }
93
125
 
94
- const result = get(locator, manifest)
126
+ expect(fit({}, manifest)).toStrictEqual({})
127
+ })
95
128
 
96
- expect(result).toStrictEqual({
97
- foo: 'hello',
98
- bar: 5
129
+ it('should throw on a value not fitting the schema', async () => {
130
+ expect(() => fit({ foo: { nested: true } }, manifest)).toThrow()
99
131
  })
100
132
  })
101
133
 
@@ -1,26 +1,45 @@
1
1
  import { type Locator } from '@toa.io/core'
2
2
  import { add } from '@toa.io/generic'
3
3
  import * as schemas from '@toa.io/schemas'
4
- import { PREFIX, SECRET_RX } from './deployment'
5
- import { type Manifest } from './manifest'
4
+ import { PREFIX, SECRET_RX } from './const'
5
+ import { Secret } from './Secret'
6
6
  import type { Schema } from '@toa.io/schemas'
7
+ import type { Manifest } from './manifest'
7
8
 
8
- export function get (locator: Locator, manifest: Manifest): Node {
9
- const values = getConfiguration(locator.uppercase)
9
+ /** The variable is set, so the values service is not consulted. */
10
+ export function overridden (locator: Locator): boolean {
11
+ return process.env[PREFIX + locator.uppercase] !== undefined
12
+ }
10
13
 
11
- substituteSecrets(values)
14
+ /** The variable, the manifest defaults, then the schema. */
15
+ export function local (locator: Locator, manifest: Manifest): Node {
16
+ const values = read(locator.uppercase)
12
17
 
13
18
  if (manifest.defaults !== undefined)
14
19
  add(values, manifest.defaults)
15
20
 
16
- const schema: Schema<any> = schemas.schema(manifest.schema)
21
+ return fit(values, manifest)
22
+ }
17
23
 
18
- schema.validate(values)
24
+ /** A copy of the value with the schema applied and the secrets substituted. */
25
+ export function fit (raw: object, manifest: Manifest): Node {
26
+ // a copy of the caller's, and one of this realm: what came over the wire is JSON anyway
27
+ const values = JSON.parse(JSON.stringify(raw)) as Node
28
+
29
+ // the schema sees the references, which are the strings it declares
30
+ validate(values, manifest)
31
+ substituteSecrets(values)
19
32
 
20
33
  return values
21
34
  }
22
35
 
23
- function getConfiguration (suffix: string): Node {
36
+ function validate (values: Node, manifest: Manifest): void {
37
+ const schema: Schema<any> = schemas.schema(manifest.schema)
38
+
39
+ schema.validate(values)
40
+ }
41
+
42
+ function read (suffix: string): Node {
24
43
  const variable = PREFIX + suffix
25
44
  const string = process.env[variable]
26
45
 
@@ -43,7 +62,7 @@ function substituteSecrets (configuration: Node): void {
43
62
 
44
63
  const name = match.groups?.variable
45
64
 
46
- configuration[key] = getSecret(name!)
65
+ configuration[key] = new Secret(getSecret(name!))
47
66
  }
48
67
  }
49
68
 
@@ -0,0 +1,17 @@
1
+ import type { Source } from '@toa.io/core'
2
+
3
+ /** Per-component variables: the local override and the secrets. */
4
+ export const PREFIX = 'TOA_CONFIGURATION_'
5
+
6
+ /** The map of every configured component, on the values service. */
7
+ export const VALUES = 'TOA_CONFIGURATION_VALUES'
8
+
9
+ export const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
10
+
11
+ /** Where the UI is mounted; `/configuration/*` belongs to the component's own API. */
12
+ export const UI_PATH = '/.configuration'
13
+ export const UI_PORT = 8003
14
+
15
+ export const EVENT = 'configuration.values.created'
16
+
17
+ export const SOURCE: Source = { service: 'configuration' }
@@ -1,7 +1,86 @@
1
- import { type Annotation, deployment } from './deployment'
1
+ import { Locator } from '@toa.io/core'
2
+ import { type Annotation, type Instance, deployment, describe as map } from './deployment'
3
+ import { epoch } from './epoch'
4
+ import { EVENT, VALUES } from './const'
5
+
6
+ const schema = {
7
+ type: 'object',
8
+ properties: { foo: { type: 'string' }, key: { type: 'string' } }
9
+ }
10
+
11
+ function instance (name: string, defaults?: Record<string, unknown>): Instance {
12
+ return {
13
+ locator: new Locator(name, 'configuration'),
14
+ manifest: { schema, defaults },
15
+ component: {} as any
16
+ }
17
+ }
2
18
 
3
19
  it('should validate annotation', async () => {
4
20
  const wrongType = 'not ok' as unknown as Annotation
5
21
 
6
22
  expect(() => deployment([], wrongType)).toThrow('object')
7
23
  })
24
+
25
+ it('should reject unknown components', async () => {
26
+ expect(() => deployment([instance('base')], { 'configuration.nope': {} }))
27
+ .toThrow("Component 'configuration.nope' does not request configuration")
28
+ })
29
+
30
+ it('should deploy the values service with the map', async () => {
31
+ const instances = [instance('base', { foo: 'hello' }), instance('other')]
32
+ const annotation = { 'configuration.other': { foo: 'set' } }
33
+
34
+ const dependency = deployment(instances, annotation)
35
+
36
+ expect(dependency.events).toStrictEqual([EVENT])
37
+ expect(dependency.services).toHaveLength(1)
38
+
39
+ const service = dependency.services![0]
40
+
41
+ expect(service.group).toStrictEqual('configuration')
42
+ expect(service.name).toStrictEqual('values')
43
+ expect(service.components).toStrictEqual(['configuration-values'])
44
+
45
+ const variable = service.variables!.find((variable) => variable.name === VALUES)
46
+
47
+ expect(variable).toBeDefined()
48
+ expect(JSON.parse(variable!.value!)).toStrictEqual(map(instances, annotation))
49
+ })
50
+
51
+ it('should describe every component', async () => {
52
+ const instances = [instance('base', { foo: 'hello' }), instance('other')]
53
+ const values = map(instances, { 'configuration.other': { foo: 'set' } })
54
+
55
+ expect(values).toStrictEqual({
56
+ 'configuration.base': { epoch: epoch(schema), schema, defaults: { foo: 'hello' } },
57
+ 'configuration.other': { epoch: epoch(schema), schema, defaults: { foo: 'set' } }
58
+ })
59
+ })
60
+
61
+ it('should prefer the context over the manifest defaults', async () => {
62
+ const values = map([instance('base', { foo: 'hello' })], { 'configuration.base': { foo: 'bye' } })
63
+
64
+ expect(values['configuration.base'].defaults).toStrictEqual({ foo: 'bye' })
65
+ })
66
+
67
+ it('should not map values to the component', async () => {
68
+ const dependency = deployment([instance('base')], { 'configuration.base': { foo: 'set' } })
69
+
70
+ expect(dependency.variables).toStrictEqual({})
71
+ })
72
+
73
+ it('should map secrets to the component', async () => {
74
+ const dependency = deployment([instance('base')], { 'configuration.base': { key: '$KEY' } })
75
+
76
+ expect(dependency.variables).toStrictEqual({
77
+ 'configuration-base': [{
78
+ name: 'TOA_CONFIGURATION__KEY',
79
+ secret: { name: 'toa-configuration', key: 'KEY' }
80
+ }]
81
+ })
82
+
83
+ // the service holds the reference, not the secret
84
+ expect(map([instance('base')], { 'configuration.base': { key: '$KEY' } })['configuration.base'].defaults)
85
+ .toStrictEqual({ key: '$KEY' })
86
+ })
@@ -1,11 +1,16 @@
1
1
  import assert from 'node:assert'
2
- import { type Dependency, type Variable, type Variables } from '@toa.io/operations'
3
- import { type Manifest } from './manifest'
2
+ import { type Dependency, type Resources, type Service, type Variable, type Variables } from '@toa.io/operations'
3
+ import { components } from './Composition'
4
+ import { EVENT, PREFIX, SECRET_RX, UI_PATH, UI_PORT, VALUES } from './const'
5
+ import { epoch } from './epoch'
4
6
  import * as validators from './schemas'
7
+ import type { Manifest } from './manifest'
5
8
  import type { context } from '@toa.io/norm'
6
9
 
7
10
  export function deployment (instances: Instance[], annotation: Annotation = {}): Dependency {
8
- validate(annotation, instances)
11
+ const { resources, values } = split(annotation)
12
+
13
+ annotation = prepare(values, instances)
9
14
 
10
15
  const variables: Variables = {}
11
16
 
@@ -15,17 +20,44 @@ export function deployment (instances: Instance[], annotation: Annotation = {}):
15
20
  if (values === undefined)
16
21
  continue
17
22
 
18
- variables[instance.locator.label] = [{
19
- name: PREFIX + instance.locator.uppercase,
20
- value: JSON.stringify(values)
21
- }]
22
-
23
23
  const secrets = createSecrets(values)
24
24
 
25
- variables[instance.locator.label].push(...secrets)
25
+ if (secrets.length > 0)
26
+ variables[instance.locator.label] = secrets
27
+ }
28
+
29
+ const service: Service = {
30
+ group: 'configuration',
31
+ name: 'values',
32
+ version: require('../package.json').version,
33
+ components: components().labels,
34
+ resources,
35
+ // the service that holds the values also serves the page that reads them
36
+ port: UI_PORT,
37
+ ingress: { path: UI_PATH },
38
+ variables: [{
39
+ name: VALUES,
40
+ value: JSON.stringify(describe(instances, annotation))
41
+ }]
26
42
  }
27
43
 
28
- return { variables }
44
+ return { services: [service], variables, events: [EVENT] }
45
+ }
46
+
47
+ /** What the values service is given: the epoch, the schema and the defaults of every component. */
48
+ export function describe (instances: Instance[], annotation: Annotation = {}): Values {
49
+ annotation = prepare(split(annotation).values, instances)
50
+
51
+ const values: Values = {}
52
+
53
+ for (const { locator, manifest } of instances)
54
+ values[locator.id] = {
55
+ epoch: epoch(manifest.schema),
56
+ schema: manifest.schema,
57
+ defaults: annotation[locator.id] ?? manifest.defaults
58
+ }
59
+
60
+ return values
29
61
  }
30
62
 
31
63
  function createSecrets (values: object): Variable[] {
@@ -57,27 +89,45 @@ function createSecrets (values: object): Variable[] {
57
89
  return secrets
58
90
  }
59
91
 
60
- function validate (annotation: Annotation, instances: Instance[]): void {
92
+ /**
93
+ * The service's own resources, and the component values that are the rest of the annotation.
94
+ *
95
+ * Every key here names a component, so the one option the service has of its own needs a
96
+ * name that cannot be mistaken for one. `resources` is reserved: a component actually
97
+ * called that is written with its namespace, `default.resources`, which is what an id is
98
+ * anyway — the bare form is the shorthand.
99
+ */
100
+ function split (annotation: Annotation): { resources?: Resources, values: Annotation } {
61
101
  validators.annotation.validate(annotation)
62
102
 
63
- const requested = instances.map((instance) => instance.locator.id)
103
+ const { resources, ...values } = annotation as Annotation & { resources?: Resources }
104
+
105
+ return { resources, values }
106
+ }
64
107
 
65
- for (let id of Object.keys(annotation)) {
66
- if (!id.includes('.')) {
67
- const newid = 'default.' + id
108
+ /** Validated, keyed by full component ids, and checked against the components that ask. */
109
+ function prepare (annotation: Annotation, instances: Instance[]): Annotation {
110
+ const normalized: Annotation = {}
111
+ const requested = instances.map((instance) => instance.locator.id)
68
112
 
69
- annotation[newid] = annotation[id]
70
- delete annotation[id]
71
- id = newid
72
- }
113
+ for (const [key, values] of Object.entries(annotation)) {
114
+ const id = key.includes('.') ? key : 'default.' + key
73
115
 
74
116
  assert.ok(requested.includes(id),
75
117
  `Component '${id}' does not request configuration or does not exist.`)
118
+
119
+ normalized[id] = values
76
120
  }
77
- }
78
121
 
79
- export const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
80
- export const PREFIX = 'TOA_CONFIGURATION_'
122
+ return normalized
123
+ }
81
124
 
82
- export type Annotation = Record<string, object>
125
+ export type Annotation = Record<string, any>
83
126
  export type Instance = context.Dependency<Manifest>
127
+ export type Values = Record<string, Entry>
128
+
129
+ export interface Entry {
130
+ epoch: string
131
+ schema: object
132
+ defaults?: object
133
+ }