@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,36 @@
|
|
|
1
|
+
// German catalog. Mirrors every key in `en.ts` — `defineLocale<typeof en>()`
|
|
2
|
+
// fails the build if a key is missing or misspelled. Translate the prose only;
|
|
3
|
+
// keep ICU placeholders (`{tenant}`, `{revision}`, `{count}`, `{time}`, `{error}`)
|
|
4
|
+
// and rich tags (`<code>`, `<a>`, `<strong>`) identical to `en.ts`.
|
|
5
|
+
|
|
6
|
+
import { defineLocale } from '@voltro/i18n'
|
|
7
|
+
import en from './en'
|
|
8
|
+
|
|
9
|
+
export default defineLocale<typeof en>()({
|
|
10
|
+
'meta.notes.title': 'Reaktive Notizen',
|
|
11
|
+
'meta.schemaUi.title': 'Schema-getriebene UI',
|
|
12
|
+
|
|
13
|
+
'nav.notes': 'Notizen',
|
|
14
|
+
'nav.schemaUi': 'Schema-UI',
|
|
15
|
+
'nav.tagline': 'Fullstack · reaktiv end-to-end',
|
|
16
|
+
'lang.label': 'Sprache',
|
|
17
|
+
|
|
18
|
+
'notes.heading': 'Reaktive Notizen',
|
|
19
|
+
'notes.status.tenant': 'Mandant <code>{tenant}</code> · Revision {revision}',
|
|
20
|
+
'notes.status.delta': ' · letztes Delta {time}',
|
|
21
|
+
'notes.status.pending': ' · {count} ausstehend',
|
|
22
|
+
'notes.status.loading': ' · lädt…',
|
|
23
|
+
'notes.error': 'Subscription-Fehler — läuft die API? {error}',
|
|
24
|
+
'notes.field.title': 'Titel',
|
|
25
|
+
'notes.field.body': 'Text (optional)',
|
|
26
|
+
'notes.add': 'Notiz hinzufügen',
|
|
27
|
+
'notes.adding': 'Wird hinzugefügt…',
|
|
28
|
+
'notes.empty': 'Noch keine Notizen — füge oben eine hinzu und sieh zu, wie sie sofort erscheint.',
|
|
29
|
+
|
|
30
|
+
'schemaUi.heading': 'Schema-getriebene UI',
|
|
31
|
+
'schemaUi.intro': 'Dieselben Notizen wie auf <a>der Startseite</a> — aber Formular + Tabelle sind hier <strong>an Deskriptoren gebunden</strong>, nicht handverdrahtet. Felder kommen aus dem Input-Schema von <code>notes.create</code>; Spalten aus dem Output-Schema von <code>notes.list</code>. Live + optimistisch, ohne Glue-Code.',
|
|
32
|
+
'schemaUi.createHeading': 'Erstellen (gebunden an <code>notes.create</code>)',
|
|
33
|
+
'schemaUi.listHeading': 'Liste (gebunden an <code>notes.list</code>)',
|
|
34
|
+
'schemaUi.submitLabel': 'Notiz hinzufügen',
|
|
35
|
+
'schemaUi.emptyText': 'Noch keine Notizen — füge oben eine hinzu.',
|
|
36
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Base catalog — the source of truth for this app's UI strings. Every key here
|
|
2
|
+
// MUST be mirrored in `de.ts` (and any other locale); `defineLocale<typeof en>()`
|
|
3
|
+
// enforces that parity at build time, so a missing translation fails typecheck
|
|
4
|
+
// rather than silently falling back.
|
|
5
|
+
//
|
|
6
|
+
// Messages use ICU syntax: `{name}` placeholders, `{n, plural, …}`, and rich
|
|
7
|
+
// tags like `<code>…</code>` (rendered by passing a matching function to
|
|
8
|
+
// `<T values={{ code: (c) => <code>{c}</code> }} />`).
|
|
9
|
+
|
|
10
|
+
import { defineCatalog } from '@voltro/i18n'
|
|
11
|
+
|
|
12
|
+
export default defineCatalog({
|
|
13
|
+
// Browser tab titles — locale-aware via each page's `meta({ locale })`.
|
|
14
|
+
'meta.notes.title': 'Reactive notes',
|
|
15
|
+
'meta.schemaUi.title': 'Schema-driven UI',
|
|
16
|
+
|
|
17
|
+
// ---- Layout shell (pages/layout.tsx) ----
|
|
18
|
+
'nav.notes': 'Notes',
|
|
19
|
+
'nav.schemaUi': 'Schema UI',
|
|
20
|
+
'nav.tagline': 'fullstack · reactive end-to-end',
|
|
21
|
+
'lang.label': 'Language',
|
|
22
|
+
|
|
23
|
+
// ---- Notes page (pages/index.tsx) ----
|
|
24
|
+
'notes.heading': 'Reactive notes',
|
|
25
|
+
'notes.status.tenant': 'Tenant <code>{tenant}</code> · revision {revision}',
|
|
26
|
+
'notes.status.delta': ' · last delta {time}',
|
|
27
|
+
'notes.status.pending': ' · {count} pending',
|
|
28
|
+
'notes.status.loading': ' · loading…',
|
|
29
|
+
'notes.error': 'subscription error — is the api running? {error}',
|
|
30
|
+
'notes.field.title': 'Title',
|
|
31
|
+
'notes.field.body': 'Body (optional)',
|
|
32
|
+
'notes.add': 'Add note',
|
|
33
|
+
'notes.adding': 'Adding…',
|
|
34
|
+
'notes.empty': 'No notes yet — add one above and watch it appear instantly.',
|
|
35
|
+
|
|
36
|
+
// ---- Schema-driven UI page (pages/schema-ui.tsx) ----
|
|
37
|
+
'schemaUi.heading': 'Schema-driven UI',
|
|
38
|
+
'schemaUi.intro': 'The same notes as <a>the home page</a> — but the form + table here are <strong>bound to descriptors</strong>, not hand-wired. Fields come from <code>notes.create</code>’s input schema; columns from <code>notes.list</code>’s output schema. Live + optimistic, zero glue.',
|
|
39
|
+
'schemaUi.createHeading': 'Create (bound to <code>notes.create</code>)',
|
|
40
|
+
'schemaUi.listHeading': 'List (bound to <code>notes.list</code>)',
|
|
41
|
+
'schemaUi.submitLabel': 'Add note',
|
|
42
|
+
'schemaUi.emptyText': 'No notes yet — add one above.',
|
|
43
|
+
} as const)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Catalog lookup OUTSIDE React — for a page's `meta({ locale })` locale-aware
|
|
2
|
+
// <title>. This app is cookie-i18n (no `/<locale>` URL prefix), so @voltro/web
|
|
3
|
+
// hands `meta` the active locale from the `voltro:lang` cookie. Inside React,
|
|
4
|
+
// use `<T>` / `useT()` via the framework's auto-wired <I18nProvider> instead.
|
|
5
|
+
|
|
6
|
+
import en from './en'
|
|
7
|
+
import de from './de'
|
|
8
|
+
|
|
9
|
+
// Both catalogs share `en`'s keys (defineLocale enforces the mirror), so a
|
|
10
|
+
// known-key lookup returns `string` — not `string | undefined` — which is what
|
|
11
|
+
// `PageMeta.title` requires under exactOptionalPropertyTypes.
|
|
12
|
+
export type Messages = Record<keyof typeof en, string>
|
|
13
|
+
|
|
14
|
+
export const getCatalog = (locale?: string): Messages =>
|
|
15
|
+
(locale === 'de' ? de : en) as Messages
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/` — the reactive notes page. It binds `useSubscription('app','notes.list')`
|
|
4
|
+
// + `useMutation('app','notes.create')` from @voltro/client. We mock that module
|
|
5
|
+
// so the render is deterministic and assert the observable output: loading /
|
|
6
|
+
// empty / list states, optimistic-row styling, subscription-error banner, and
|
|
7
|
+
// the submit path calling create.mutate with the trimmed title + body + tenant.
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
10
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
11
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
12
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
13
|
+
import enCatalog from '../locales/en'
|
|
14
|
+
|
|
15
|
+
interface SubState {
|
|
16
|
+
data: unknown
|
|
17
|
+
revision?: number
|
|
18
|
+
emittedAt?: number
|
|
19
|
+
error?: unknown
|
|
20
|
+
pendingPatches?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const useSubscription = vi.fn<() => SubState>()
|
|
24
|
+
const mutate = vi.fn<(input: unknown) => Promise<unknown>>()
|
|
25
|
+
const useMutation = vi.fn(() => ({ mutate, pending: false, error: undefined, data: undefined }))
|
|
26
|
+
|
|
27
|
+
vi.mock('@voltro/client', () => ({
|
|
28
|
+
useSubscription: (...a: unknown[]) => useSubscription(...(a as [])),
|
|
29
|
+
useMutation: (...a: unknown[]) => useMutation(...(a as [])),
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
const { default: IndexPage } = await import('./index')
|
|
33
|
+
|
|
34
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
35
|
+
|
|
36
|
+
let container: HTMLDivElement
|
|
37
|
+
let root: Root
|
|
38
|
+
|
|
39
|
+
// The page uses <T>/useTFn, so it needs an <I18nProvider>. Wrap with the real
|
|
40
|
+
// English catalog (the framework auto-wires this provider in the running app).
|
|
41
|
+
const render = (node: ReactNode): void => {
|
|
42
|
+
container = document.createElement('div')
|
|
43
|
+
document.body.appendChild(container)
|
|
44
|
+
act(() => {
|
|
45
|
+
root = createRoot(container)
|
|
46
|
+
root.render(
|
|
47
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const setInput = (el: HTMLInputElement | HTMLTextAreaElement, value: string): void => {
|
|
53
|
+
const proto =
|
|
54
|
+
el instanceof window.HTMLTextAreaElement
|
|
55
|
+
? window.HTMLTextAreaElement.prototype
|
|
56
|
+
: window.HTMLInputElement.prototype
|
|
57
|
+
const setter = Object.getOwnPropertyDescriptor(proto, 'value')!.set!
|
|
58
|
+
act(() => {
|
|
59
|
+
setter.call(el, value)
|
|
60
|
+
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
useSubscription.mockReset()
|
|
66
|
+
mutate.mockReset()
|
|
67
|
+
mutate.mockResolvedValue(undefined)
|
|
68
|
+
useMutation.mockClear()
|
|
69
|
+
useMutation.mockReturnValue({ mutate, pending: false, error: undefined, data: undefined })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
if (root) act(() => root.unmount())
|
|
74
|
+
container?.remove()
|
|
75
|
+
document.body.innerHTML = ''
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('index — subscription states', () => {
|
|
79
|
+
test('shows the loading hint before the first snapshot (data undefined)', () => {
|
|
80
|
+
useSubscription.mockReturnValue({ data: undefined, revision: -1 })
|
|
81
|
+
render(createElement(IndexPage))
|
|
82
|
+
expect(container.textContent).toContain('loading…')
|
|
83
|
+
expect(container.querySelectorAll('li')).toHaveLength(0)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('shows the empty state once a snapshot arrives with no rows', () => {
|
|
87
|
+
useSubscription.mockReturnValue({ data: [], revision: 0 })
|
|
88
|
+
render(createElement(IndexPage))
|
|
89
|
+
expect(container.textContent).toContain('No notes yet')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('renders each note with its title and body', () => {
|
|
93
|
+
useSubscription.mockReturnValue({
|
|
94
|
+
data: [
|
|
95
|
+
{ id: 'n_1', title: 'first', body: 'body one', done: false, tenantId: 'acme', createdAt: new Date(0) },
|
|
96
|
+
{ id: 'n_2', title: 'second', body: '', done: false, tenantId: 'acme', createdAt: new Date(0) },
|
|
97
|
+
],
|
|
98
|
+
revision: 3,
|
|
99
|
+
})
|
|
100
|
+
render(createElement(IndexPage))
|
|
101
|
+
const items = Array.from(container.querySelectorAll('li'))
|
|
102
|
+
expect(items).toHaveLength(2)
|
|
103
|
+
expect(container.textContent).toContain('first')
|
|
104
|
+
expect(container.textContent).toContain('body one')
|
|
105
|
+
expect(container.textContent).toContain('second')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('renders optimistic rows faintly + italicised', () => {
|
|
109
|
+
useSubscription.mockReturnValue({
|
|
110
|
+
data: [
|
|
111
|
+
{ id: 'n_opt', title: 'pending', body: '', done: false, tenantId: 'acme', createdAt: new Date(0), optimistic: true },
|
|
112
|
+
],
|
|
113
|
+
revision: 1,
|
|
114
|
+
pendingPatches: 1,
|
|
115
|
+
})
|
|
116
|
+
render(createElement(IndexPage))
|
|
117
|
+
const li = container.querySelector('li')!
|
|
118
|
+
expect(li.style.opacity).toBe('0.5')
|
|
119
|
+
expect(li.style.fontStyle).toBe('italic')
|
|
120
|
+
expect(container.textContent).toContain('1 pending')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('renders the subscription error banner when error is set', () => {
|
|
124
|
+
useSubscription.mockReturnValue({ data: undefined, error: 'boom', revision: -1 })
|
|
125
|
+
render(createElement(IndexPage))
|
|
126
|
+
expect(container.textContent).toContain('subscription error')
|
|
127
|
+
expect(container.textContent).toContain('boom')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('index — create form', () => {
|
|
132
|
+
test('the Add button is disabled until a non-empty title is typed', () => {
|
|
133
|
+
useSubscription.mockReturnValue({ data: [], revision: 0 })
|
|
134
|
+
render(createElement(IndexPage))
|
|
135
|
+
const button = container.querySelector('button[type="submit"]') as HTMLButtonElement
|
|
136
|
+
expect(button.disabled).toBe(true)
|
|
137
|
+
setInput(container.querySelector('input') as HTMLInputElement, 'a note')
|
|
138
|
+
expect(button.disabled).toBe(false)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('submitting calls create.mutate with the trimmed title + body + tenant, then clears', async () => {
|
|
142
|
+
useSubscription.mockReturnValue({ data: [], revision: 0 })
|
|
143
|
+
render(createElement(IndexPage))
|
|
144
|
+
const input = container.querySelector('input') as HTMLInputElement
|
|
145
|
+
const textarea = container.querySelector('textarea') as HTMLTextAreaElement
|
|
146
|
+
setInput(input, ' my title ')
|
|
147
|
+
setInput(textarea, ' my body ')
|
|
148
|
+
const form = container.querySelector('form') as HTMLFormElement
|
|
149
|
+
await act(async () => {
|
|
150
|
+
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
|
|
151
|
+
})
|
|
152
|
+
expect(mutate).toHaveBeenCalledWith({ tenantId: 'acme', title: 'my title', body: 'my body' })
|
|
153
|
+
expect(input.value).toBe('')
|
|
154
|
+
expect(textarea.value).toBe('')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('a whitespace-only title does not fire a mutation', async () => {
|
|
158
|
+
useSubscription.mockReturnValue({ data: [], revision: 0 })
|
|
159
|
+
render(createElement(IndexPage))
|
|
160
|
+
setInput(container.querySelector('input') as HTMLInputElement, ' ')
|
|
161
|
+
const form = container.querySelector('form') as HTMLFormElement
|
|
162
|
+
await act(async () => {
|
|
163
|
+
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
|
|
164
|
+
})
|
|
165
|
+
expect(mutate).not.toHaveBeenCalled()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// `/` — the notes page. Auto-mapped from src/pages/index.tsx by the
|
|
2
|
+
// framework's file-convention page discovery.
|
|
3
|
+
//
|
|
4
|
+
// THIS is the reactive end-to-end loop. `useSubscription('app',
|
|
5
|
+
// 'notes.list')` opens a live WebSocket subscription to the api's
|
|
6
|
+
// `notes.list` query; `useMutation('app', 'notes.create')` invokes the
|
|
7
|
+
// `notes.create` mutation. When the mutation commits, the server pushes a
|
|
8
|
+
// delta and this list re-renders — no refetch, no polling.
|
|
9
|
+
//
|
|
10
|
+
// Auto-optimistic: `notes.create` declares `target: { table: 'notes', op:
|
|
11
|
+
// 'insert' }` server-side, so the framework prepends an optimistic row to
|
|
12
|
+
// this subscription the instant you submit — and replaces it with the real
|
|
13
|
+
// row (or reverts it) when the rpc resolves. There is no `.withOptimistic`,
|
|
14
|
+
// no `useOptimistic`, no `startTransition` — just `create.mutate(input)`.
|
|
15
|
+
|
|
16
|
+
import { useMutation, useSubscription } from '@voltro/client'
|
|
17
|
+
import { useState, type FormEvent, type ReactNode } from 'react'
|
|
18
|
+
import type { PageMeta } from '@voltro/web'
|
|
19
|
+
import { T, useTFn } from '@voltro/i18n'
|
|
20
|
+
import { getCatalog } from '../locales'
|
|
21
|
+
|
|
22
|
+
// Locale-aware tab title: @voltro/web resolves meta({ locale }) from the
|
|
23
|
+
// active locale (the `voltro:lang` cookie in this cookie-i18n app).
|
|
24
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
25
|
+
title: getCatalog(locale)['meta.notes.title'],
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// The dev AuthMiddleware resolves the tenant from the `x-tenant` header,
|
|
29
|
+
// defaulting to 'acme'. The `notes` table carries the `tenant()` mixin, so
|
|
30
|
+
// reads are auto-scoped to this tenant and `notes.create` guards that the
|
|
31
|
+
// submitted tenantId matches the caller's (a typed TenantMismatch otherwise).
|
|
32
|
+
const TENANT = 'acme'
|
|
33
|
+
|
|
34
|
+
interface Note {
|
|
35
|
+
readonly id: string
|
|
36
|
+
readonly title: string
|
|
37
|
+
readonly body: string
|
|
38
|
+
readonly done: boolean
|
|
39
|
+
readonly tenantId: string
|
|
40
|
+
readonly createdAt: Date
|
|
41
|
+
readonly optimistic?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const IndexPage = (): ReactNode => {
|
|
45
|
+
const { data, revision, emittedAt, error, pendingPatches } =
|
|
46
|
+
useSubscription<ReadonlyArray<Note>>('app', 'notes.list')
|
|
47
|
+
|
|
48
|
+
const create = useMutation<{ tenantId: string; title: string; body: string }>('app', 'notes.create')
|
|
49
|
+
const t = useTFn()
|
|
50
|
+
|
|
51
|
+
const notes = data ?? []
|
|
52
|
+
const [title, setTitle] = useState('')
|
|
53
|
+
const [body, setBody] = useState('')
|
|
54
|
+
|
|
55
|
+
const handleSubmit = async (event: FormEvent) => {
|
|
56
|
+
event.preventDefault()
|
|
57
|
+
const trimmedTitle = title.trim()
|
|
58
|
+
if (trimmedTitle.length === 0) return
|
|
59
|
+
setTitle('')
|
|
60
|
+
setBody('')
|
|
61
|
+
try {
|
|
62
|
+
await create.mutate({ tenantId: TENANT, title: trimmedTitle, body: body.trim() })
|
|
63
|
+
} catch (mutationError) {
|
|
64
|
+
// notes.create can fail with a typed TenantMismatch; surface it however
|
|
65
|
+
// your UI prefers. The optimistic row auto-reverts on failure.
|
|
66
|
+
console.error('notes.create failed:', mutationError)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<section>
|
|
72
|
+
<h1 style={{ fontSize: 24, marginBottom: 4 }}><T id="notes.heading" /></h1>
|
|
73
|
+
<p style={{ color: '#666', marginTop: 0, fontSize: 13 }}>
|
|
74
|
+
<T
|
|
75
|
+
id="notes.status.tenant"
|
|
76
|
+
values={{ tenant: TENANT, revision, code: (c: ReactNode) => <code>{c}</code> }}
|
|
77
|
+
/>
|
|
78
|
+
{emittedAt ? (
|
|
79
|
+
<T id="notes.status.delta" values={{ time: new Date(emittedAt).toLocaleTimeString() }} />
|
|
80
|
+
) : null}
|
|
81
|
+
{pendingPatches > 0 && <T id="notes.status.pending" values={{ count: pendingPatches }} />}
|
|
82
|
+
{data === undefined && error === undefined && <T id="notes.status.loading" />}
|
|
83
|
+
</p>
|
|
84
|
+
|
|
85
|
+
{error !== undefined && (
|
|
86
|
+
<p style={{ color: '#c00', fontSize: 13 }}>
|
|
87
|
+
<T id="notes.error" values={{ error: String(error) }} />
|
|
88
|
+
</p>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
<form onSubmit={handleSubmit} style={{ display: 'grid', gap: 8, margin: '16px 0' }}>
|
|
92
|
+
<input
|
|
93
|
+
value={title}
|
|
94
|
+
onChange={(event) => setTitle(event.target.value)}
|
|
95
|
+
placeholder={t('notes.field.title')}
|
|
96
|
+
style={{ padding: '6px 10px', fontSize: 14 }}
|
|
97
|
+
/>
|
|
98
|
+
<textarea
|
|
99
|
+
value={body}
|
|
100
|
+
onChange={(event) => setBody(event.target.value)}
|
|
101
|
+
placeholder={t('notes.field.body')}
|
|
102
|
+
rows={2}
|
|
103
|
+
style={{ padding: '6px 10px', fontSize: 14, resize: 'vertical' }}
|
|
104
|
+
/>
|
|
105
|
+
<button type="submit" disabled={title.trim().length === 0 || create.pending} style={{ justifySelf: 'start' }}>
|
|
106
|
+
{create.pending ? t('notes.adding') : t('notes.add')}
|
|
107
|
+
</button>
|
|
108
|
+
</form>
|
|
109
|
+
|
|
110
|
+
{notes.length === 0 && data !== undefined && (
|
|
111
|
+
<p style={{ color: '#999' }}><T id="notes.empty" /></p>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 8 }}>
|
|
115
|
+
{notes.map((note) => (
|
|
116
|
+
<li
|
|
117
|
+
key={note.id}
|
|
118
|
+
style={{
|
|
119
|
+
border: '1px solid #eee',
|
|
120
|
+
borderRadius: 6,
|
|
121
|
+
padding: '10px 12px',
|
|
122
|
+
// Optimistic rows (not yet confirmed by the server) render faintly.
|
|
123
|
+
opacity: note.optimistic ? 0.5 : 1,
|
|
124
|
+
fontStyle: note.optimistic ? 'italic' : 'normal',
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<strong>{note.title}</strong>
|
|
128
|
+
{note.body && <p style={{ margin: '4px 0 0', color: '#444', fontSize: 14 }}>{note.body}</p>}
|
|
129
|
+
</li>
|
|
130
|
+
))}
|
|
131
|
+
</ul>
|
|
132
|
+
</section>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default IndexPage
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Root layout that wraps every page in this app. `<html>` / `<head>` /
|
|
2
|
+
// `<body>` are owned by the framework's index.html shell — don't render
|
|
3
|
+
// them here. Use a page's `meta` export for title + meta tags, and
|
|
4
|
+
// `theme: 'dark' | 'light' | 'system'` in app.config.ts for the dark class.
|
|
5
|
+
//
|
|
6
|
+
// The app is bilingual (see `locales` in app.config.ts): nav labels come from
|
|
7
|
+
// the catalog via `<T>`, and <LocaleSwitcher> writes the `voltro:lang` cookie
|
|
8
|
+
// + reloads so the framework re-renders in the chosen language.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import { T, useLocale, useT } from '@voltro/i18n'
|
|
12
|
+
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
13
|
+
|
|
14
|
+
interface LayoutProps {
|
|
15
|
+
readonly children: ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const LOCALES = [
|
|
19
|
+
{ code: 'en', label: 'English' },
|
|
20
|
+
{ code: 'de', label: 'Deutsch' },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export default function Layout({ children }: LayoutProps): ReactNode {
|
|
24
|
+
const locale = useLocale()
|
|
25
|
+
const langLabel = useT('lang.label')
|
|
26
|
+
return (
|
|
27
|
+
<div style={{ maxWidth: 640, margin: '0 auto', padding: '24px 16px', fontFamily: 'system-ui, sans-serif' }}>
|
|
28
|
+
<header style={{ display: 'flex', gap: 16, alignItems: 'baseline', marginBottom: 24 }}>
|
|
29
|
+
<strong style={{ fontSize: 16 }}>{{capProjectName}}</strong>
|
|
30
|
+
{/* Internal <a>s are SPA-intercepted by the Router (no full reload). */}
|
|
31
|
+
<nav style={{ display: 'flex', gap: 12, fontSize: 13 }}>
|
|
32
|
+
<a href="/"><T id="nav.notes" /></a>
|
|
33
|
+
<a href="/schema-ui"><T id="nav.schemaUi" /></a>
|
|
34
|
+
</nav>
|
|
35
|
+
<span style={{ color: '#999', fontSize: 12, marginLeft: 'auto' }}><T id="nav.tagline" /></span>
|
|
36
|
+
<LocaleSwitcher locales={LOCALES} current={locale} ariaLabel={langLabel} />
|
|
37
|
+
</header>
|
|
38
|
+
<main>{children}</main>
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/schema-ui` — the schema-driven-UI twin of the notes page. It binds
|
|
4
|
+
// <AutoForm> to the `notes.create` mutation and <DataTable> to the `notes.list`
|
|
5
|
+
// query, with title + body Fields. The real AutoForm/DataTable need a live
|
|
6
|
+
// framework runtime; here we mock them (from @voltro/web) as prop-recording
|
|
7
|
+
// sentinels and assert the page passes the right descriptor bindings + defaults.
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
10
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
11
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
12
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
13
|
+
import enCatalog from '../locales/en'
|
|
14
|
+
|
|
15
|
+
interface AutoFormProps {
|
|
16
|
+
api: string
|
|
17
|
+
mutation: string
|
|
18
|
+
defaults?: Record<string, unknown>
|
|
19
|
+
submitLabel?: string
|
|
20
|
+
children?: () => ReactNode
|
|
21
|
+
}
|
|
22
|
+
interface DataTableProps {
|
|
23
|
+
api: string
|
|
24
|
+
query: string
|
|
25
|
+
emptyText?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const autoFormProps: AutoFormProps[] = []
|
|
29
|
+
const dataTableProps: DataTableProps[] = []
|
|
30
|
+
|
|
31
|
+
vi.mock('@voltro/web', () => ({
|
|
32
|
+
AutoForm: (props: AutoFormProps) => {
|
|
33
|
+
autoFormProps.push(props)
|
|
34
|
+
return createElement('div', { 'data-testid': 'auto-form' }, props.children?.())
|
|
35
|
+
},
|
|
36
|
+
DataTable: (props: DataTableProps) => {
|
|
37
|
+
dataTableProps.push(props)
|
|
38
|
+
return createElement('div', { 'data-testid': 'data-table' })
|
|
39
|
+
},
|
|
40
|
+
Field: ({ name }: { name: string }) =>
|
|
41
|
+
createElement('span', { 'data-testid': `field-${name}` }, name),
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
const { default: SchemaUiPage } = await import('./schema-ui')
|
|
45
|
+
|
|
46
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
47
|
+
|
|
48
|
+
let container: HTMLDivElement
|
|
49
|
+
let root: Root
|
|
50
|
+
|
|
51
|
+
// The page uses <T>/useTFn, so it needs an <I18nProvider> (real English catalog).
|
|
52
|
+
const render = (node: ReactNode): void => {
|
|
53
|
+
container = document.createElement('div')
|
|
54
|
+
document.body.appendChild(container)
|
|
55
|
+
act(() => {
|
|
56
|
+
root = createRoot(container)
|
|
57
|
+
root.render(
|
|
58
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
autoFormProps.length = 0
|
|
65
|
+
dataTableProps.length = 0
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
if (root) act(() => root.unmount())
|
|
70
|
+
container?.remove()
|
|
71
|
+
document.body.innerHTML = ''
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('schema-ui — descriptor bindings', () => {
|
|
75
|
+
test('binds AutoForm to notes.create with the tenant default + title & body Fields', () => {
|
|
76
|
+
render(createElement(SchemaUiPage))
|
|
77
|
+
expect(autoFormProps).toHaveLength(1)
|
|
78
|
+
expect(autoFormProps[0]).toMatchObject({
|
|
79
|
+
api: 'app',
|
|
80
|
+
mutation: 'notes.create',
|
|
81
|
+
defaults: { tenantId: 'acme', done: false },
|
|
82
|
+
})
|
|
83
|
+
expect(container.querySelector('[data-testid="field-title"]')).toBeTruthy()
|
|
84
|
+
expect(container.querySelector('[data-testid="field-body"]')).toBeTruthy()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('binds DataTable to the notes.list query with empty text', () => {
|
|
88
|
+
render(createElement(SchemaUiPage))
|
|
89
|
+
expect(dataTableProps).toHaveLength(1)
|
|
90
|
+
expect(dataTableProps[0]).toMatchObject({ api: 'app', query: 'notes.list' })
|
|
91
|
+
expect(dataTableProps[0]!.emptyText).toContain('No notes yet')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('renders the Schema-driven UI heading', () => {
|
|
95
|
+
render(createElement(SchemaUiPage))
|
|
96
|
+
const headings = Array.from(container.querySelectorAll('h1,h2')).map((h) => h.textContent)
|
|
97
|
+
expect(headings.some((h) => h?.includes('Schema-driven UI'))).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// `/schema-ui` — the SAME notes as `/` (index.tsx), but rendered by the
|
|
2
|
+
// schema-driven UI components instead of hand-wired hooks. index.tsx wires
|
|
3
|
+
// useSubscription + useMutation + form state by hand (the reactive baseline);
|
|
4
|
+
// this page binds <DataTable> to `notes.list` and <AutoForm> to `notes.create`.
|
|
5
|
+
// The columns + fields come straight from the descriptors' effect/Schema, and
|
|
6
|
+
// validation, live updates, and auto-optimistic come for free — no glue code.
|
|
7
|
+
//
|
|
8
|
+
// Delete this page if you don't need it; it's here to show the binding.
|
|
9
|
+
|
|
10
|
+
import { AutoForm, DataTable, Field } from '@voltro/web'
|
|
11
|
+
import type { PageMeta } from '@voltro/web'
|
|
12
|
+
import type { ReactNode } from 'react'
|
|
13
|
+
import { T, useTFn } from '@voltro/i18n'
|
|
14
|
+
import { getCatalog } from '../locales'
|
|
15
|
+
|
|
16
|
+
const TENANT = 'acme'
|
|
17
|
+
|
|
18
|
+
// Locale-aware tab title (cookie-i18n — @voltro/web hands meta the active locale).
|
|
19
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
20
|
+
title: getCatalog(locale)['meta.schemaUi.title'],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const SchemaUiPage = (): ReactNode => {
|
|
24
|
+
const t = useTFn()
|
|
25
|
+
return (
|
|
26
|
+
<section style={{ display: 'grid', gap: '1.5rem' }}>
|
|
27
|
+
<header>
|
|
28
|
+
<h1 style={{ fontSize: 24, marginBottom: 4 }}><T id="schemaUi.heading" /></h1>
|
|
29
|
+
<p style={{ color: '#666', marginTop: 0, fontSize: 13 }}>
|
|
30
|
+
<T
|
|
31
|
+
id="schemaUi.intro"
|
|
32
|
+
values={{
|
|
33
|
+
a: (c: ReactNode) => <a href="/">{c}</a>,
|
|
34
|
+
strong: (c: ReactNode) => <strong>{c}</strong>,
|
|
35
|
+
code: (c: ReactNode) => <code>{c}</code>,
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
</p>
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<div>
|
|
42
|
+
<h2 style={{ fontSize: 16 }}>
|
|
43
|
+
<T id="schemaUi.createHeading" values={{ code: (c: ReactNode) => <code>{c}</code> }} />
|
|
44
|
+
</h2>
|
|
45
|
+
{/* Rung 3 — own the layout: show title + body; tenantId is a default. */}
|
|
46
|
+
<AutoForm
|
|
47
|
+
api="app"
|
|
48
|
+
mutation="notes.create"
|
|
49
|
+
defaults={{ tenantId: TENANT, done: false }}
|
|
50
|
+
submitLabel={t('schemaUi.submitLabel')}
|
|
51
|
+
>
|
|
52
|
+
{() => (
|
|
53
|
+
<>
|
|
54
|
+
<Field name="title" />
|
|
55
|
+
<Field name="body" />
|
|
56
|
+
</>
|
|
57
|
+
)}
|
|
58
|
+
</AutoForm>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div>
|
|
62
|
+
<h2 style={{ fontSize: 16 }}>
|
|
63
|
+
<T id="schemaUi.listHeading" values={{ code: (c: ReactNode) => <code>{c}</code> }} />
|
|
64
|
+
</h2>
|
|
65
|
+
{/* Columns inferred from the query's output schema; rows are a live
|
|
66
|
+
subscription that updates on every notes.create. Add a `rowActions`
|
|
67
|
+
prop once your api defines a per-row mutation (e.g. notes.delete). */}
|
|
68
|
+
<DataTable api="app" query="notes.list" emptyText={t('schemaUi.emptyText')} />
|
|
69
|
+
</div>
|
|
70
|
+
</section>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default SchemaUiPage
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-app",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "The reactive end-to-end loop — a web frontend wired to an api. A live notes list (useSubscription) + a create form (useMutation) with zero-boilerplate auto-optimistic updates. Pairs with api-backend: scaffold both with `create-project --api=api-backend --web=frontend-app`.",
|
|
5
|
+
"tags": ["web", "frontend", "fullstack", "reactive", "subscription", "mutation", "optimistic", "api-backend"]
|
|
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,18 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro web app scaffold (template: **frontend-blank**).
|
|
4
|
+
|
|
5
|
+
## Boot
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
10
|
+
# → http://localhost:5173
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Routing
|
|
14
|
+
|
|
15
|
+
File-based. `src/pages/index.tsx` mounts at `/`. Add more `*.tsx`
|
|
16
|
+
files in `src/pages/` to create routes. See AGENTS.md for the
|
|
17
|
+
naming rules (`[id]`, `[...slug]`, `(group)`, `error.tsx`,
|
|
18
|
+
`not-found.tsx`, etc.).
|