@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
@@ -0,0 +1,88 @@
1
+ import assert from 'node:assert'
2
+ import * as http from 'node:http'
3
+ import { resolve } from 'node:path'
4
+ import { after, binding, given, then, when } from 'cucumber-tsflow'
5
+ import { UI } from '../../source/UI'
6
+ import { UI_PORT } from '../../source/const'
7
+
8
+ /**
9
+ * The scenarios are about the server, not about the page: it is pointed at a fixture
10
+ * directory, so that running them never needs a UI build.
11
+ */
12
+ @binding()
13
+ export class Site {
14
+ private server: UI | null = null
15
+ private response: Response | null = null
16
+
17
+ @given('the UI is published')
18
+ public async publish (): Promise<void> {
19
+ this.server = new UI(UI_PORT, resolve(__dirname, '..', 'site'))
20
+
21
+ await this.server.connect()
22
+ }
23
+
24
+ @when('{string} is requested')
25
+ public async request (path: string): Promise<void> {
26
+ this.response = await get(path)
27
+ }
28
+
29
+ @then('the status is {int}')
30
+ public statusIs (status: number): void {
31
+ assert.equal(this.response?.status, status)
32
+ }
33
+
34
+ @then('the body contains {string}')
35
+ public bodyContains (text: string): void {
36
+ assert.ok(this.response?.body.includes(text),
37
+ `Expected the body to contain '${text}', got '${this.response?.body}'`)
38
+ }
39
+
40
+ @then('the body is empty')
41
+ public bodyIsEmpty (): void {
42
+ assert.equal(this.response?.body, '')
43
+ }
44
+
45
+ @then('the {string} header is {string}')
46
+ public headerIs (name: string, value: string): void {
47
+ assert.equal(this.response?.headers[name], value)
48
+ }
49
+
50
+ @after()
51
+ public async shutdown (): Promise<void> {
52
+ await this.server?.disconnect()
53
+
54
+ this.server = null
55
+ this.response = null
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Raw `http`, not `fetch`: the URL parser normalizes `%2e%2e` away, and one of the
61
+ * scenarios is about exactly that segment reaching the server.
62
+ */
63
+ async function get (path: string): Promise<Response> {
64
+ return await new Promise((resolve, reject) => {
65
+ // `agent: false` — a keep-alive socket outlives the scenario that opened it
66
+ const request = http.get({ port: UI_PORT, path, agent: false }, (response) => {
67
+ let body = ''
68
+
69
+ response.setEncoding('utf8')
70
+ response.on('data', (chunk: string) => (body += chunk))
71
+ response.on('end', () => {
72
+ resolve({
73
+ status: response.statusCode!,
74
+ headers: response.headers as Record<string, string>,
75
+ body
76
+ })
77
+ })
78
+ })
79
+
80
+ request.on('error', reject)
81
+ })
82
+ }
83
+
84
+ interface Response {
85
+ status: number
86
+ headers: Record<string, string>
87
+ body: string
88
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "noEmit": true,
5
+ "moduleResolution": "node",
6
+ "experimentalDecorators": true,
7
+ "emitDecoratorMetadata": true
8
+ }
9
+ }
@@ -0,0 +1,85 @@
1
+ @ui
2
+ Feature: Publishing the UI
3
+
4
+ The values service serves the UI on its own port, out of the directory `ui` builds. The
5
+ mount path is baked into the bundle and forwarded by the ingress, so the server routes
6
+ relative to it.
7
+
8
+ Scenario: The root is the way in
9
+ Given the UI is published
10
+ When "/" is requested
11
+ Then the status is 302
12
+ And the "location" header is "/.configuration/"
13
+
14
+ Scenario: The UI answers at its mount path
15
+ Given the UI is published
16
+ When "/.configuration" is requested
17
+ Then the status is 200
18
+ And the body contains "the page"
19
+
20
+ Scenario: A trailing slash is the same resource
21
+ Given the UI is published
22
+ When "/.configuration/" is requested
23
+ Then the status is 200
24
+ And the body contains "the page"
25
+
26
+ Scenario: A query string does not change the resource
27
+ Given the UI is published
28
+ When "/.configuration?epoch=e1" is requested
29
+ Then the status is 200
30
+ And the body contains "the page"
31
+
32
+ Scenario: An unknown route is the page
33
+ The client router knows what routes there are; this server does not.
34
+
35
+ Given the UI is published
36
+ When "/.configuration/whatever" is requested
37
+ Then the status is 200
38
+ And the body contains "the page"
39
+
40
+ Scenario: A route that looks like a file is still a route
41
+ A component is named `namespace.component`, and the address of its configuration
42
+ carries that name — so an extension is no way to tell an asset from a route.
43
+
44
+ Given the UI is published
45
+ When "/.configuration/dummies.dummy/" is requested
46
+ Then the status is 200
47
+ And the body contains "the page"
48
+
49
+ Scenario: And with no trailing slash to give it away
50
+ Given the UI is published
51
+ When "/.configuration/dummies.dummy" is requested
52
+ Then the status is 200
53
+ And the body contains "the page"
54
+
55
+ Scenario: A hashed asset is served forever
56
+ Given the UI is published
57
+ When "/.configuration/_app/immutable/asset.js" is requested
58
+ Then the status is 200
59
+ And the "content-type" header is "text/javascript; charset=utf-8"
60
+ And the "cache-control" header is "public, max-age=31536000, immutable"
61
+
62
+ Scenario: The page itself is never cached
63
+ Given the UI is published
64
+ When "/.configuration/" is requested
65
+ Then the "cache-control" header is "no-cache"
66
+
67
+ Scenario: A missing asset is missing
68
+ Given the UI is published
69
+ When "/.configuration/_app/immutable/absent.js" is requested
70
+ Then the status is 404
71
+
72
+ Scenario: The path of the values API is not served by the UI
73
+ Given the UI is published
74
+ When "/configuration/values/" is requested
75
+ Then the status is 404
76
+
77
+ Scenario: A path that only looks like the mount path is absent
78
+ Given the UI is published
79
+ When "/.configurationable" is requested
80
+ Then the status is 404
81
+
82
+ Scenario: Nothing outside the site is reachable
83
+ Given the UI is published
84
+ When "/.configuration/%2e%2e/%2e%2e/package.json" is requested
85
+ Then the status is 404
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.configuration",
3
- "version": "1.0.0-alpha.273",
3
+ "version": "1.0.0-alpha.274",
4
4
  "description": "Toa Configuration",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -17,16 +17,23 @@
17
17
  "access": "public"
18
18
  },
