diff --git a/astro.config.ts b/astro.config.ts index ea457e6f3df57..b55dc5e7ae324 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -1,4 +1,5 @@ import starlight from '@astrojs/starlight'; +import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'; import { defineConfig, sharpImageService } from 'astro/config'; import { makeLocalesConfig } from './config/locales'; import { makeSidebar } from './config/sidebar'; @@ -23,6 +24,9 @@ export default defineConfig({ starlight({ title: 'Docs', customCss: ['./src/styles/custom.css'], + expressiveCode: { + plugins: [pluginCollapsibleSections()], + }, components: { EditLink: './src/components/starlight/EditLink.astro', Head: './src/components/starlight/Head.astro', diff --git a/package.json b/package.json index 4febb2a005991..941d578851d92 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@astrojs/sitemap": "^3.1.5", "@astrojs/starlight": "^0.26.1", "@docsearch/js": "^3.5.2", + "@expressive-code/plugin-collapsible-sections": "^0.35.0", "@fontsource/ibm-plex-mono": "^4.5.10", "@lunariajs/core": "^0.1.1", "canvas-confetti": "^1.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be755b967b59d..3b48df19620d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,16 @@ importers: version: 0.7.0(prettier-plugin-astro@0.12.2)(prettier@3.1.0)(typescript@5.0.2) '@astrojs/sitemap': specifier: ^3.1.5 - version: 3.1.5 + version: 3.1.6 '@astrojs/starlight': specifier: ^0.26.1 version: 0.26.1(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2)) '@docsearch/js': specifier: ^3.5.2 version: 3.5.2(@algolia/client-search@4.23.3)(search-insights@2.13.0) + '@expressive-code/plugin-collapsible-sections': + specifier: ^0.35.0 + version: 0.35.6 '@fontsource/ibm-plex-mono': specifier: ^4.5.10 version: 4.5.10 @@ -68,13 +71,13 @@ importers: version: 1.6.0 '@types/hast': specifier: ^3.0.3 - version: 3.0.3 + version: 3.0.4 '@types/html-escaper': specifier: ^3.0.0 version: 3.0.0 '@types/mdast': specifier: ^4.0.3 - version: 4.0.3 + version: 4.0.4 '@types/node': specifier: ^18.6.4 version: 18.6.4 @@ -197,7 +200,7 @@ importers: version: 5.0.2 unified: specifier: ^11.0.4 - version: 11.0.4 + version: 11.0.5 unist-util-remove: specifier: ^4.0.0 version: 4.0.0 @@ -340,8 +343,8 @@ packages: '@astrojs/markdown-remark@5.2.0': resolution: {integrity: sha512-vWGM24KZXz11jR3JO+oqYU3T2qpuOi4uGivJ9SQLCAI01+vEkHC60YJMRvHPc+hwd60F7euNs1PeOEixIIiNQw==} - '@astrojs/mdx@3.1.3': - resolution: {integrity: sha512-hOM4dMM4RfJI254d3p/AnOZuk2VyKszRtuY5FBm+Xc4XdhIpGrR56OXMNEcWchtwz4HQyPe/eJSgvBjSROcQIQ==} + '@astrojs/mdx@3.1.4': + resolution: {integrity: sha512-AcdcAlDpzTM5LHpur7A3NWoIqyfhH1gZNbTvvjiUlDEo7eJjIxl4gdWrb/kZZRfLBEuM8cptCB+Qk11ncQL4IA==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} peerDependencies: astro: ^4.8.0 @@ -350,9 +353,6 @@ packages: resolution: {integrity: sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} - '@astrojs/sitemap@3.1.5': - resolution: {integrity: sha512-GLdzJ01387Uzb8RKYpsYLlg/GzoPnGbmDeQNkarSE11i2+l9Qp8Nj/WoTEy9nkTS25fxxy0kxDfJmreeVleCqg==} - '@astrojs/sitemap@3.1.6': resolution: {integrity: sha512-1Qp2NvAzVImqA6y+LubKi1DVhve/hXXgFvB0szxiipzh7BvtuKe4oJJ9dXSqaubaTkt4nMa6dv6RCCAYeB6xaQ==} @@ -683,6 +683,9 @@ packages: '@expressive-code/core@0.35.6': resolution: {integrity: sha512-xGqCkmfkgT7lr/rvmfnYdDSeTdCSp1otAHgoFS6wNEeO7wGDPpxdosVqYiIcQ8CfWUABh/pGqWG90q+MV3824A==} + '@expressive-code/plugin-collapsible-sections@0.35.6': + resolution: {integrity: sha512-PciZoBynxp3DCrK3dvcc/rxkj2HVbFxX992yqez1pircmPj0g1STySslkOBVMHh9COy6whJ4Mbq2k9DPV1A5/Q==} + '@expressive-code/plugin-frames@0.35.6': resolution: {integrity: sha512-CqjSWjDJ3wabMJZfL9ZAzH5UAGKg7KWsf1TBzr4xvUbZvWoBtLA/TboBML0U1Ls8h/4TRCIvR4VEb8dv5+QG3w==} @@ -978,9 +981,6 @@ packages: '@shikijs/core@1.14.1': resolution: {integrity: sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==} - '@shikijs/core@1.7.0': - resolution: {integrity: sha512-O6j27b7dGmJbR3mjwh/aHH8Ld+GQvA0OQsNO43wKWnqbAae3AYXrhFyScHGX8hXZD6vX2ngjzDFkZY5srtIJbQ==} - '@ts-morph/common@0.16.0': resolution: {integrity: sha512-SgJpzkTgZKLKqQniCjLaE3c2L2sdL7UShvmTmPBejAKd2OKV/yfMpQ2IWpAuA+VY5wy7PkSUaEObIqEK6afFuw==} @@ -1014,9 +1014,6 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/hast@3.0.3': - resolution: {integrity: sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -1032,9 +1029,6 @@ packages: '@types/markdown-it@12.2.3': resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} - '@types/mdast@4.0.3': - resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} - '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -1181,16 +1175,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - - acorn@8.12.0: - resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} @@ -1547,15 +1531,6 @@ packages: resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} engines: {node: '>= 12'} - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.5: resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} engines: {node: '>=6.0'} @@ -1699,9 +1674,6 @@ packages: resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} engines: {node: '>= 0.4'} - es-module-lexer@1.5.3: - resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} - es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} @@ -2282,9 +2254,6 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - import-meta-resolve@4.0.0: - resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} - import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} @@ -3347,9 +3316,6 @@ packages: shiki@1.14.1: resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} - shiki@1.7.0: - resolution: {integrity: sha512-H5pMn4JA7ayx8H0qOz1k2qANq6mZVCMl1gKLK6kWIrv1s2Ial4EmD4s4jE8QB5Dw03d/oCQUxc24sotuyR5byA==} - side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -3378,11 +3344,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - sitemap@7.1.1: - resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} - engines: {node: '>=12.0.0', npm: '>=5.6.0'} - hasBin: true - sitemap@7.1.2: resolution: {integrity: sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==} engines: {node: '>=12.0.0', npm: '>=5.6.0'} @@ -3642,9 +3603,6 @@ packages: unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} - unified@11.0.4: - resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} - unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3730,9 +3688,6 @@ packages: vfile@5.3.7: resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} - vfile@6.0.1: - resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} - vfile@6.0.2: resolution: {integrity: sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==} @@ -3959,7 +3914,7 @@ snapshots: '@11ty/eleventy-fetch@3.0.0': dependencies: - debug: 4.3.4 + debug: 4.3.5 flat-cache: 3.0.4 node-fetch: 2.6.7 p-queue: 6.6.2 @@ -4143,7 +4098,7 @@ snapshots: github-slugger: 2.0.0 hast-util-from-html: 2.0.1 hast-util-to-text: 4.0.2 - import-meta-resolve: 4.0.0 + import-meta-resolve: 4.1.0 mdast-util-definitions: 6.0.0 rehype-raw: 7.0.0 rehype-stringify: 10.0.0 @@ -4151,12 +4106,12 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.0 remark-smartypants: 2.0.0 - shiki: 1.7.0 - unified: 11.0.4 + shiki: 1.14.1 + unified: 11.0.5 unist-util-remove-position: 5.0.0 unist-util-visit: 5.0.0 unist-util-visit-parents: 6.0.1 - vfile: 6.0.1 + vfile: 6.0.2 transitivePeerDependencies: - supports-color @@ -4183,7 +4138,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.3(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2))': + '@astrojs/mdx@3.1.4(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2))': dependencies: '@astrojs/markdown-remark': 5.2.0 '@mdx-js/mdx': 3.0.1 @@ -4191,7 +4146,6 @@ snapshots: astro: 4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 - github-slugger: 2.0.0 gray-matter: 4.0.3 hast-util-to-html: 9.0.1 kleur: 4.1.5 @@ -4208,12 +4162,6 @@ snapshots: dependencies: prismjs: 1.29.0 - '@astrojs/sitemap@3.1.5': - dependencies: - sitemap: 7.1.1 - stream-replace-string: 2.0.0 - zod: 3.23.8 - '@astrojs/sitemap@3.1.6': dependencies: sitemap: 7.1.2 @@ -4222,7 +4170,7 @@ snapshots: '@astrojs/starlight@0.26.1(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2))': dependencies: - '@astrojs/mdx': 3.1.3(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2)) + '@astrojs/mdx': 3.1.4(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2)) '@astrojs/sitemap': 3.1.6 '@pagefind/default-ui': 1.0.3 '@types/hast': 3.0.4 @@ -4279,7 +4227,7 @@ snapshots: '@babel/traverse': 7.24.7 '@babel/types': 7.24.7 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4403,7 +4351,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.4 + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4551,7 +4499,7 @@ snapshots: '@eslint/eslintrc@1.3.3': dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.5 espree: 9.4.1 globals: 13.15.0 ignore: 5.2.0 @@ -4574,6 +4522,10 @@ snapshots: unist-util-visit: 5.0.0 unist-util-visit-parents: 6.0.1 + '@expressive-code/plugin-collapsible-sections@0.35.6': + dependencies: + '@expressive-code/core': 0.35.6 + '@expressive-code/plugin-frames@0.35.6': dependencies: '@expressive-code/core': 0.35.6 @@ -4581,7 +4533,7 @@ snapshots: '@expressive-code/plugin-shiki@0.35.6': dependencies: '@expressive-code/core': 0.35.6 - shiki: 1.7.0 + shiki: 1.14.1 '@expressive-code/plugin-text-markers@0.35.6': dependencies: @@ -4592,7 +4544,7 @@ snapshots: '@humanwhocodes/config-array@0.11.7': dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 + debug: 4.3.5 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4838,8 +4790,6 @@ snapshots: dependencies: '@types/hast': 3.0.4 - '@shikijs/core@1.7.0': {} - '@ts-morph/common@0.16.0': dependencies: fast-glob: 3.3.2 @@ -4886,10 +4836,6 @@ snapshots: '@types/estree@1.0.5': {} - '@types/hast@3.0.3': - dependencies: - '@types/unist': 3.0.2 - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.2 @@ -4905,10 +4851,6 @@ snapshots: '@types/linkify-it': 3.0.2 '@types/mdurl': 1.0.2 - '@types/mdast@4.0.3': - dependencies: - '@types/unist': 3.0.2 - '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.2 @@ -4953,7 +4895,7 @@ snapshots: '@typescript-eslint/scope-manager': 5.46.1 '@typescript-eslint/type-utils': 5.46.1(eslint@8.29.0)(typescript@5.0.2) '@typescript-eslint/utils': 5.46.1(eslint@8.29.0)(typescript@5.0.2) - debug: 4.3.4 + debug: 4.3.5 eslint: 8.29.0 ignore: 5.2.0 natural-compare-lite: 1.4.0 @@ -4970,7 +4912,7 @@ snapshots: '@typescript-eslint/scope-manager': 5.46.1 '@typescript-eslint/types': 5.46.1 '@typescript-eslint/typescript-estree': 5.46.1(typescript@5.0.2) - debug: 4.3.4 + debug: 4.3.5 eslint: 8.29.0 optionalDependencies: typescript: 5.0.2 @@ -4986,7 +4928,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 5.46.1(typescript@5.0.2) '@typescript-eslint/utils': 5.46.1(eslint@8.29.0)(typescript@5.0.2) - debug: 4.3.4 + debug: 4.3.5 eslint: 8.29.0 tsutils: 3.21.0(typescript@5.0.2) optionalDependencies: @@ -5000,7 +4942,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.46.1 '@typescript-eslint/visitor-keys': 5.46.1 - debug: 4.3.4 + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -5094,18 +5036,10 @@ snapshots: '@webgpu/types@0.1.21': {} - acorn-jsx@5.3.2(acorn@8.12.0): - dependencies: - acorn: 8.12.0 - acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 - acorn@8.11.3: {} - - acorn@8.12.0: {} - acorn@8.12.1: {} ajv@6.12.6: @@ -5211,7 +5145,7 @@ snapshots: astro-auto-import@0.4.2(astro@4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2)): dependencies: '@types/node': 18.6.4 - acorn: 8.11.3 + acorn: 8.12.1 astro: 4.11.0(@types/node@18.6.4)(sass@1.54.3)(typescript@5.0.2) astro-eslint-parser@0.16.0: @@ -5220,7 +5154,7 @@ snapshots: '@typescript-eslint/scope-manager': 5.46.1 '@typescript-eslint/types': 5.46.1 astrojs-compiler-sync: 0.3.1(@astrojs/compiler@2.8.0) - debug: 4.3.4 + debug: 4.3.5 eslint-visitor-keys: 3.3.0 espree: 9.4.1 semver: 7.6.2 @@ -5232,7 +5166,7 @@ snapshots: '@astrojs/compiler': 0.31.0 '@typescript-eslint/types': 5.46.1 astrojs-compiler-sync: 0.3.1(@astrojs/compiler@0.31.0) - debug: 4.3.4 + debug: 4.3.5 eslint-scope: 7.1.1 eslint-visitor-keys: 3.3.0 espree: 9.4.1 @@ -5265,7 +5199,7 @@ snapshots: '@babel/types': 7.24.7 '@types/babel__core': 7.20.5 '@types/cookie': 0.6.0 - acorn: 8.12.0 + acorn: 8.12.1 aria-query: 5.3.0 axobject-query: 4.0.0 boxen: 7.1.1 @@ -5281,7 +5215,7 @@ snapshots: diff: 5.2.0 dlv: 1.1.3 dset: 3.1.3 - es-module-lexer: 1.5.3 + es-module-lexer: 1.5.4 esbuild: 0.21.5 estree-walker: 3.0.3 execa: 8.0.1 @@ -5304,12 +5238,12 @@ snapshots: rehype: 13.0.1 resolve: 1.22.8 semver: 7.6.2 - shiki: 1.7.0 + shiki: 1.14.1 string-width: 7.1.0 strip-ansi: 7.1.0 tsconfck: 3.1.0(typescript@5.0.2) unist-util-visit: 5.0.0 - vfile: 6.0.1 + vfile: 6.0.2 vite: 5.3.1(@types/node@18.6.4)(sass@1.54.3) vitefu: 0.2.5(vite@5.3.1(@types/node@18.6.4)(sass@1.54.3)) which-pm: 2.2.0 @@ -5563,10 +5497,6 @@ snapshots: data-uri-to-buffer@4.0.0: {} - debug@4.3.4: - dependencies: - ms: 2.1.2 - debug@4.3.5: dependencies: ms: 2.1.2 @@ -5726,8 +5656,6 @@ snapshots: unbox-primitive: 1.0.2 which-typed-array: 1.1.9 - es-module-lexer@1.5.3: {} - es-module-lexer@1.5.4: {} es-set-tostringtag@2.0.1: @@ -5926,7 +5854,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.5 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 @@ -5963,8 +5891,8 @@ snapshots: espree@9.4.1: dependencies: - acorn: 8.12.0 - acorn-jsx: 5.3.2(acorn@8.12.0) + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 3.3.0 esprima@4.0.1: {} @@ -6263,11 +6191,11 @@ snapshots: hast-util-from-html@2.0.1: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 devlop: 1.1.0 hast-util-from-parse5: 8.0.1 parse5: 7.1.2 - vfile: 6.0.1 + vfile: 6.0.2 vfile-message: 4.0.2 hast-util-from-parse5@8.0.1: @@ -6327,7 +6255,7 @@ snapshots: hast-util-select@6.0.2: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 '@types/unist': 3.0.2 bcp-47-match: 2.0.2 comma-separated-tokens: 2.0.3 @@ -6367,7 +6295,7 @@ snapshots: hast-util-to-html@9.0.1: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 '@types/unist': 3.0.2 ccount: 2.0.1 comma-separated-tokens: 2.0.3 @@ -6412,7 +6340,7 @@ snapshots: hast-util-to-string@3.0.0: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 hast-util-to-text@4.0.2: dependencies: @@ -6427,7 +6355,7 @@ snapshots: hastscript@8.0.0: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 property-information: 6.2.0 @@ -6473,8 +6401,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-meta-resolve@4.0.0: {} - import-meta-resolve@4.1.0: {} imurmurhash@0.1.4: {} @@ -6796,7 +6722,7 @@ snapshots: mdast-util-from-markdown@2.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 '@types/unist': 3.0.2 decode-named-character-reference: 1.0.2 devlop: 1.1.0 @@ -6925,8 +6851,8 @@ snapshots: mdast-util-to-hast@13.0.2: dependencies: - '@types/hast': 3.0.3 - '@types/mdast': 4.0.3 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 '@ungap/structured-clone': 1.2.0 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.0 @@ -6947,7 +6873,7 @@ snapshots: mdast-util-to-string@4.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 mdurl@1.0.1: {} @@ -7210,7 +7136,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.7 - debug: 4.3.4 + debug: 4.3.5 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -7629,11 +7555,11 @@ snapshots: rehype-autolink-headings@7.1.0: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 '@ungap/structured-clone': 1.2.0 hast-util-heading-rank: 3.0.0 hast-util-is-element: 3.0.0 - unified: 11.0.4 + unified: 11.0.5 unist-util-visit: 5.0.0 rehype-expressive-code@0.35.6: @@ -7663,7 +7589,7 @@ snapshots: dependencies: '@types/hast': 3.0.4 hast-util-from-html: 2.0.1 - unified: 11.0.4 + unified: 11.0.5 rehype-raw@7.0.0: dependencies: @@ -7673,7 +7599,7 @@ snapshots: rehype-slug@6.0.0: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 github-slugger: 2.0.0 hast-util-heading-rank: 3.0.0 hast-util-to-string: 3.0.0 @@ -7687,28 +7613,28 @@ snapshots: rehype@13.0.1: dependencies: - '@types/hast': 3.0.3 + '@types/hast': 3.0.4 rehype-parse: 9.0.0 rehype-stringify: 10.0.0 - unified: 11.0.4 + unified: 11.0.5 remark-directive@3.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 mdast-util-directive: 3.0.0 micromark-extension-directive: 3.0.0 - unified: 11.0.4 + unified: 11.0.5 transitivePeerDependencies: - supports-color remark-gfm@4.0.0: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 mdast-util-gfm: 3.0.0 micromark-extension-gfm: 3.0.0 remark-parse: 11.0.0 remark-stringify: 11.0.0 - unified: 11.0.4 + unified: 11.0.5 transitivePeerDependencies: - supports-color @@ -7753,14 +7679,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 mdast-util-to-markdown: 2.1.0 - unified: 11.0.4 + unified: 11.0.5 remark@15.0.1: dependencies: - '@types/mdast': 4.0.3 + '@types/mdast': 4.0.4 remark-parse: 11.0.0 remark-stringify: 11.0.0 - unified: 11.0.4 + unified: 11.0.5 transitivePeerDependencies: - supports-color @@ -7964,10 +7890,6 @@ snapshots: '@shikijs/core': 1.14.1 '@types/hast': 3.0.4 - shiki@1.7.0: - dependencies: - '@shikijs/core': 1.7.0 - side-channel@1.0.4: dependencies: call-bind: 1.0.2 @@ -8002,13 +7924,6 @@ snapshots: sisteransi@1.0.5: {} - sitemap@7.1.1: - dependencies: - '@types/node': 17.0.45 - '@types/sax': 1.2.4 - arg: 5.0.2 - sax: 1.2.4 - sitemap@7.1.2: dependencies: '@types/node': 17.0.45 @@ -8271,16 +8186,6 @@ snapshots: trough: 2.1.0 vfile: 5.3.7 - unified@11.0.4: - dependencies: - '@types/unist': 3.0.2 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.0.0 - trough: 2.1.0 - vfile: 6.0.1 - unified@11.0.5: dependencies: '@types/unist': 3.0.2 @@ -8409,12 +8314,6 @@ snapshots: unist-util-stringify-position: 3.0.3 vfile-message: 3.1.4 - vfile@6.0.1: - dependencies: - '@types/unist': 3.0.2 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 - vfile@6.0.2: dependencies: '@types/unist': 3.0.2 diff --git a/src/content/docs/en/guides/actions.mdx b/src/content/docs/en/guides/actions.mdx new file mode 100644 index 0000000000000..bd2c0735f42aa --- /dev/null +++ b/src/content/docs/en/guides/actions.mdx @@ -0,0 +1,581 @@ +--- +title: Actions +description: Learn how to create type-safe server functions you can call from anywhere. +i18nReady: true +--- + +import { Steps } from '@astrojs/starlight/components'; +import Since from '~/components/Since.astro'; +import ReadMore from '~/components/ReadMore.astro'; + +

