@voltro/cli 0.1.0
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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +20236 -0
- package/bin/voltro.mjs +43 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +9 -0
- package/dist/commands-CfPH2Wf4.js +18061 -0
- package/dist/frameworkInspectState-CX2250XB.js +86 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +5 -0
- package/dist/inspectState.d.ts +116 -0
- package/dist/inspectState.js +2 -0
- package/dist/startup.d.ts +26 -0
- package/dist/startup.js +2 -0
- package/dist/startupRunner-CRhuUl91.js +71 -0
- package/package.json +88 -0
- package/templates/AGENTS.core.md +258 -0
- package/templates/AGENTS.md +351 -0
- package/templates/agent-docs/_index.md +93 -0
- package/templates/agent-docs/_manifest.json +655 -0
- package/templates/agent-docs/ai.md +1845 -0
- package/templates/agent-docs/authentication.md +1788 -0
- package/templates/agent-docs/caching.md +624 -0
- package/templates/agent-docs/cli.md +1650 -0
- package/templates/agent-docs/configuration.md +295 -0
- package/templates/agent-docs/data.md +2172 -0
- package/templates/agent-docs/database/advancedqueries.md +1583 -0
- package/templates/agent-docs/database/columntypes.md +1200 -0
- package/templates/agent-docs/database/hosting.md +881 -0
- package/templates/agent-docs/database/migrations.md +2938 -0
- package/templates/agent-docs/database/misc.md +270 -0
- package/templates/agent-docs/database/overview.md +108 -0
- package/templates/agent-docs/database/querying.md +1622 -0
- package/templates/agent-docs/database/scaling.md +331 -0
- package/templates/agent-docs/database/schema.md +1458 -0
- package/templates/agent-docs/database/seedsdialects.md +1235 -0
- package/templates/agent-docs/database/transactions.md +285 -0
- package/templates/agent-docs/deployment.md +999 -0
- package/templates/agent-docs/internationalization.md +359 -0
- package/templates/agent-docs/introduction.md +438 -0
- package/templates/agent-docs/multi-tenancy.md +610 -0
- package/templates/agent-docs/observability.md +350 -0
- package/templates/agent-docs/plugins.md +1273 -0
- package/templates/agent-docs/reference.md +978 -0
- package/templates/agent-docs/routing.md +1553 -0
- package/templates/agent-docs/scheduling.md +666 -0
- package/templates/agent-docs/schema-driven-ui.md +607 -0
- package/templates/agent-docs/security.md +42 -0
- package/templates/agent-docs/templates/apibackends.md +3214 -0
- package/templates/agent-docs/templates/appshells.md +2062 -0
- package/templates/agent-docs/templates/custom.md +128 -0
- package/templates/agent-docs/templates/overview.md +122 -0
- package/templates/agent-docs/templates/serverless.md +315 -0
- package/templates/agent-docs/testing.md +376 -0
- package/templates/agent-docs/workflows.md +1351 -0
- package/templates/apps/api-ai/README.md +105 -0
- package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
- package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
- package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
- package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
- package/templates/apps/api-ai/app.config.ts +57 -0
- package/templates/apps/api-ai/database/schema.ts +67 -0
- package/templates/apps/api-ai/package.json +27 -0
- package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
- package/templates/apps/api-ai/template.json +6 -0
- package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
- package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
- package/templates/apps/api-ai/tsconfig.json +5 -0
- package/templates/apps/api-auth/.env +17 -0
- package/templates/apps/api-auth/README.md +109 -0
- package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
- package/templates/apps/api-auth/actions/me.action.ts +19 -0
- package/templates/apps/api-auth/app.config.ts +59 -0
- package/templates/apps/api-auth/database/schema.ts +26 -0
- package/templates/apps/api-auth/package.json +28 -0
- package/templates/apps/api-auth/template.json +6 -0
- package/templates/apps/api-auth/tests/me.test.ts +37 -0
- package/templates/apps/api-auth/tsconfig.json +5 -0
- package/templates/apps/api-backend/README.md +25 -0
- package/templates/apps/api-backend/app.config.ts +39 -0
- package/templates/apps/api-backend/database/schema.ts +57 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend/package.json +28 -0
- package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend/template.json +6 -0
- package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend/tsconfig.json +5 -0
- package/templates/apps/api-backend-deactivation/README.md +49 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
- package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
- package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
- package/templates/apps/api-backend-deactivation/package.json +28 -0
- package/templates/apps/api-backend-deactivation/template.json +6 -0
- package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
- package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
- package/templates/apps/api-backend-mail/README.md +39 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
- package/templates/apps/api-backend-mail/app.config.ts +34 -0
- package/templates/apps/api-backend-mail/database/schema.ts +57 -0
- package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mail/package.json +30 -0
- package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mail/template.json +6 -0
- package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mail/tsconfig.json +5 -0
- package/templates/apps/api-backend-mariadb/.env.example +35 -0
- package/templates/apps/api-backend-mariadb/README.md +31 -0
- package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
- package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mariadb/package.json +30 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mariadb/template.json +6 -0
- package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/README.md +86 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
- package/templates/apps/api-backend-storage/app.config.ts +38 -0
- package/templates/apps/api-backend-storage/database/schema.ts +57 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-storage/package.json +27 -0
- package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-storage/template.json +6 -0
- package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-storage/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/.env +17 -0
- package/templates/apps/api-data-advanced/README.md +88 -0
- package/templates/apps/api-data-advanced/app.config.ts +56 -0
- package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
- package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
- package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
- package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
- package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
- package/templates/apps/api-data-advanced/database/index.ts +23 -0
- package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
- package/templates/apps/api-data-advanced/package.json +28 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
- package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
- package/templates/apps/api-data-advanced/template.json +6 -0
- package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
- package/templates/apps/api-data-advanced/tsconfig.json +5 -0
- package/templates/apps/api-durable/README.md +81 -0
- package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
- package/templates/apps/api-durable/app.config.ts +41 -0
- package/templates/apps/api-durable/database/schema.ts +65 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
- package/templates/apps/api-durable/package.json +28 -0
- package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
- package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
- package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
- package/templates/apps/api-durable/template.json +6 -0
- package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
- package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
- package/templates/apps/api-durable/tsconfig.json +5 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
- package/templates/apps/api-feature-flags/README.md +63 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
- package/templates/apps/api-feature-flags/app.config.ts +41 -0
- package/templates/apps/api-feature-flags/database/schema.ts +37 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
- package/templates/apps/api-feature-flags/package.json +29 -0
- package/templates/apps/api-feature-flags/template.json +6 -0
- package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
- package/templates/apps/api-feature-flags/tsconfig.json +5 -0
- package/templates/apps/api-governance/.env +4 -0
- package/templates/apps/api-governance/README.md +61 -0
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
- package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
- package/templates/apps/api-governance/app.config.ts +35 -0
- package/templates/apps/api-governance/database/schema.ts +41 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
- package/templates/apps/api-governance/package.json +29 -0
- package/templates/apps/api-governance/template.json +6 -0
- package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
- package/templates/apps/api-governance/tsconfig.json +5 -0
- package/templates/apps/api-kv/README.md +100 -0
- package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
- package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
- package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
- package/templates/apps/api-kv/app.config.ts +48 -0
- package/templates/apps/api-kv/database/schema.ts +62 -0
- package/templates/apps/api-kv/package.json +28 -0
- package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
- package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
- package/templates/apps/api-kv/template.json +6 -0
- package/templates/apps/api-kv/tests/sync.test.ts +103 -0
- package/templates/apps/api-kv/tsconfig.json +5 -0
- package/templates/apps/api-moderation/README.md +46 -0
- package/templates/apps/api-moderation/app.config.ts +33 -0
- package/templates/apps/api-moderation/database/schema.ts +45 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
- package/templates/apps/api-moderation/package.json +29 -0
- package/templates/apps/api-moderation/template.json +6 -0
- package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
- package/templates/apps/api-moderation/tsconfig.json +5 -0
- package/templates/apps/api-observability/README.md +106 -0
- package/templates/apps/api-observability/app.config.ts +32 -0
- package/templates/apps/api-observability/database/schema.ts +38 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
- package/templates/apps/api-observability/package.json +29 -0
- package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
- package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
- package/templates/apps/api-observability/template.json +6 -0
- package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
- package/templates/apps/api-observability/tsconfig.json +5 -0
- package/templates/apps/api-ratelimit/README.md +49 -0
- package/templates/apps/api-ratelimit/app.config.ts +42 -0
- package/templates/apps/api-ratelimit/database/schema.ts +37 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
- package/templates/apps/api-ratelimit/package.json +29 -0
- package/templates/apps/api-ratelimit/template.json +6 -0
- package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
- package/templates/apps/api-ratelimit/tsconfig.json +5 -0
- package/templates/apps/api-rbac/README.md +59 -0
- package/templates/apps/api-rbac/app.config.ts +48 -0
- package/templates/apps/api-rbac/database/schema.ts +37 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
- package/templates/apps/api-rbac/package.json +29 -0
- package/templates/apps/api-rbac/template.json +6 -0
- package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
- package/templates/apps/api-rbac/tsconfig.json +5 -0
- package/templates/apps/api-rest/README.md +85 -0
- package/templates/apps/api-rest/app.config.ts +70 -0
- package/templates/apps/api-rest/database/schema.ts +52 -0
- package/templates/apps/api-rest/lib/product.ts +30 -0
- package/templates/apps/api-rest/package.json +27 -0
- package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
- package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
- package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
- package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
- package/templates/apps/api-rest/template.json +6 -0
- package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
- package/templates/apps/api-rest/tsconfig.json +5 -0
- package/templates/apps/api-saas/README.md +106 -0
- package/templates/apps/api-saas/app.config.ts +51 -0
- package/templates/apps/api-saas/database/schema.ts +50 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas/package.json +31 -0
- package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
- package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas/template.json +6 -0
- package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
- package/templates/apps/api-saas/tsconfig.json +5 -0
- package/templates/apps/api-search/README.md +68 -0
- package/templates/apps/api-search/app.config.ts +31 -0
- package/templates/apps/api-search/database/schema.ts +47 -0
- package/templates/apps/api-search/lib/search.ts +27 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
- package/templates/apps/api-search/package.json +29 -0
- package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
- package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
- package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
- package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
- package/templates/apps/api-search/template.json +6 -0
- package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
- package/templates/apps/api-search/tsconfig.json +5 -0
- package/templates/apps/api-versioning/README.md +51 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
- package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
- package/templates/apps/api-versioning/app.config.ts +24 -0
- package/templates/apps/api-versioning/database/schema.ts +38 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
- package/templates/apps/api-versioning/package.json +29 -0
- package/templates/apps/api-versioning/template.json +6 -0
- package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
- package/templates/apps/api-versioning/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/.env +6 -0
- package/templates/apps/api-webhooks/README.md +106 -0
- package/templates/apps/api-webhooks/app.config.ts +16 -0
- package/templates/apps/api-webhooks/database/schema.ts +49 -0
- package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
- package/templates/apps/api-webhooks/package.json +28 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
- package/templates/apps/api-webhooks/template.json +6 -0
- package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
- package/templates/apps/api-webhooks/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
- package/templates/apps/changelog/README.md +77 -0
- package/templates/apps/changelog/app.config.ts +27 -0
- package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
- package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
- package/templates/apps/changelog/package.json +30 -0
- package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
- package/templates/apps/changelog/src/globals.css +66 -0
- package/templates/apps/changelog/src/globals.d.ts +16 -0
- package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
- package/templates/apps/changelog/src/lib/locale.ts +55 -0
- package/templates/apps/changelog/src/lib/releases.ts +21 -0
- package/templates/apps/changelog/src/locales/de.ts +22 -0
- package/templates/apps/changelog/src/locales/en.ts +30 -0
- package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
- package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
- package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
- package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
- package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
- package/templates/apps/changelog/src/pages/index.tsx +55 -0
- package/templates/apps/changelog/src/pages/layout.tsx +84 -0
- package/templates/apps/changelog/template.json +6 -0
- package/templates/apps/changelog/tsconfig.json +11 -0
- package/templates/apps/edge-functions/README.md +55 -0
- package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
- package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
- package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
- package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
- package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
- package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
- package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
- package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
- package/templates/apps/edge-functions/package.json +21 -0
- package/templates/apps/edge-functions/template.json +6 -0
- package/templates/apps/edge-functions/tsconfig.json +10 -0
- package/templates/apps/frontend-admin/README.md +40 -0
- package/templates/apps/frontend-admin/app.config.ts +37 -0
- package/templates/apps/frontend-admin/package.json +30 -0
- package/templates/apps/frontend-admin/src/config.ts +8 -0
- package/templates/apps/frontend-admin/src/globals.css +76 -0
- package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
- package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
- package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
- package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
- package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
- package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
- package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
- package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
- package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
- package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-admin/template.json +6 -0
- package/templates/apps/frontend-admin/tsconfig.json +11 -0
- package/templates/apps/frontend-app/README.md +98 -0
- package/templates/apps/frontend-app/app.config.ts +43 -0
- package/templates/apps/frontend-app/package.json +30 -0
- package/templates/apps/frontend-app/src/locales/de.ts +36 -0
- package/templates/apps/frontend-app/src/locales/en.ts +43 -0
- package/templates/apps/frontend-app/src/locales/index.ts +15 -0
- package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
- package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
- package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
- package/templates/apps/frontend-app/template.json +6 -0
- package/templates/apps/frontend-app/tsconfig.json +11 -0
- package/templates/apps/frontend-blank/README.md +18 -0
- package/templates/apps/frontend-blank/app.config.ts +29 -0
- package/templates/apps/frontend-blank/package.json +29 -0
- package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
- package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
- package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
- package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
- package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
- package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
- package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
- package/templates/apps/frontend-blank/template.json +6 -0
- package/templates/apps/frontend-blank/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/README.md +65 -0
- package/templates/apps/frontend-contact/app.config.ts +25 -0
- package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
- package/templates/apps/frontend-contact/package.json +33 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
- package/templates/apps/frontend-contact/src/config.ts +12 -0
- package/templates/apps/frontend-contact/src/globals.css +84 -0
- package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
- package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
- package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
- package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
- package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
- package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
- package/templates/apps/frontend-contact/template.json +6 -0
- package/templates/apps/frontend-contact/tsconfig.json +11 -0
- package/templates/apps/frontend-dashboard/README.md +54 -0
- package/templates/apps/frontend-dashboard/app.config.ts +39 -0
- package/templates/apps/frontend-dashboard/package.json +29 -0
- package/templates/apps/frontend-dashboard/src/config.ts +8 -0
- package/templates/apps/frontend-dashboard/src/globals.css +72 -0
- package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
- package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
- package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
- package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
- package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
- package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-dashboard/template.json +6 -0
- package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
- package/templates/apps/frontend-docs/README.md +19 -0
- package/templates/apps/frontend-docs/app.config.ts +25 -0
- package/templates/apps/frontend-docs/package.json +29 -0
- package/templates/apps/frontend-docs/src/globals.css +32 -0
- package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
- package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
- package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
- package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
- package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-docs/template.json +6 -0
- package/templates/apps/frontend-docs/tsconfig.json +11 -0
- package/templates/apps/frontend-i18n/README.md +61 -0
- package/templates/apps/frontend-i18n/app.config.ts +33 -0
- package/templates/apps/frontend-i18n/package.json +28 -0
- package/templates/apps/frontend-i18n/src/globals.css +46 -0
- package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
- package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
- package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
- package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
- package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
- package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
- package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
- package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
- package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
- package/templates/apps/frontend-i18n/template.json +6 -0
- package/templates/apps/frontend-i18n/tsconfig.json +11 -0
- package/templates/apps/frontend-landing/README.md +17 -0
- package/templates/apps/frontend-landing/app.config.ts +25 -0
- package/templates/apps/frontend-landing/package.json +29 -0
- package/templates/apps/frontend-landing/src/globals.css +23 -0
- package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
- package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
- package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
- package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
- package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-landing/template.json +6 -0
- package/templates/apps/frontend-landing/tsconfig.json +11 -0
- package/templates/apps/frontend-spa/README.md +45 -0
- package/templates/apps/frontend-spa/app.config.ts +27 -0
- package/templates/apps/frontend-spa/package.json +29 -0
- package/templates/apps/frontend-spa/src/globals.css +84 -0
- package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
- package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
- package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
- package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
- package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
- package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-spa/template.json +6 -0
- package/templates/apps/frontend-spa/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr/README.md +68 -0
- package/templates/apps/frontend-ssr/app.config.ts +32 -0
- package/templates/apps/frontend-ssr/package.json +29 -0
- package/templates/apps/frontend-ssr/src/globals.css +67 -0
- package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
- package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
- package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
- package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
- package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
- package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
- package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
- package/templates/apps/frontend-ssr/template.json +6 -0
- package/templates/apps/frontend-ssr/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr-api/README.md +50 -0
- package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
- package/templates/apps/frontend-ssr-api/package.json +30 -0
- package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
- package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
- package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
- package/templates/apps/frontend-ssr-api/template.json +6 -0
- package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
- package/templates/apps/frontend-static-blog/README.md +49 -0
- package/templates/apps/frontend-static-blog/app.config.ts +34 -0
- package/templates/apps/frontend-static-blog/package.json +28 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
- package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
- package/templates/apps/frontend-static-blog/src/globals.css +75 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
- package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
- package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
- package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
- package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
- package/templates/apps/frontend-static-blog/template.json +6 -0
- package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
- package/templates/baselines/bare/.env.example +37 -0
- package/templates/baselines/bare/README.md +47 -0
- package/templates/baselines/bare/baseline.json +31 -0
- package/templates/baselines/bare/deploy/README.md +43 -0
- package/templates/baselines/bare/deploy/voltro.service.example +36 -0
- package/templates/baselines/compose/.env.example +46 -0
- package/templates/baselines/compose/README.md +69 -0
- package/templates/baselines/compose/baseline.json +51 -0
- package/templates/baselines/compose/docker/.dockerignore +38 -0
- package/templates/baselines/compose/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose/docker-compose.dev.yml +89 -0
- package/templates/baselines/compose/docker-compose.prod.yml +87 -0
- package/templates/baselines/compose/docker-compose.yml +41 -0
- package/templates/baselines/compose-mariadb/.env.example +57 -0
- package/templates/baselines/compose-mariadb/README.md +78 -0
- package/templates/baselines/compose-mariadb/baseline.json +51 -0
- package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
- package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
- package/templates/baselines/helm/.env.example +39 -0
- package/templates/baselines/helm/README.md +98 -0
- package/templates/baselines/helm/baseline.json +53 -0
- package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
- package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
- package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
- package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
- package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
- package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
- package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
- package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
- package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
- package/templates/baselines/helm/deploy/README.md +94 -0
- package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/feed-swr` ISR + stale-while-revalidate page — its cache config exports,
|
|
4
|
+
// the loader, and the render.
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
7
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
8
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
9
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
10
|
+
import enCatalog from '../locales/en'
|
|
11
|
+
|
|
12
|
+
const loaderData = vi.fn<() => unknown>()
|
|
13
|
+
vi.mock('@voltro/web', () => ({ useLoaderData: () => loaderData() }))
|
|
14
|
+
|
|
15
|
+
const { default: FeedSwr, meta, loader, renderMode, revalidate, staleWhileRevalidate } =
|
|
16
|
+
await import('./feed-swr')
|
|
17
|
+
|
|
18
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
19
|
+
|
|
20
|
+
let container: HTMLDivElement
|
|
21
|
+
let root: Root
|
|
22
|
+
|
|
23
|
+
// The page uses <T>, so it needs an <I18nProvider> (real English catalog).
|
|
24
|
+
const render = (node: ReactNode): void => {
|
|
25
|
+
container = document.createElement('div')
|
|
26
|
+
document.body.appendChild(container)
|
|
27
|
+
act(() => {
|
|
28
|
+
root = createRoot(container)
|
|
29
|
+
root.render(
|
|
30
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
loaderData.mockReset()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
if (root) act(() => root.unmount())
|
|
41
|
+
container?.remove()
|
|
42
|
+
document.body.innerHTML = ''
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('feed-swr — cache config', () => {
|
|
46
|
+
test('is ISR with both a revalidate and a stale-while-revalidate window', () => {
|
|
47
|
+
expect(renderMode).toBe('isr')
|
|
48
|
+
expect(revalidate).toBe('5 seconds')
|
|
49
|
+
expect(staleWhileRevalidate).toBe('30 seconds')
|
|
50
|
+
expect(meta({ locale: 'en' }).title).toContain('stale-while-revalidate')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('feed-swr — loader', () => {
|
|
55
|
+
test('resolves a fresh ISO timestamp + a numeric nonce', async () => {
|
|
56
|
+
const data = await loader({} as never)
|
|
57
|
+
expect(Number.isNaN(Date.parse(data.renderedAt))).toBe(false)
|
|
58
|
+
expect(typeof data.nonce).toBe('number')
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('feed-swr — render', () => {
|
|
63
|
+
test('renders the loader data', () => {
|
|
64
|
+
loaderData.mockReturnValue({ renderedAt: '2020-01-01T00:00:00.000Z', nonce: 99 })
|
|
65
|
+
render(createElement(FeedSwr))
|
|
66
|
+
expect(container.textContent).toContain('2020-01-01T00:00:00.000Z')
|
|
67
|
+
expect(container.textContent).toContain('99')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// `/feed-swr` — ISR with `staleWhileRevalidate`. When the `revalidate` window
|
|
2
|
+
// expires, instead of blocking the next visitor on a fresh render, the cache
|
|
3
|
+
// serves the STALE HTML immediately AND kicks off a background re-render. The
|
|
4
|
+
// visitor never waits; the cache catches up out-of-band.
|
|
5
|
+
|
|
6
|
+
import type { ReactNode } from 'react'
|
|
7
|
+
import { useLoaderData, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
8
|
+
import { T } from '@voltro/i18n'
|
|
9
|
+
import { getCatalog } from '../locales'
|
|
10
|
+
|
|
11
|
+
export const renderMode = 'isr' as const
|
|
12
|
+
export const revalidate = '5 seconds'
|
|
13
|
+
// Within this window AFTER `revalidate` expires, requests get the STALE HTML
|
|
14
|
+
// instantly + a background refresh runs. Beyond (revalidate + swr) the next
|
|
15
|
+
// request blocks on a fresh render, like plain ISR.
|
|
16
|
+
export const staleWhileRevalidate = '30 seconds'
|
|
17
|
+
|
|
18
|
+
// Locale-aware tab title + description (cookie-i18n — @voltro/web hands meta
|
|
19
|
+
// the active locale).
|
|
20
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
21
|
+
title: getCatalog(locale)['meta.swr.title'],
|
|
22
|
+
description: getCatalog(locale)['meta.swr.description'],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
interface SwrData {
|
|
26
|
+
readonly renderedAt: string
|
|
27
|
+
readonly nonce: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const loader: LoaderFn<SwrData> = async () => ({
|
|
31
|
+
renderedAt: new Date().toISOString(),
|
|
32
|
+
nonce: Math.floor(Math.random() * 1_000_000),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export default function FeedSwr(): ReactNode {
|
|
36
|
+
const data = useLoaderData<SwrData>()
|
|
37
|
+
const code = (c: ReactNode) => <code>{c}</code>
|
|
38
|
+
return (
|
|
39
|
+
<section>
|
|
40
|
+
<span className="badge"><T id="swr.badge" /></span>
|
|
41
|
+
<h1 style={{ marginTop: '0.75rem' }}><T id="swr.heading" /></h1>
|
|
42
|
+
<p className="lead"><T id="swr.lead" /></p>
|
|
43
|
+
|
|
44
|
+
<ul className="fields">
|
|
45
|
+
<li><span className="k"><T id="field.renderedAt" /></span><span className="v">{data.renderedAt}</span></li>
|
|
46
|
+
<li><span className="k"><T id="field.nonce" /></span><span className="v">{data.nonce}</span></li>
|
|
47
|
+
</ul>
|
|
48
|
+
|
|
49
|
+
<p className="note">
|
|
50
|
+
<T id="swr.note" values={{ code }} />
|
|
51
|
+
</p>
|
|
52
|
+
</section>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/feed` ISR page — its cache config exports (revalidate, tenantAware), the
|
|
4
|
+
// loader reading the `x-tenant` header, and the render.
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
7
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
8
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
9
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
10
|
+
import enCatalog from '../locales/en'
|
|
11
|
+
|
|
12
|
+
const loaderData = vi.fn<() => unknown>()
|
|
13
|
+
vi.mock('@voltro/web', () => ({ useLoaderData: () => loaderData() }))
|
|
14
|
+
|
|
15
|
+
const { default: Feed, meta, loader, renderMode, revalidate, tenantAware } = await import('./feed')
|
|
16
|
+
|
|
17
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
18
|
+
|
|
19
|
+
let container: HTMLDivElement
|
|
20
|
+
let root: Root
|
|
21
|
+
|
|
22
|
+
// The page uses <T>, so it needs an <I18nProvider> (real English catalog).
|
|
23
|
+
const render = (node: ReactNode): void => {
|
|
24
|
+
container = document.createElement('div')
|
|
25
|
+
document.body.appendChild(container)
|
|
26
|
+
act(() => {
|
|
27
|
+
root = createRoot(container)
|
|
28
|
+
root.render(
|
|
29
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
loaderData.mockReset()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
if (root) act(() => root.unmount())
|
|
40
|
+
container?.remove()
|
|
41
|
+
document.body.innerHTML = ''
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('feed — cache config', () => {
|
|
45
|
+
test('is ISR with a revalidate window and is tenant-aware', () => {
|
|
46
|
+
expect(renderMode).toBe('isr')
|
|
47
|
+
expect(revalidate).toBe('10 seconds')
|
|
48
|
+
expect(tenantAware).toBe(true)
|
|
49
|
+
expect(meta({ locale: 'en' }).title).toContain('ISR')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('feed — loader', () => {
|
|
54
|
+
test('reads the tenant from the x-tenant header', async () => {
|
|
55
|
+
const data = await loader({ headers: { 'x-tenant': 'acme' } } as never)
|
|
56
|
+
expect(data.tenant).toBe('acme')
|
|
57
|
+
expect(Number.isNaN(Date.parse(data.renderedAt))).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('falls back to `anonymous` when x-tenant is absent', async () => {
|
|
61
|
+
const data = await loader({ headers: {} } as never)
|
|
62
|
+
expect(data.tenant).toBe('anonymous')
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('feed — render', () => {
|
|
67
|
+
test('renders the tenant + nonce from loader data', () => {
|
|
68
|
+
loaderData.mockReturnValue({ renderedAt: '2020-01-01T00:00:00.000Z', nonce: 7, tenant: 'acme' })
|
|
69
|
+
render(createElement(Feed))
|
|
70
|
+
expect(container.textContent).toContain('acme')
|
|
71
|
+
expect(container.textContent).toContain('7')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// `/feed` — `renderMode: 'isr'`. Same render path as SSR, but `voltro start`
|
|
2
|
+
// CACHES the HTML and serves the cached copy to every request within the
|
|
3
|
+
// `revalidate` window. After the window expires, the next request re-renders
|
|
4
|
+
// and replaces the cache.
|
|
5
|
+
//
|
|
6
|
+
// Use ISR for content that changes occasionally, is the same for every
|
|
7
|
+
// visitor (or only per-tenant), and is expensive to render (a CMS lookup, a
|
|
8
|
+
// third-party API). Cache hits return in <1ms.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import { useLoaderData, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
12
|
+
import { T } from '@voltro/i18n'
|
|
13
|
+
import { getCatalog } from '../locales'
|
|
14
|
+
|
|
15
|
+
export const renderMode = 'isr' as const
|
|
16
|
+
|
|
17
|
+
// Cache window — a number (seconds) OR a string like '1 hour' / '30 seconds'.
|
|
18
|
+
export const revalidate = '10 seconds'
|
|
19
|
+
|
|
20
|
+
// Tenant-aware caching: the cache key includes the `x-tenant` request header,
|
|
21
|
+
// so tenant A's render is never served to tenant B. Leave it off for pages
|
|
22
|
+
// that don't vary by tenant (they then share ONE cache entry).
|
|
23
|
+
export const tenantAware = true
|
|
24
|
+
|
|
25
|
+
// Locale-aware tab title + description (cookie-i18n — @voltro/web hands meta
|
|
26
|
+
// the active locale).
|
|
27
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
28
|
+
title: getCatalog(locale)['meta.isr.title'],
|
|
29
|
+
description: getCatalog(locale)['meta.isr.description'],
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
interface FeedData {
|
|
33
|
+
readonly renderedAt: string
|
|
34
|
+
readonly nonce: number
|
|
35
|
+
readonly tenant: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const loader: LoaderFn<FeedData> = async ({ headers }) => ({
|
|
39
|
+
renderedAt: new Date().toISOString(),
|
|
40
|
+
nonce: Math.floor(Math.random() * 1_000_000),
|
|
41
|
+
tenant: headers?.['x-tenant'] ?? 'anonymous',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export default function Feed(): ReactNode {
|
|
45
|
+
const data = useLoaderData<FeedData>()
|
|
46
|
+
const code = (c: ReactNode) => <code>{c}</code>
|
|
47
|
+
return (
|
|
48
|
+
<section>
|
|
49
|
+
<span className="badge"><T id="isr.badge" /></span>
|
|
50
|
+
<h1 style={{ marginTop: '0.75rem' }}><T id="isr.heading" /></h1>
|
|
51
|
+
<p className="lead"><T id="isr.lead" /></p>
|
|
52
|
+
|
|
53
|
+
<ul className="fields">
|
|
54
|
+
<li><span className="k"><T id="field.renderedAt" /></span><span className="v">{data.renderedAt}</span></li>
|
|
55
|
+
<li><span className="k"><T id="field.nonce" /></span><span className="v">{data.nonce}</span></li>
|
|
56
|
+
<li><span className="k"><T id="isr.field.tenant" /></span><span className="v">{data.tenant}</span></li>
|
|
57
|
+
</ul>
|
|
58
|
+
|
|
59
|
+
<p className="note">
|
|
60
|
+
<T id="isr.note" values={{ code }} />
|
|
61
|
+
</p>
|
|
62
|
+
</section>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/` SSR page — its `meta`, its per-request `loader`, and the render reading
|
|
4
|
+
// both the loader data and the incoming request (via useServerRequest). The
|
|
5
|
+
// hooks are backed by a <Router> context in the real app, so we mock
|
|
6
|
+
// `@voltro/web` and drive the data directly.
|
|
7
|
+
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
9
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
10
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
11
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
12
|
+
import enCatalog from '../locales/en'
|
|
13
|
+
|
|
14
|
+
const loaderData = vi.fn<() => unknown>()
|
|
15
|
+
const serverRequest = vi.fn<() => unknown>()
|
|
16
|
+
vi.mock('@voltro/web', () => ({
|
|
17
|
+
useLoaderData: () => loaderData(),
|
|
18
|
+
useServerRequest: () => serverRequest(),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
const { default: Home, meta, loader, renderMode } = await import('./index')
|
|
22
|
+
|
|
23
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
24
|
+
|
|
25
|
+
let container: HTMLDivElement
|
|
26
|
+
let root: Root
|
|
27
|
+
|
|
28
|
+
// The page uses <T>, so it needs an <I18nProvider>. Wrap with the real English
|
|
29
|
+
// catalog (the framework auto-wires this provider in the running app).
|
|
30
|
+
const render = (node: ReactNode): void => {
|
|
31
|
+
container = document.createElement('div')
|
|
32
|
+
document.body.appendChild(container)
|
|
33
|
+
act(() => {
|
|
34
|
+
root = createRoot(container)
|
|
35
|
+
root.render(
|
|
36
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
loaderData.mockReset()
|
|
43
|
+
serverRequest.mockReset()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
if (root) act(() => root.unmount())
|
|
48
|
+
container?.remove()
|
|
49
|
+
document.body.innerHTML = ''
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('index — config + meta', () => {
|
|
53
|
+
test('renders per request (ssr) and declares a title', () => {
|
|
54
|
+
expect(renderMode).toBe('ssr')
|
|
55
|
+
const m = meta({ locale: 'en' })
|
|
56
|
+
expect(m.title).toContain('SSR')
|
|
57
|
+
expect(m.description).toContain('every request')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('index — loader', () => {
|
|
62
|
+
test('resolves a fresh ISO timestamp + a numeric nonce', async () => {
|
|
63
|
+
const data = await loader({} as never)
|
|
64
|
+
expect(Number.isNaN(Date.parse(data.renderedAt))).toBe(false)
|
|
65
|
+
expect(typeof data.nonce).toBe('number')
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('index — render', () => {
|
|
70
|
+
test('shows the loader data + falls back to `unset` when the request is empty', () => {
|
|
71
|
+
loaderData.mockReturnValue({ renderedAt: '2020-01-01T00:00:00.000Z', nonce: 42 })
|
|
72
|
+
serverRequest.mockReturnValue(undefined)
|
|
73
|
+
render(createElement(Home))
|
|
74
|
+
expect(container.textContent).toContain('2020-01-01T00:00:00.000Z')
|
|
75
|
+
expect(container.textContent).toContain('42')
|
|
76
|
+
expect(container.textContent).toContain('unset')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('surfaces the accept-language header + theme cookie from the request', () => {
|
|
80
|
+
loaderData.mockReturnValue({ renderedAt: '2020-01-01T00:00:00.000Z', nonce: 1 })
|
|
81
|
+
serverRequest.mockReturnValue({
|
|
82
|
+
headers: { 'accept-language': 'de-DE' },
|
|
83
|
+
cookies: { 'voltro:theme': 'dark' },
|
|
84
|
+
})
|
|
85
|
+
render(createElement(Home))
|
|
86
|
+
expect(container.textContent).toContain('de-DE')
|
|
87
|
+
expect(container.textContent).toContain('dark')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// `/` — `renderMode: 'ssr'`. The loader runs on the SERVER on EVERY request
|
|
2
|
+
// (`voltro start`), so the page is rendered fresh per visit — the timestamp
|
|
3
|
+
// and nonce change on each refresh, and the loader can read the incoming
|
|
4
|
+
// request (cookies, headers) to personalise the HTML before it's sent.
|
|
5
|
+
//
|
|
6
|
+
// Use SSR for anything that varies per request: a logged-in dashboard, a
|
|
7
|
+
// page that reads the visitor's cookie/locale, or any data that must NOT be
|
|
8
|
+
// cached across requests.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import { useLoaderData, useServerRequest, type LoaderFn, type PageMeta } from '@voltro/web'
|
|
12
|
+
import { T } from '@voltro/i18n'
|
|
13
|
+
import { getCatalog } from '../locales'
|
|
14
|
+
|
|
15
|
+
export const renderMode = 'ssr' as const
|
|
16
|
+
|
|
17
|
+
// Locale-aware tab title + description: @voltro/web resolves meta({ locale })
|
|
18
|
+
// from the active locale (the `voltro:lang` cookie in this cookie-i18n app).
|
|
19
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
20
|
+
title: getCatalog(locale)['meta.ssr.title'],
|
|
21
|
+
description: getCatalog(locale)['meta.ssr.description'],
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
interface HomeData {
|
|
25
|
+
readonly renderedAt: string
|
|
26
|
+
readonly nonce: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The loader runs server-side per request. `headers` carries the incoming
|
|
30
|
+
// request headers (lowercased). To pull fresh data from YOUR reactive api
|
|
31
|
+
// instead of computing it here, declare an `apis` entry in app.config.ts and:
|
|
32
|
+
//
|
|
33
|
+
// export const loader: LoaderFn<HomeData> = async ({ query, headers }) => ({
|
|
34
|
+
// // `query` is present ONLY server-side (ssr/isr). It calls your api's
|
|
35
|
+
// // rpc directly, forwarding the session cookie — same Subject as the WS.
|
|
36
|
+
// latest: await query!('posts.latest', {}),
|
|
37
|
+
// })
|
|
38
|
+
//
|
|
39
|
+
export const loader: LoaderFn<HomeData> = async () => ({
|
|
40
|
+
renderedAt: new Date().toISOString(),
|
|
41
|
+
nonce: Math.floor(Math.random() * 1_000_000),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export default function Home(): ReactNode {
|
|
45
|
+
const data = useLoaderData<HomeData>()
|
|
46
|
+
// `useServerRequest()` reads the request's cookies + headers — on the
|
|
47
|
+
// server during SSR, and the same shape on the client. Drive cookie-backed
|
|
48
|
+
// prefs (locale, theme) with it WITHOUT a hydration flash.
|
|
49
|
+
const req = useServerRequest()
|
|
50
|
+
const acceptLanguage = req?.headers['accept-language'] ?? 'unset'
|
|
51
|
+
const themeCookie = req?.cookies['voltro:theme'] ?? 'unset'
|
|
52
|
+
const code = (c: ReactNode) => <code>{c}</code>
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<section>
|
|
56
|
+
<span className="badge"><T id="ssr.badge" /></span>
|
|
57
|
+
<h1 style={{ marginTop: '0.75rem' }}><T id="ssr.heading" /></h1>
|
|
58
|
+
<p className="lead"><T id="ssr.lead" /></p>
|
|
59
|
+
|
|
60
|
+
<ul className="fields">
|
|
61
|
+
<li><span className="k"><T id="field.renderedAt" /></span><span className="v">{data.renderedAt}</span></li>
|
|
62
|
+
<li><span className="k"><T id="field.nonce" /></span><span className="v">{data.nonce}</span></li>
|
|
63
|
+
<li><span className="k"><T id="ssr.field.acceptLanguage" /></span><span className="v">{acceptLanguage}</span></li>
|
|
64
|
+
<li><span className="k"><T id="ssr.field.themeCookie" /></span><span className="v">{themeCookie}</span></li>
|
|
65
|
+
</ul>
|
|
66
|
+
|
|
67
|
+
<p className="note">
|
|
68
|
+
<T id="ssr.note" values={{ code }} />
|
|
69
|
+
</p>
|
|
70
|
+
</section>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Root layout — nav + globals. These pages hydrate fully (the default for
|
|
2
|
+
// ssr/isr), so internal <a> links do client-side navigation (the Router
|
|
3
|
+
// intercepts them) after the first load.
|
|
4
|
+
//
|
|
5
|
+
// The app is bilingual (see `locales` in app.config.ts): nav labels come from
|
|
6
|
+
// the catalog via `<T>`, and <LocaleSwitcher> writes the `voltro:lang` cookie
|
|
7
|
+
// + reloads so the framework re-renders in the chosen language.
|
|
8
|
+
|
|
9
|
+
import type { ReactNode } from 'react'
|
|
10
|
+
import { T, useLocale, useT } from '@voltro/i18n'
|
|
11
|
+
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
12
|
+
import '../globals.css'
|
|
13
|
+
|
|
14
|
+
const LOCALES = [
|
|
15
|
+
{ code: 'en', label: 'English' },
|
|
16
|
+
{ code: 'de', label: 'Deutsch' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export default function Layout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
20
|
+
const locale = useLocale()
|
|
21
|
+
const langLabel = useT('lang.label')
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<nav className="top">
|
|
25
|
+
{/* Plain <a> — the Router intercepts internal anchors for SPA nav (no
|
|
26
|
+
full reload), same as the reference templates. */}
|
|
27
|
+
<a href="/"><T id="nav.ssr" /></a>
|
|
28
|
+
<a href="/feed"><T id="nav.isr" /></a>
|
|
29
|
+
<a href="/feed-swr"><T id="nav.swr" /></a>
|
|
30
|
+
<span style={{ marginLeft: 'auto' }}>
|
|
31
|
+
<LocaleSwitcher locales={LOCALES} current={locale} ariaLabel={langLabel} />
|
|
32
|
+
</span>
|
|
33
|
+
</nav>
|
|
34
|
+
<main>{children}</main>
|
|
35
|
+
</>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-ssr",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "Server-rendered pages — the SSR + ISR render modes. A per-request SSR page (reads cookies/headers via useServerRequest, fresh every visit) plus ISR pages (revalidate, stale-while-revalidate, tenant-aware caching). Self-contained: builds + runs with zero infra; swap the loader for ctx.query to pull from your api.",
|
|
5
|
+
"tags": ["web", "frontend", "ssr", "isr", "render-mode", "revalidate", "cache", "swr"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler"
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*"],
|
|
10
|
+
"exclude": ["node_modules", "dist", ".framework"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro web app scaffold (template: **frontend-ssr-api**) — server-rendered
|
|
4
|
+
pages fed by a sibling api.
|
|
5
|
+
|
|
6
|
+
## Boot — needs a paired api
|
|
7
|
+
|
|
8
|
+
This template consumes an api that exposes `notes.list` / `notes.create`.
|
|
9
|
+
Scaffold both:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
voltro create-project acme --api=api-backend --web=frontend-ssr-api
|
|
13
|
+
pnpm install
|
|
14
|
+
# api in one terminal, web in another:
|
|
15
|
+
pnpm --filter @acme/api dev
|
|
16
|
+
pnpm --filter @acme/web dev # → http://localhost:{{port}}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What this shows
|
|
20
|
+
|
|
21
|
+
- **SSR fed by your backend** — `src/pages/index.tsx` is `renderMode: 'ssr'`.
|
|
22
|
+
Its loader runs on the server on every request and calls
|
|
23
|
+
`query('notes.list', {})` over the api's `POST /rpc` surface (forwarding the
|
|
24
|
+
session cookie, so the same Subject + tenant resolve as the WebSocket path).
|
|
25
|
+
Real rows are in the **first paint** and the document `<title>` — crawlable,
|
|
26
|
+
no client round-trip.
|
|
27
|
+
- **`meta` from loader data** — the `<title>` reflects the real note count in
|
|
28
|
+
the server-rendered HTML (good for SEO + social cards), not a client guess.
|
|
29
|
+
- **SSR → live, one data source** — the component renders the SSR'd snapshot,
|
|
30
|
+
then `useSubscription('app', 'notes.list')` upgrades it to live once the
|
|
31
|
+
WebSocket connects (`useSubscription` is `undefined` during SSR, so the
|
|
32
|
+
first paint is the loader's data; it swaps to live on the client).
|
|
33
|
+
|
|
34
|
+
## How the pairing resolves
|
|
35
|
+
|
|
36
|
+
`apis.app.package` in `app.config.ts` points at the sibling api package
|
|
37
|
+
(`@{{projectName}}/api`). The api's port is auto-discovered from its own
|
|
38
|
+
`app.config.ts`, and codegen pulls its typed rpc surface — so `query` and
|
|
39
|
+
`useSubscription` are end-to-end typed against the api's schema.
|
|
40
|
+
|
|
41
|
+
## SSR vs the other render modes
|
|
42
|
+
|
|
43
|
+
- This page must hit the api per request → `renderMode: 'ssr'`.
|
|
44
|
+
- If the data changes only occasionally and can be cached, use `'isr'`
|
|
45
|
+
(`export const revalidate = '5 minutes'`) — same loader, cached HTML.
|
|
46
|
+
- For a fully client-reactive page (no SSR), use `useSubscription` /
|
|
47
|
+
`useMutation` in a default-mode page — see `frontend-app`.
|
|
48
|
+
|
|
49
|
+
See AGENTS.md › *Server-side rpc in loaders (`ctx.query`)* for the full
|
|
50
|
+
reference.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Web app for the {{projectName}} project — SERVER-RENDERED, fed by a sibling api.
|
|
2
|
+
//
|
|
3
|
+
// The index page is `renderMode: 'ssr'`: its loader runs on the SERVER on
|
|
4
|
+
// every request and calls `ctx.query('notes.list', {})` over the api's
|
|
5
|
+
// POST /rpc surface, so real rows are baked into the FIRST paint + the
|
|
6
|
+
// document <title> — no client round-trip, crawlable, correct for SEO. The
|
|
7
|
+
// component then upgrades the SAME data to live with `useSubscription`.
|
|
8
|
+
//
|
|
9
|
+
// This pairs with an api that exposes `notes.list` — scaffold one with
|
|
10
|
+
// `--api=api-backend`. The `apis.app.package` resolves that sibling api in
|
|
11
|
+
// the project; its port is auto-discovered from its own app.config.ts, and
|
|
12
|
+
// codegen pulls its typed rpc surface so `query` + `useSubscription` are
|
|
13
|
+
// end-to-end typed.
|
|
14
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
15
|
+
|
|
16
|
+
// Public, build-time env (baked into the bundle; read via `publicEnv`).
|
|
17
|
+
// Never a secret here — declare secrets on the api.
|
|
18
|
+
export const env = defineEnv({
|
|
19
|
+
VOLTRO_PUBLIC_APP_NAME: envVar.string({ access: 'public', default: '{{capProjectName}}' }),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
type: 'web' as const,
|
|
24
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
25
|
+
port: {{port}},
|
|
26
|
+
theme: 'system' as const,
|
|
27
|
+
env,
|
|
28
|
+
// Bilingual by default. The framework auto-wires a cookie-driven
|
|
29
|
+
// <I18nProvider> from these codes and loads the matching
|
|
30
|
+
// `src/locales/<code>.ts` catalog — so `<T id="…" />` / `useT()` /
|
|
31
|
+
// `useLocale()` work app-wide (SSR too) with no manual setup. The
|
|
32
|
+
// <LocaleSwitcher> in the layout writes the `voltro:lang` cookie + reloads.
|
|
33
|
+
// Grow the app to more languages by adding a code here + a
|
|
34
|
+
// `src/locales/<code>.ts`.
|
|
35
|
+
locales: ['en', 'de'] as const,
|
|
36
|
+
defaultLocale: 'en' as const,
|
|
37
|
+
// The api this frontend consumes. The NAME ('app') is the lookup key the
|
|
38
|
+
// loader's `query` + the hooks use: `query('notes.list', {})`,
|
|
39
|
+
// `useSubscription('app', 'notes.list')`.
|
|
40
|
+
apis: {
|
|
41
|
+
app: { package: '@{{projectName}}/api' },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "voltro dev .",
|
|
8
|
+
"build": "voltro build .",
|
|
9
|
+
"start": "voltro start .",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "voltro test"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@{{projectName}}/api": "0.1.0",
|
|
15
|
+
"@voltro/cli": "0.1.0",
|
|
16
|
+
"@voltro/client": "0.1.0",
|
|
17
|
+
"@voltro/env": "0.1.0",
|
|
18
|
+
"@voltro/i18n": "0.1.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.1.0",
|
|
20
|
+
"@voltro/web": "0.1.0",
|
|
21
|
+
"react": "^19.0.0",
|
|
22
|
+
"react-dom": "^19.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^19.0.0",
|
|
26
|
+
"@types/react-dom": "^19.0.0",
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vitest": "^3.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Plain CSS — no Tailwind, so there's no @source scanner to configure. */
|
|
2
|
+
:root {
|
|
3
|
+
--bg: #ffffff;
|
|
4
|
+
--fg: #0a0a0a;
|
|
5
|
+
--muted: #6b7280;
|
|
6
|
+
--accent: #4f46e5;
|
|
7
|
+
--border: #e5e7eb;
|
|
8
|
+
--panel: #f9fafb;
|
|
9
|
+
}
|
|
10
|
+
:root.dark {
|
|
11
|
+
--bg: #0a0a0a;
|
|
12
|
+
--fg: #fafafa;
|
|
13
|
+
--muted: #9ca3af;
|
|
14
|
+
--accent: #818cf8;
|
|
15
|
+
--border: #1f2937;
|
|
16
|
+
--panel: #111827;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* { box-sizing: border-box; }
|
|
20
|
+
body {
|
|
21
|
+
margin: 0;
|
|
22
|
+
background: var(--bg);
|
|
23
|
+
color: var(--fg);
|
|
24
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
25
|
+
line-height: 1.55;
|
|
26
|
+
}
|
|
27
|
+
.shell { max-width: 40rem; margin: 0 auto; padding: 2.5rem 1.25rem; }
|
|
28
|
+
.badge { display: inline-block; font-size: 0.75rem; font-weight: 600; color: var(--accent); background: color-mix(in oklab, var(--accent) 14%, transparent); padding: 0.15rem 0.5rem; border-radius: 999px; }
|
|
29
|
+
h1 { margin: 0.75rem 0 0.25rem; }
|
|
30
|
+
.lead { color: var(--muted); margin-top: 0; }
|
|
31
|
+
ul.notes { list-style: none; padding: 0; margin: 1.5rem 0 0; display: flex; flex-direction: column; gap: 0.6rem; }
|
|
32
|
+
ul.notes li { border: 1px solid var(--border); border-radius: 10px; padding: 0.8rem 1rem; background: var(--panel); }
|
|
33
|
+
ul.notes .title { font-weight: 600; }
|
|
34
|
+
ul.notes .done { color: var(--accent); }
|
|
35
|
+
ul.notes .body { color: var(--muted); font-size: 0.9rem; margin-top: 0.2rem; }
|
|
36
|
+
.empty { color: var(--muted); margin-top: 1.5rem; }
|
|
37
|
+
.muted { color: var(--muted); }
|
|
38
|
+
code { background: color-mix(in oklab, var(--fg) 8%, transparent); padding: 0.1em 0.35em; border-radius: 4px; }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Ambient declarations.
|
|
2
|
+
//
|
|
3
|
+
// `declare module '*.css'`: TypeScript with `moduleResolution: Bundler`
|
|
4
|
+
// rejects a bare `import './globals.css'` unless the asset module is
|
|
5
|
+
// declared. Vite resolves it at build time; the declaration is type-only.
|
|
6
|
+
declare module '*.css'
|