19
19
  "scripts": {
20
- "transpile": "tsc"
20
+ "transpile": "tsc && tsc -p ./components/configuration.values",
21
+ "build:ui": "npm --prefix ui ci && npm --prefix ui run build",
22
+ "prepack": "npm run build:ui",
23
+ "features": "npx cucumber-js"
21
24
  },
22
25
  "jest": {
23
26
  "preset": "ts-jest",
24
- "testEnvironment": "node"
27
+ "testEnvironment": "node",
28
+ "testPathIgnorePatterns": [
29
+ "<rootDir>/ui"
30
+ ]
25
31
  },
26
- "gitHead": "ad3284850ece95ffd5325fd0995707fc144c9c3d",
32
+ "gitHead": "7df32eb2872155c45681c299036b1efbeefd6518",
27
33
  "dependencies": {
28
34
  "@toa.io/core": "1.0.0-alpha.273",
29
35
  "@toa.io/generic": "1.0.0-alpha.273",
30
- "@toa.io/schemas": "1.0.0-alpha.273"
36
+ "@toa.io/schemas": "1.0.0-alpha.274",
37
+ "openspan": "1.0.0-alpha.272"
31
38
  }
32
39
  }
package/readme.md CHANGED
@@ -49,22 +49,28 @@ configuration:
49
49
  $ toa conceal configuration BAZ_VALUE=$ecr3t
50
50
  ```
51
51
 
52
- ---
52
+ ### Change at runtime
53
53
 
54
- ## Problem Definition
54
+ ```http
55
+ POST /configuration/values/dummies.dummy/ HTTP/1.1
56
+ authorization: Token ...
57
+ content-type: application/yaml
55
58
 