+ +Astro Actions allow you to define and call backend functions with type-safety. Actions perform data fetching, JSON parsing, and input validation for you. This can greatly reduce the amount of boilerplate needed compared to using an [API endpoint](/en/guides/endpoints/). + +Use actions instead of API endpoints for seamless communication between your client and server code and to: + +- Automatically validate JSON and form data inputs using [Zod validaton](https://zod.dev/?id=primitives). +- Generate type-safe functions to call your backend from the client and even [from HTML form actions](#call-actions-from-an-html-form-action). No need for manual `fetch()` calls. +- Standardize backend errors with the [`ActionError`](/en/reference/api-reference/#actionerror) object. + +## Basic usage + +Actions are defined in a `server` object exported from `src/actions/index.ts`: + +```ts title="src/actions/index.ts" +import { defineAction } from 'astro:actions'; +import { z } from 'astro:schema'; + +export const server = { + myAction: defineAction({ /* ... */ }) +} +``` + +Your actions are available as functions from the `astro:actions` module. Import `actions` and call them client-side within a [UI framework component](/en/guides/framework-components/), [a form POST request](#call-actions-from-an-html-form-action), or by using a ` +``` + +### Write your first action + +Follow these steps to define an action and call it in a `script` tag in your Astro page. + + + +1. Create a `src/actions/index.ts` file and export a `server` object. + + ```ts title="src/actions/index.ts" + export const server = { + // action declarations + } + ``` + +2. Import the `defineAction()` utility from `astro:actions`, and the `z` object from `astro:schema`. + + ```ts ins={1-2} title="src/actions/index.ts" + import { defineAction } from 'astro:actions'; + import { z } from 'astro:schema'; + + export const server = { + // action declarations + } + ``` + +3. Use the `defineAction()` utility to define a `getGreeting` action. The `input` property will be used to validate input parameters with a [Zod](https://zod.dev) schema and the `handler()` function includes the backend logic to run on the server. + + ```ts ins={5-12} title="src/actions/index.ts" + import { defineAction } from 'astro:actions'; + import { z } from 'astro:schema'; + + export const server = { + getGreeting: defineAction({ + input: z.object({ + name: z.string(), + }), + handler: async (input) => { + return `Hello, ${input.name}!` + } + }) + } + ``` + +4. Create an Astro component with a button that will fetch a greeting using your `getGreeting` action when clicked. + + ```astro title="src/pages/index.astro" + --- + --- + + + + + ``` + +5. To use your action, import `actions` from `astro:actions` and then call `actions.getGreeting()` in the click handler. The `name` option will be sent to your action’s `handler()` on the server and, if there are no errors, the result will be available as the `data` property. + + ```astro title="src/pages/index.astro" ins={7, 12-13} + --- + --- + + + + + ``` + + + +See the full Actions API documentation for details on [`defineAction()`](/en/reference/api-reference/#defineaction) and its properties. + +## Organizing actions + +All actions in your project must be exported from the `server` object in the `src/actions/index.ts` file. You can define actions inline or you can move action definitions to separate files and import them. You can even group related functions in nested objects. + +For example, to colocate all of your user actions, you can create a `src/actions/user.ts` file and nest the definitions of both `getUser` and `createUser` inside a single `user` object. + +```ts +// src/actions/user.ts +import { defineAction } from 'astro:actions'; + +export const user = { + getUser: defineAction(/* ... */), + createUser: defineAction(/* ... */), +} +``` + +Then, you can import this `user` object into your `src/actions/index.ts` file and add it as a top-level key to the `server` object alongside any other actions: + +```ts title="src/actions/index.ts" ins={1,5} +import { user } from './user'; + +export const server = { + myAction: defineAction({ /* ... */ }), + user, +} +``` + +Now, all of your user actions are callable from the `actions.user` object: + +- `actions.user.getUser()` +- `actions.user.createUser()` + + +## Handling returned data + +Actions return an object containing either `data` with the type-safe return value of your `handler()`, or an `error` with any backend errors. Errors may come from validation errors on the `input` property or thrown errors within the `handler()`. + +### Checking for errors + +It's best to check if an `error` is present before using the `data` property. This allows you to handle errors in advance and ensures `data` is defined without an `undefined` check. + +```ts +const { data, error } = await actions.example(); + +if (error) { + // handle error cases + return; +} +// use `data` +``` + +### Accessing `data` directly without an error check + +To skip error handling, for example while prototyping or using a library that will catch errors for you, use the `.orThrow()` property on your action call to throw errors instead of returning an `error`. This will return the action's `data` directly. + +This example calls a `likePost()` action that returns the updated number of likes as a `number` from the action `handler`: + +```ts ins="orThrow" +const updatedLikes = await actions.likePost.orThrow({ postId: 'example' }); +// ^ type: number +``` + +### Handling backend errors in your action + +You can use the provided `ActionError` to throw an error from your action `handler()`, such as "not found" when a database entry is missing, or "unauthorized" when a user is not logged in. This has two main benefits over returning `undefined`: + + +- You can set a status code like `404 - Not found` or `401 - Unauthorized`. This improves debugging errors in both development and in production by letting you see the status code of each request. + +- In your application code, all errors are passed to the `error` object on an action result. This avoids the need for `undefined` checks on data, and allows you to display targeted feedback to the user depending on what went wrong. + +#### Creating an `ActionError` + +To throw an error, import the `ActionError()` class from the `astro:actions` module. Pass it a human-readable status `code` (e.g. `"NOT_FOUND"` or `"BAD_REQUEST"`), and an optional `message` to provide further information about the error. + +This example throws an error from a `likePost` action when a user is not logged in, after checking a hypothetical "user-session" cookie for authentication: + +```ts title="src/actions/index.ts" ins=/ActionError(?= )/ ins={9-12} +import { defineAction, ActionError } from "astro:actions"; +import { z } from "astro:schema"; + +export const server = { + likePost: defineAction({ + input: z.object({ postId: z.string() }), + handler: async (input, ctx) => { + if (!ctx.cookies.has('user-session')) { + throw new ActionError({ + code: "UNAUTHORIZED", + message: "User must be logged in.", + }); + } + // Otherwise, like the post + }, + }), +}; +``` + +#### Handling an `ActionError` + +To handle this error, you can call the action from your application and check whether an `error` property is present. This property will be of type `ActionError` and will contain your `code` and `message`. + +In the following example, a `LikeButton.tsx` component calls the `likePost()` action when clicked. If an authentication error occurs, the `error.code` attribute is used to determine whether to display a login link: + +```tsx title=src/components/LikeButton.tsx ins="if (error.code === 'UNAUTHORIZED') setShowLogin(true);" +import { actions } from 'astro:actions'; +import { useState } from 'preact/hooks'; + +export function LikeButton({ postId }: { postId: string }) { + const [showLogin, setShowLogin] = useState(false); + return ( + <> + { + showLogin && Log in to like a post. + } + + + ) +} +``` + +### Handling client redirects + +When calling actions from the client, you can integrate with a client-side library like `react-router`, or you can use Astro's [`navigate()` function](/en/guides/view-transitions/#trigger-navigation) to redirect to a new page when an action succeeds. + +This example navigates to the homepage after a `logout` action returns successfully: + +```tsx title=src/pages/LogoutButton.tsx {2,7-8} +import { actions } from 'astro:actions'; +import { navigate } from 'astro:transitions/client'; + +export function LogoutButton() { + return ( + + ); +} +``` + +## Accepting form data from an action + +Actions accept JSON data by default. To accept form data from an HTML form, set `accept: 'form'` in your `defineAction()` call: + +```ts title="src/actions/index.ts" ins={6} +import { defineAction } from 'astro:actions'; +import { z } from 'astro:schema'; + +export const server = { + comment: defineAction({ + accept: 'form', + input: z.object(/* ... */), + handler: async (input) => { /* ... */ }, + }) +} +``` + +### Validating form data + +Actions will parse submitted form data to an object, using the value of each input’s `name` attribute as the object keys. For example, a form containing `` will be parsed to an object like `{ search: 'user input' }`. Your action's `input` schema will be used to validate this object. + +To receive the raw `FormData` object in your action handler instead of a parsed object, omit the `input` property in your action definition. + +The following example shows a validated newsletter registration form that accepts a user's email and requires a "terms of service" agreement checkbox. + + + +1. Create an HTML form component with unique `name` attributes on each input: + + ```astro title="src/components/Newsletter.astro" /name="\w+"/ +
+ + + + +
+ ``` + +2. Define a `newsletter` action to handle the submitted form. Validate the `email` field using the `z.string().email()` validator, and the `terms` checkbox using `z.boolean()`: + + ```ts title="src/actions/index.ts" ins={5-12} + import { defineAction } from 'astro:actions'; + import { z } from 'astro:schema'; + + export const server = { + newsletter: defineAction({ + accept: 'form', + input: z.object({ + email: z.string().email(), + terms: z.boolean(), + }), + handler: async ({ email, terms }) => { /* ... */ }, + }) + } + ``` + + See the [`input` API reference](/en/reference/api-reference/#input-validator) for all available form validators. + +3. Add a ` + ``` + + See [“Call actions from an HTML form action”](#call-actions-from-an-html-form-action) for an alternative way to submit form data. + +
+ +### Displaying form input errors + +You can validate form inputs before submission using [native HTML form validation attributes](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation) like `required`, `type="email"`, and `pattern`. For more complex `input` validation on the backend, you can use the provided [`isInputError()`](/en/reference/api-reference/#isinputerror) utility function. + +To retrieve input errors, use the `isInputError()` utility to check whether an error was caused by invalid input. Input errors contain a `fields` object with messages for each input name that failed to validate. You can use these messages to prompt your user to correct their submission. + +The following example checks the error with `isInputError()`, then checks whether the error is in the email field, before finally creating a message from the errors. You can use JavaScript DOM manipulation or your preferred UI framework to display this message to users. + +```js /isInputError(?= )/ {5-12} +import { actions, isInputError } from 'astro:actions'; + +const form = document.querySelector('form'); +const formData = new FormData(form); +const { error } = await actions.newsletter(formData); +if (isInputError(error)) { + // Handle input errors. + if (error.fields.email) { + const message = error.fields.email.join(', '); + } +} +``` + +## Call actions from an HTML form action + +:::note +Pages must be on-demand rendered when calling actions using a form action. [Ensure prerendering is disabled on the page](/en/guides/server-side-rendering/#opting-out-of-pre-rendering-in-hybrid-mode) before using this API. +::: + +You can enable zero-JS form submissions with standard attributes on any `
` element. Form submissions without client-side JavaScript may be useful both as a fallback for when JavaScript fails to load, or if you prefer to handle forms entirely from the server. + +Calling [Astro.getActionResult()](/en/reference/api-reference/#astrogetactionresult) on the server returns the result of your form submission (`data` or `error`), and can be used to dynamically redirect, handle form errors, update the UI, and more. + +To call an action from an HTML form, add `method="POST"` to your ``, then set the form's `action` attribute using your action, for example `action={actions.logout}`. This will set the `action` attribute to use a query string that is handled by the server automatically. + +For example, this Astro component calls the `logout` action when the button is clicked and reloads the current page: + +```astro title="src/components/LogoutButton.astro" +--- +import { actions } from 'astro:actions'; +--- + + + +
+``` + +### Redirect on action success + +To navigate to a different page when an action is successful without client-side JavaScript, you can prepend a path in the `action` attribute. + +For example, `action={'/confirmation' + actions.newsletter}` will navigate to `/confirmation` when the `newsletter` action succeeds: + +```astro title="src/components/NewsletterSignup.astro" /action=\{[^\{\}]+\}/ +--- +import { actions } from 'astro:actions'; +--- + +
+ + +
+``` + +#### Dynamic redirect on action success + +If you need to decide where to redirect to dynamically, you can use an action’s result on the server. A common example is creating a product record and redirecting to the new product's page, e.g. `/products/[id]`. + +For example, say you have a `createProduct` action that returns the generated product id: + +```ts title="src/actions/index.ts" mark={10} +import { defineAction } from 'astro:actions'; +import { z } from 'astro:schema'; + +export const server = { + createProduct: defineAction({ + accept: 'form', + input: z.object({ /* ... */ }), + handler: async (input) => { + const product = await persistToDatabase(input); + return { id: product.id }; + }, + }) +} +``` + +You can retrieve the action result from your Astro component by calling `Astro.getActionResult()`. This returns an object containing `data` or `error` properties when an action is called, or `undefined` if the action was not called during this request. + +Use the `data` property to construct a URL to use with `Astro.redirect()`: + +```astro title="src/pages/products/create.astro" {4-7} +--- +import { actions } from 'astro:actions'; + +const result = Astro.getActionResult(actions.createProduct); +if (result && !result.error) { + return Astro.redirect(`/products/${result.data.id}`); +} +--- + +
+ +
+``` + +### Handle form action errors + +Astro will not redirect to your `action` route when an action fails. Instead, the current page is reloaded with any errors the action returned. Calling `Astro.getActionResult()` in the Astro component containing your form gives you access to the `error` object for custom error handling. + +The following example displays a general failure message when a `newsletter` action fails: + +```astro title="src/pages/index.astro" {4,7-9} +--- +import { actions } from 'astro:actions'; + +const result = Astro.getActionResult(actions.newsletter); +--- + +{result?.error && ( +

Unable to sign up. Please try again later.

+)} +
+ + +
+``` + +For more customization, you can [use the `isInputError()` utility](#displaying-form-input-errors) to check whether an error is caused by invalid input. + +The following example renders an error banner under the `email` input field when an invalid email is submitted: + +```astro title="src/pages/index.astro" ins={5,13} ins='aria-describedby="error"' +--- +import { actions, isInputError } from 'astro:actions'; + +const result = Astro.getActionResult(actions.newsletter); +const inputErrors = isInputError(result?.error) ? result.error.fields : {}; +--- + +
+ + {inputErrors.email &&

{inputErrors.email.join(',')}

} + +
+``` + +:::note +Astro persists action `data` and `error` with a single-use cookie. This means `getActionResult()` will return a result on the first request _only_, and `undefined` when revisiting the page. +::: + +#### Preserve input values on error + +Inputs will be cleared whenever a form is submitted. To persist input values, you can [enable view transitions](/en/guides/view-transitions/#adding-view-transitions-to-a-page) on the page and apply the `transition:persist` directive to each input: + +```astro ins="transition:persist" + +``` + +### Update the UI with a form action result + +The result returned by `Astro.getActionResult()` is single-use, and will reset to `undefined` whenever the page is refreshed. This is ideal for [displaying input errors](#handle-form-action-errors) and showing temporary notifications to the user on success. + +:::tip +If you need a result to be displayed across page refreshes, consider storing the result in a database or [in a cookie](/en/reference/api-reference/#astrocookies). +::: + +Pass an action to `Astro.getActionResult()` and use the returned `data` property to render any temporary UI you want to display. This example uses the `productName` property returned by an `addToCart` action to show a success message: + +```astro title="src/pages/products/[slug].astro" +--- +import { actions } from 'astro:actions'; + +const result = Astro.getActionResult(actions.addToCart); +--- + +{result && !result.error && ( +

Added {result.data.productName} to cart

+)} + + +``` + +:::caution +Action data is passed using a persisted cookie. **This cookie is not encrypted.** In general, we recommend returning the minimum information required from your action `handler` to avoid vulnerabilities, and persist other sensitive information in a database. + +For example, you might return the name of a product in an `addToCart` action, rather than returning the entire `product` object: + +```ts title="src/actions/index.ts" del={7} ins={8} +import { defineAction } from 'astro:actions'; + +export const server = { + addToCard: defineAction({ + handler: async () => { + /* ... */ + return product; + return { productName: product.name }; + } + }) +} +``` +::: diff --git a/src/content/docs/en/guides/astro-db.mdx b/src/content/docs/en/guides/astro-db.mdx index 1234d259811db..97585c02684f7 100644 --- a/src/content/docs/en/guides/astro-db.mdx +++ b/src/content/docs/en/guides/astro-db.mdx @@ -7,6 +7,7 @@ i18nReady: true import { FileTree } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; import ReadMore from '~/components/ReadMore.astro'; +import Since from '~/components/Since.astro'; import StudioHeading from '~/components/StudioHeading.astro'; import { Steps } from '@astrojs/starlight/components'; @@ -38,7 +39,7 @@ If you prefer, you can [install `@astrojs/db` manually](/en/guides/integrations- ## Define your database -Astro DB is a complete solution to configuring, developing and querying your data. A local database is created whenever you run `astro dev`, using LibSQL to manage your data without the need for Docker or a network connection. +Astro DB is a complete solution to configuring, developing and querying your data. A local database is created whenever you run `astro dev`, using libSQL to manage your data without the need for Docker or a network connection. Installing `@astrojs/db` with the `astro add` command will create a `db/config.ts` file in your project where you will define your databases tables: @@ -654,6 +655,68 @@ When you're ready to deploy, see our [Deploy with a Studio Connection guide](#de +## libSQL + +

+ +Astro DB can connect to any libSQL server from any platform that exposes the libSQL remote protocol of the server, or can be self-hosted. + +To connect Astro DB to a libSQL database, set the following environment variables: +- `ASTRO_DB_REMOTE_URL`: the connection URL to your libSQL server +- `ASTRO_DB_APP_TOKEN`: the auth token to your libSQL server + +The [commands for deploying and pushing changes](#deploy-with-a-studio-connection) to your database are the same when using libSQL as when connecting to an Astro Studio hosted database. However, both of your environment variables need to be set locally when running commands with the `--remote` option like `astro build` and `astro db push`. + +Details of the libSQL connection (e.g. encryption key, replication, sync interval) can be configured as query parameters in the remote connection URL. + +For example, to have a local file work as an embedded replica to an encrypted libSQL server, you can set the following environment variables: + +```env title=".env" +ASTRO_DB_REMOTE_URL=file://local-copy.db?encryptionKey=your-encryption-key&syncInterval=60&syncUrl=libsql%3A%2F%2Fyour.server.io +ASTRO_DB_APP_TOKEN=token-to-your-remote-url +``` + +### URL scheme and host + +libSQL supports both HTTP and WebSockets as the transport protocol for a remote server. It also supports using a local file or an in-memory DB. Those can be configured using the following URL schemes in the connection URL: + +- `memory:` will use an in-memory DB. The host must be empty in this case. +- `file:` will use a local file. The host is the path to the file (`file:path/to/file.db`). +- `libsql:` will use a remote server through the protocol preferred by the library (this might be different across versions). The host is the address of the server (`libsql://your.server.io`). +- `http:` will use a remote server through HTTP. `https:` can be used to enable a secure connection. The host is the same as for `libsql:`. +- `ws:` will use a remote server through WebSockets. `wss:` can be used to enable a secure connection. The host is the same as for `libsql:`. + +### `encryptionKey` + +libSQL has native support for encrypted databases. Passing this search parameter will enable encryption using the given key: + +```env title=".env" +ASTRO_DB_REMOTE_URL=file:path/to/file.db?encryptionKey=your-encryption-key +``` + +### `syncUrl` + +Embedded replicas are a feature of libSQL clients that enables a full synchronized copy of your database on a local file or in memory for ultra-fast reads. Writes are sent to a remote database defined on the `syncUrl` and synchronized with the local copy. + +Use this property to pass a separate connection URL to turn the DB into an embedded replica of another database. This should only be used with the schemes `file:` and `memory:`. The parameter must be URL encoded. + +For example, to have an in-memory embedded replica of a database on `libsql://your.server.io`, you can set the connection URL as such: + +```env title=".env" +ASTRO_DB_REMOTE_URL=memory:?syncUrl=libsql%3A%2F%2Fyour.server.io +``` + +### `syncInterval` + +Interval between embedded replicas synchronizations in seconds. By default it only synchronizes on startup and after writes. + +This property is only used when `syncUrl` is also set. For example, to set an in-memory embedded replica to synchronize every minute set the following environment variable: + +```env title=".env" +ASTRO_DB_REMOTE_URL=memory:?syncUrl=libsql%3A%2F%2Fyour.server.io&syncInterval=60 +``` + + ## Building Astro DB integrations [Astro integrations](/en/reference/integrations-reference/) can extend user projects with additional Astro DB tables and seed data. @@ -752,4 +815,3 @@ Additionally, [pushing any table schema changes](#pushing-table-schemas) (also k :::danger If you override your `.db` file on deployment, you will lose your production data. Follow the deployment method process for your host carefully to prevent data loss. ::: - diff --git a/src/content/docs/en/guides/internationalization.mdx b/src/content/docs/en/guides/internationalization.mdx index b543e2f53d673..b005c14790943 100644 --- a/src/content/docs/en/guides/internationalization.mdx +++ b/src/content/docs/en/guides/internationalization.mdx @@ -286,13 +286,22 @@ With the above configuration: The above URLs will also be returned by the `getAbsoluteLocaleUrl()` and `getAbsoluteLocaleUrlList()` functions. -## `fallback` +## Fallback -Astro's i18n routing allows you to configure a **fallback routing strategy**. When a page in one language doesn't exist (e.g. a page that is not yet translated), instead of displaying a 404 page, you can redirect a user from one locale to another on a per-language basis. This is useful when you do not yet have a page for every route, but you want to still provide some content to your visitors. +When a page in one language doesn't exist (e.g. a page that is not yet translated), instead of displaying a 404 page, you can choose to display fallback content from another `locale` on a per-language basis. This is useful when you do not yet have a page for every route, but you want to still provide some content to your visitors. -For example, the configuration below sets `es` as the fallback locale for any missing `fr` routes. This means that a user visiting `example.com/fr/my-page/` will be redirected to and shown the content for `example.com/es/my-page/` instead of being taken to a 404 page when `src/pages/fr/my-page.astro` does not exist. +Your fallback strategy consists of two parts: choosing which languages should fallback to which other languages ([`i18n.fallback`](/en/reference/configuration-reference/#i18nfallback)) and choosing whether to perform a [redirect](/en/guides/routing/#redirects) or a [rewrite](/en/guides/routing/#rewrites) to show the fallback content ([`i18n.routing.fallbackType`](/en/reference/configuration-reference/#i18nroutingfallbacktype) added in Astro v4.15.0). -```js title="astro.config.mjs" ins={6-8} +For example, when you configure `i18n.fallback: { fr: "es" }`, Astro will ensure that a page is built in `src/pages/fr/` for every page that exists in `src/pages/es/`. + +If any page does not already exist, then a page will be created depending on your `fallbackType`: + +- With a redirect to the corresponding `es` route (default behavior). +- With the content of the `/es/` page (`i18n.routing.fallbackType: "rewrite"`). + +For example, the configuration below sets `es` as the fallback locale for any missing `fr` routes. This means that a user visiting `example.com/fr/my-page/` will be shown the content for `example.com/es/my-page/` (without being redirected) instead of being taken to a 404 page when `src/pages/fr/my-page.astro` does not exist. + +```js title="astro.config.mjs" ins={6-8,10} import { defineConfig } from "astro/config" export default defineConfig({ i18n: { @@ -300,13 +309,14 @@ export default defineConfig({ locales: ["es", "en", "fr"], fallback: { fr: "es" + }, + routing: { + fallbackType: "rewrite" } } }) ``` -Astro will ensure that a page is built in `src/pages/fr` for every page that exists in `src/pages/es/`. If the page does not already exist, then a page with a redirect to the corresponding `es` route will be created. - ## Custom locale paths In addition to defining your site's supported `locales` as strings (e.g. "en", "pt-br"), Astro also allows you to map an arbitrary number of [browser-recognized language `codes`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language#syntax) to a custom URL `path`. While locales can be strings of any format as long as they correspond to your project folder structure, `codes` must follow the browser's accepted syntax. diff --git a/src/content/docs/en/guides/view-transitions.mdx b/src/content/docs/en/guides/view-transitions.mdx index 75d10a0983e07..57648267d1a07 100644 --- a/src/content/docs/en/guides/view-transitions.mdx +++ b/src/content/docs/en/guides/view-transitions.mdx @@ -328,8 +328,8 @@ The following example shows an Astro component that navigates a visitor to anoth import { navigate } from 'astro:transitions/client'; // Navigate to the selected option automatically. - document.querySelector('select').onchange = (ev) => { - let href = ev.target.value; + document.querySelector('select').onchange = (event) => { + let href = event.target.value; navigate(href); }; @@ -572,9 +572,9 @@ Here is an example of using the `astro:before-preparation` event to load a spinn ```js ``` @@ -634,14 +634,44 @@ At this point of the lifecycle, you could choose to define your own swap impleme ```astro ``` +#### Building a custom swap function + +

+ +The `swapFunction` object of the `astro:transitions/client` module provides five utility functions that handle specific swap-related tasks, including handling document attributes, page elements, and script execution. These functions can be used directly to define a custom swap implementation. + +The following example demonstrates how to use these functions to recreate Astro's built-in swap implementation: + +```astro +