56
- - Components should be runnable in different deployment environments.
57
- - Some algorithm's parameters should be deployed secretly.
58
- - Components should be reusable in different contexts.
59
+ configuration:
60
+ foo: quux
61
+ bar: 2
62
+ ```
63
+
64
+ ---
59
65
 
60
66
  ## Manifest
61
67
 
62
- Component's configuration is declared using `configuration` manifest,
63
- containing `schema` and optionnaly `defaults` properties.
68
+ Component's configuration is declared using the `configuration` manifest, containing `schema`
69
+ and optionally `defaults` properties.
64
70
 
65
71
  ### Schema
66
72
 
67
- Configuration schema is declared with [JSONSchema](https://json-schema.org).
73
+ Configuration schema is declared with [JSON Schema](https://json-schema.org).
68
74
 
69
75
  ```yaml
70
76
  # manifest.toa.yaml
@@ -73,34 +79,18 @@ namespace: dummies
73
79
 
74
80
  configuration:
75
81
  schema:
76
- foo: string
77
- bar: number
78
- ```
79
-
80
- > Introducing non-backward compatible changes to a configuration schema will result in a loss of
81
- > compatibility with existing contexts and deployment environments.
82
- > Therefore, configuration schema changes are subject to component versioning.
83
-
84
- If `configuration` object doesn't contain property `schema`, then it is considered to be schema.
85
-
86
- ```yaml
87
- # manifest.toa.yaml
88
- name: dummy
89
- namespace: dummies
90
-
91
- configuration:
92
- type: object
93
- properties:
94
- foo:
95
- type: string
96
- bar:
97
- type: number
82
+ type: object
83
+ properties:
84
+ foo:
85
+ type: string
86
+ bar:
87
+ type: number
98
88
  ```
99
89
 
100
90
  ### Defaults
101
91
 
102
- The default configuration value can be provided using the `defaults` property, which should conform
103
- to the configuration schema.
92
+ The default configuration value can be provided using the `defaults` property, which should
93
+ conform to the configuration schema.
104
94
 
105
95
  ```yaml
106
96
  # manifest.toa.yaml
@@ -120,6 +110,12 @@ configuration:
120
110
  bar: 0
121
111
  ```
122
112
 
113
+ ### Epoch
114
+
115
+ The configuration epoch of a component is the SHA-256 of its configuration schema, as
116
+ canonical JSON. A configuration object belongs to the epoch of the schema it was validated
117
+ against. A schema change is a new epoch.
118
+
123
119
  ## Annotation
124
120
 
125
121
  A component's configuration can be overridden using the configuration context annotation.
@@ -133,9 +129,26 @@ configuration:
133
129
  bar@staging: 2
134
130
  ```
135
131
 
132
+ The annotated object is deployed as the defaults of the component for its epoch, in place of
133
+ the manifest `defaults`.
134
+
135
+ Every key names a component except `resources`, which is the values service's own: it deploys
136
+ like any other and states what it may take. A component actually named `resources` is written
137
+ with its namespace, `default.resources` — the bare form is only shorthand for that.
138
+
139
+ ```yaml
140
+ # context.toa.yaml
141
+ configuration:
142
+ resources:
143
+ cpu: [200m, 1000m]
144
+ memory: [200Mi, 500Mi]
145
+ dummies.dummy:
146
+ foo: bye
147
+ ```
148
+
136
149
  ## Secrets
137
150
 
138
- Configuration annotation top-level values which are uppercase strings prefixed with `$` considered as secrets.
151
+ Configuration values which are uppercase strings prefixed with `$` are considered as secrets.
139
152
 
140
153
  ```yaml
141
154
  # context.toa.yaml
@@ -155,14 +168,117 @@ Deployed kubernetes secret's name is predefined as `configuration`.
155
168
  $ toa conceal configuration STRIPE_API_KEY=xxxxxxxx
156
169
  ```
157
170
 
171
+ A secret is substituted in the component's process, from the variable
172
+ `TOA_CONFIGURATION__<NAME>` deployed to it. The values service holds and returns the
173
+ reference.
174
+
175
+ In the component, a secret is a `Secret` object: `unwrap()` returns the string, while
176
+ `toString()`, JSON and `util.inspect` give `<REDACTED>`.
177
+
178
+ ```javascript
179
+ function transition (input, entity, context) {
180
+ const key = context.configuration.apiKey.unwrap()
181
+
182
+ // ...
183
+ }
184
+ ```
185
+
186
+ A value a component reads as a secret is given as a reference: a plain string in its place
187
+ has no `unwrap`.
188
+
189
+ ## Values
190
+
191
+ Configuration values are held by the `configuration.values` component, deployed as the
192
+ `configuration-values` service. The service is deployed with the variable
193
+ `TOA_CONFIGURATION_VALUES`: the epoch, the schema and the defaults of every component
194
+ declaring configuration.
195
+
196
+ ```json
197
+ {
198
+ "dummies.dummy": {
199
+ "epoch": "3f2a…",
200
+ "schema": { "type": "object", "properties": { "foo": { "type": "string" } } },
201
+ "defaults": { "foo": "bye" }
202
+ }
203
+ }
204
+ ```
205
+
206
+ Configuration objects are immutable. Creating a configuration is creating a new object for
207
+ the component's current epoch; the latest object for a component and an epoch is the last
208
+ created one. Each object records its `originator`.
209
+
210
+ The configuration of a component for an epoch is:
211
+
212
+ 1. The latest object created for the component and the epoch;
213
+ 2. Otherwise, the deployed defaults, if the epoch is the deployed one;
214
+ 3. Otherwise, none.
215
+
216
+ ### Operations
217
+
218
+ - `get({ component, epoch? })`: the configuration with the schema it is checked against, as
219
+ `{ configuration, schema, epoch }`, or `null` when there is none. The epoch is the deployed
220
+ one when omitted; an epoch the deployment does not know has no `schema`.
221
+ - `fetch([{ component, epoch }])`: the same for several pairs at once, as
222
+ `[{ component, epoch, configuration }]`.
223
+ - `list()`: every component's configuration for its deployed epoch, by component name, as
224
+ `[{ component, epoch, schema, configuration }]`.
225
+ - `create({ component, configuration, originator })`: a new object for the component's
226
+ deployed epoch. The configuration must satisfy the schema. Errors: `UNKNOWN_COMPONENT`,
227
+ `INVALID_CONFIGURATION`.
228
+
229
+ Creating a configuration publishes the `configuration.values.created` event with the
230
+ object as stored.
231
+
232
+ ### Resources
233
+
234
+ | Method | Path | Role |
235
+ |--------|---------------------------------------|-------------------------------|
236
+ | `GET` | `/configuration/values/` | `system:configuration:get` |
237
+ | `GET` | `/configuration/values/:component/` | `system:configuration:get` |
238
+ | `POST` | `/configuration/values/:component/` | `system:configuration:create` |
239
+
240
+ `GET /configuration/values/` lists every component's configuration for its deployed epoch, by
241
+ component name, as `[{ component, epoch, schema, configuration }]`.
242
+
243
+ `GET /configuration/values/:component/` returns `{ configuration, schema, epoch }` for the
244
+ deployed epoch, `404` when there is none.
245
+
246
+ `POST` takes `{ configuration }`, records the Identity as the `originator`, and returns
247
+ `{ id, epoch }`. A configuration not satisfying the schema, or an unknown component, is
248
+ `422`.
249
+
250
+ ## UI
251
+
252
+ The values service serves a page listing the configured components and creating
253
+ configurations, mounted at `/.configuration` on port `8003`. Reading it needs the
254
+ `system:configuration:get` role, creating needs `system:configuration:create`.
255
+
256
+ The page is always published: unlike the introspection annotation, the configuration
257
+ annotation is the per-component values map and has nowhere to carry a switch.
258
+
158
259
  ## Aspect
159
260
 
160
261
  Component's configuration values are available as a well-known Aspect `configuration`.
161
262
 
162
263
  ```javascript
163
264
  function transition (input, entity, context) {
164
- const foo = context.configiuration.foo
265
+ const foo = context.configuration.foo
165
266
 
166
267
  // ...
167
268
  }
168
269
  ```
270
+
271
+ On start, a component requests its configuration for its epoch from the values service and
272
+ waits until there is one, reporting every fifth attempt. The schema is applied, and secrets
273
+ are substituted. After a configuration is created, the running component receives the new
274
+ object and takes it when its `_created` is later than that of the value it holds.
275
+
276
+ ### Local override
277
+
278
+ When the variable `TOA_CONFIGURATION_<NAMESPACE>_<NAME>` is set, the component's
279
+ configuration is the variable's value with the manifest `defaults` and the schema applied,
280
+ and the values service is not used.
281
+
282
+ ```shell
283
+ $ TOA_CONFIGURATION_DUMMIES_DUMMY='{"foo":"local"}' toa run components/dummy
284
+ ```
@@ -1,4 +1,29 @@
1
- type: object
1
+ # context.toa.yaml configuration annotation
2
+ #
3
+ # Every key names a component and holds that component's values, except `resources`, which
4
+ # is the values service's own: it deploys like any other and has to state what it may take.
5
+ # A component actually named `resources` is written with its namespace, `default.resources`
6
+ # — the bare form is only shorthand for that.
7
+ properties:
8
+ resources:
9
+ # `null` deploys the service without any, which every deployment has to state one way
10
+ # or the other — see `migrations/263.md`.
11
+ type: object
12
+ nullable: true
13
+ properties:
14
+ cpu:
15
+ type: array
16
+ items:
17
+ type: string
18
+ minItems: 2
19
+ maxItems: 2
20
+ memory:
21
+ type: array
22
+ items:
23
+ type: string
24
+ minItems: 2
25
+ maxItems: 2
2
26
  patternProperties:
3
- ^.+$:
27
+ ^(?!resources$).+$:
4
28
  type: object
29
+ type: object
@@ -1,15 +1,91 @@
1
+ import { Connector, Locator } from '@toa.io/core'
2
+ import { generate } from 'randomstring'
1
3
  import { Aspect } from './Aspect'
4
+ import type { Client, Listener } from './Client'
5
+ import type { Manifest } from './manifest'
2
6
 
3
- it('should return value', async () => {
4
- const configuration = {
5
- foo: 'bar',
6
- bar: {
7
- baz: 'quux'
7
+ class Fake extends Connector {
8
+ public readonly fetch = jest.fn(async () => ({ configuration: { foo: 'served' }, created: 5 }))
9
+ public readonly subscribe = jest.fn()
10
+ public readonly unsubscribe = jest.fn()
11
+ }
12
+
13
+ const manifest: Manifest = {
14
+ schema: {
15
+ type: 'object',
16
+ properties: {
17
+ foo: { type: 'string' },
18
+ bar: { type: 'object', properties: { baz: { type: 'string' } }, default: { baz: 'quux' } }
8
19
  }
9
20
  }
21
+ }
22
+
23
+ let locator: Locator
24
+
25
+ beforeEach(() => {
26
+ locator = new Locator(generate(), generate())
27
+ })
28
+
29
+ afterEach(() => {
30
+ delete process.env['TOA_CONFIGURATION_' + locator.uppercase]
31
+ })
32
+
33
+ it('should be named', async () => {
34
+ expect(new Aspect(locator, manifest, null).name).toStrictEqual('configuration')
35
+ })
36
+
37
+ it('should resolve locally without a client', async () => {
38
+ process.env['TOA_CONFIGURATION_' + locator.uppercase] = JSON.stringify({ foo: 'local' })
39
+
40
+ const aspect = new Aspect(locator, manifest, null)
41
+
42
+ await aspect.connect()
43
+
44
+ expect(aspect.invoke()).toStrictEqual({ foo: 'local', bar: { baz: 'quux' } })
45
+ expect(aspect.invoke(['foo'])).toStrictEqual('local')
46
+ expect(aspect.invoke(['bar', 'baz'])).toStrictEqual('quux')
47
+ })
48
+
49
+ it('should fetch from the client and follow it', async () => {
50
+ const client = new Fake()
51
+ const aspect = new Aspect(locator, manifest, client as unknown as Client)
52
+
53
+ await aspect.connect()
54
+
55
+ expect(client.connected).toStrictEqual(true)
56
+ expect(client.fetch).toHaveBeenCalledTimes(1)
57
+
58
+ const [component, epoch] = client.fetch.mock.calls[0] as unknown as [string, string]
59
+
60
+ expect(component).toStrictEqual(locator.id)
61
+ expect(epoch).toMatch(/^[a-f0-9]{64}$/)
62
+ expect(aspect.invoke()).toStrictEqual({ foo: 'served', bar: { baz: 'quux' } })
63
+
64
+ expect(client.subscribe).toHaveBeenCalledWith(component, epoch, expect.any(Function))
65
+
66
+ const listener = client.subscribe.mock.calls[0][2] as Listener
67
+
68
+ listener({ configuration: { foo: 'updated' }, created: 6 })
69
+
70
+ expect(aspect.invoke(['foo'])).toStrictEqual('updated')
71
+
72
+ // what is not newer than the held value is left alone
73
+ listener({ configuration: { foo: 'stale' }, created: 6 })
74
+ listener({ configuration: { foo: 'older' }, created: 4 })
75
+
76
+ expect(aspect.invoke(['foo'])).toStrictEqual('updated')
77
+
78
+ // a value that does not fit keeps the previous one
79
+ listener({ configuration: { foo: { nested: true } }, created: 7 })
80
+
81
+ expect(aspect.invoke(['foo'])).toStrictEqual('updated')
82
+
83
+ // and the one after it still applies
84
+ listener({ configuration: { foo: 'latest' }, created: 8 })
85
+
86
+ expect(aspect.invoke(['foo'])).toStrictEqual('latest')
10
87
 
11
- const aspect = new Aspect(configuration)
88
+ await aspect.disconnect()
12
89
 
13
- expect(aspect.invoke(['foo'])).toStrictEqual(configuration.foo)
14
- expect(aspect.invoke(['bar', 'baz'])).toStrictEqual(configuration.bar.baz)
90
+ expect(client.unsubscribe).toHaveBeenCalledWith(component, epoch, listener)
15
91
  })
package/source/Aspect.ts CHANGED
@@ -1,17 +1,37 @@
1
- import { Connector, type extensions } from '@toa.io/core'
1
+ import { console } from 'openspan'
2
+ import { Connector, type Locator, type extensions } from '@toa.io/core'
3
+ import { fit, local, type Node } from './configuration'
4
+ import { epoch } from './epoch'
5
+ import type { Client, Value } from './Client'
6
+ import type { Manifest } from './manifest'
2
7
 
3
8
  export class Aspect extends Connector implements extensions.Aspect {
4
9
  public readonly name = 'configuration'
5
10
 
6
- private readonly value: object
11
+ private readonly locator: Locator
12
+ private readonly manifest: Manifest
13
+ private readonly client: Client | null
14
+ private readonly epoch: string
15
+ private value: Node = {}
16
+ private created = 0
7
17
 
8
- public constructor (value: object) {
18
+ /**
19
+ * Without a client the value is local: the variable, the defaults and the schema.
20
+ * With one, the value is what the service holds, and it follows the service.
21
+ */
22
+ public constructor (locator: Locator, manifest: Manifest, client: Client | null) {
9
23
  super()
10
24
 
11
- this.value = value
25
+ this.locator = locator
26
+ this.manifest = manifest
27
+ this.client = client
28
+ this.epoch = epoch(manifest.schema)
29
+
30
+ if (client !== null)
31
+ this.depends(client)
12
32
  }
13
33
 
14
- public invoke (path: string[]): any {
34
+ public invoke (path?: string[]): any {
15
35
  let cursor: any = this.value
16
36
 
17
37
  if (path !== undefined)
@@ -20,4 +40,38 @@ export class Aspect extends Connector implements extensions.Aspect {
20
40
 
21
41
  return cursor
22
42
  }
43
+
44
+ protected override async open (): Promise<void> {
45
+ if (this.client === null) {
46
+ this.value = local(this.locator, this.manifest)
47
+
48
+ return
49
+ }
50
+
51
+ const { configuration, created } = await this.client.fetch(this.locator.id, this.epoch)
52
+
53
+ this.value = fit(configuration, this.manifest)
54
+ this.created = created
55
+ this.client.subscribe(this.locator.id, this.epoch, this.listener)
56
+ }
57
+
58
+ protected override async close (): Promise<void> {
59
+ this.client?.unsubscribe(this.locator.id, this.epoch, this.listener)
60
+ }
61
+
62
+ private readonly listener = ({ configuration, created }: Value): void => {
63
+ // deliveries may repeat or cross: only what is newer than the held value replaces it
64
+ if (created <= this.created)
65
+ return
66
+
67
+ try {
68
+ this.value = fit(configuration, this.manifest)
69
+ this.created = created
70
+
71
+ console.info('Configuration updated', { component: this.locator.id, created })
72
+ } catch (error) {
73
+ // the service validated it against the schema of its epoch, so the two schemas differ
74
+ console.error('Configuration value does not match the schema', { component: this.locator.id, error })
75
+ }
76
+ }
23
77
  }