@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,47 @@
|
|
|
1
|
+
// currencyConvert — convert an amount between currencies using a live rate.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: outbound HTTP with the framework-provided `HttpClient`,
|
|
4
|
+
// reading the JSON response (`yield* response.json`), transforming it, and a
|
|
5
|
+
// typed error status when the upstream can't answer (422). Uses a public,
|
|
6
|
+
// no-key FX endpoint so it runs out of the box.
|
|
7
|
+
//
|
|
8
|
+
// curl -X POST http://localhost:8910/ -d '{"from":"USD","to":"EUR","amount":42}'
|
|
9
|
+
|
|
10
|
+
import { Effect, Schema } from 'effect'
|
|
11
|
+
import { HttpClient, HttpClientRequest } from '@effect/platform'
|
|
12
|
+
import { defineServerless, ServerlessHttpError } from '@voltro/serverless'
|
|
13
|
+
|
|
14
|
+
const CODE = Schema.String.pipe(Schema.pattern(/^[A-Za-z]{3}$/))
|
|
15
|
+
|
|
16
|
+
export default defineServerless({
|
|
17
|
+
name: 'currency-convert',
|
|
18
|
+
method: 'POST',
|
|
19
|
+
input: Schema.Struct({
|
|
20
|
+
from: CODE,
|
|
21
|
+
to: CODE,
|
|
22
|
+
amount: Schema.Number.pipe(Schema.greaterThanOrEqualTo(0)),
|
|
23
|
+
}),
|
|
24
|
+
output: Schema.Struct({
|
|
25
|
+
from: Schema.String,
|
|
26
|
+
to: Schema.String,
|
|
27
|
+
amount: Schema.Number,
|
|
28
|
+
rate: Schema.Number,
|
|
29
|
+
result: Schema.Number,
|
|
30
|
+
}),
|
|
31
|
+
runtime: { memoryMb: 128, timeoutSeconds: 10 },
|
|
32
|
+
handler: ({ from, to, amount }, _ctx) =>
|
|
33
|
+
Effect.gen(function* () {
|
|
34
|
+
const base = from.toUpperCase()
|
|
35
|
+
const quote = to.toUpperCase()
|
|
36
|
+
const http = yield* HttpClient.HttpClient
|
|
37
|
+
const response = yield* http.execute(HttpClientRequest.get(`https://open.er-api.com/v6/latest/${base}`))
|
|
38
|
+
const body = (yield* response.json) as { rates?: Record<string, number> }
|
|
39
|
+
const rate = body.rates?.[quote]
|
|
40
|
+
if (typeof rate !== 'number') {
|
|
41
|
+
return yield* Effect.fail(
|
|
42
|
+
new ServerlessHttpError({ status: 422, message: 'unknown-currency', detail: `no rate ${base}->${quote}` }),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return { from: base, to: quote, amount, rate, result: Math.round(amount * rate * 100) / 100 }
|
|
46
|
+
}),
|
|
47
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// geoGreeting — a localized greeting based on the caller's country.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: reading the raw request via `ctx.request.headers` (the geo
|
|
4
|
+
// header the edge host injects), plus an optional GET query param. The whole
|
|
5
|
+
// point of running at the edge is being close to the user — this reacts to
|
|
6
|
+
// where they are.
|
|
7
|
+
//
|
|
8
|
+
// curl 'http://localhost:8910/?name=Ada' -H 'cf-ipcountry: DE'
|
|
9
|
+
|
|
10
|
+
import { Effect, Schema } from 'effect'
|
|
11
|
+
import { defineServerless } from '@voltro/serverless'
|
|
12
|
+
|
|
13
|
+
const HELLO: Record<string, string> = {
|
|
14
|
+
DE: 'Hallo',
|
|
15
|
+
FR: 'Bonjour',
|
|
16
|
+
ES: 'Hola',
|
|
17
|
+
IT: 'Ciao',
|
|
18
|
+
JP: 'こんにちは',
|
|
19
|
+
US: 'Hi',
|
|
20
|
+
GB: 'Hello',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default defineServerless({
|
|
24
|
+
name: 'geo-greeting',
|
|
25
|
+
method: 'GET',
|
|
26
|
+
input: Schema.Struct({ name: Schema.optional(Schema.String) }),
|
|
27
|
+
output: Schema.Struct({
|
|
28
|
+
greeting: Schema.String,
|
|
29
|
+
country: Schema.String,
|
|
30
|
+
}),
|
|
31
|
+
handler: ({ name }, ctx) =>
|
|
32
|
+
Effect.sync(() => {
|
|
33
|
+
// Cloudflare sets `cf-ipcountry`; other proxies use varied headers. We
|
|
34
|
+
// read whatever the host injected — the function code stays the same.
|
|
35
|
+
const country = (
|
|
36
|
+
ctx.request.headers.get('cf-ipcountry') ??
|
|
37
|
+
ctx.request.headers.get('x-vercel-ip-country') ??
|
|
38
|
+
ctx.request.headers.get('x-forwarded-country') ??
|
|
39
|
+
'XX'
|
|
40
|
+
).toUpperCase()
|
|
41
|
+
const hello = HELLO[country] ?? 'Hello'
|
|
42
|
+
return { greeting: `${hello}, ${name ?? 'there'}!`, country }
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// health — the simplest function: a GET diagnostic.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: GET (input from the query string), reading `ctx.env`, and a
|
|
4
|
+
// pure synchronous handler (`Effect.sync`). No I/O, no secrets.
|
|
5
|
+
//
|
|
6
|
+
// voltro serverless dev functions/health.serverless.ts
|
|
7
|
+
// curl http://localhost:8910/
|
|
8
|
+
|
|
9
|
+
import { Effect, Schema } from 'effect'
|
|
10
|
+
import { defineServerless } from '@voltro/serverless'
|
|
11
|
+
|
|
12
|
+
export default defineServerless({
|
|
13
|
+
name: 'health',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
input: Schema.Struct({}),
|
|
16
|
+
output: Schema.Struct({
|
|
17
|
+
status: Schema.Literal('ok'),
|
|
18
|
+
region: Schema.String,
|
|
19
|
+
now: Schema.String,
|
|
20
|
+
}),
|
|
21
|
+
handler: (_input, ctx) =>
|
|
22
|
+
Effect.sync(() => ({
|
|
23
|
+
status: 'ok' as const,
|
|
24
|
+
// The provider injects region into the env (Scaleway SCW_REGION, your own
|
|
25
|
+
// VOLTRO_REGION on a node host). Cloudflare is global → 'edge'.
|
|
26
|
+
region: ctx.env.VOLTRO_REGION ?? ctx.env.SCW_REGION ?? ctx.env.AWS_REGION ?? 'edge',
|
|
27
|
+
now: new Date().toISOString(),
|
|
28
|
+
})),
|
|
29
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// resolveLink — resolve a short code to its destination URL (the read side of
|
|
2
|
+
// a link shortener; `shareLink` is the write side).
|
|
3
|
+
//
|
|
4
|
+
// Demonstrates: GET with a query param, a lookup, and a 404 via
|
|
5
|
+
// `ServerlessHttpError` when the code is unknown. Swap the in-memory map for a
|
|
6
|
+
// KV / D1 / database read in a real shortener — the shape stays the same.
|
|
7
|
+
//
|
|
8
|
+
// curl 'http://localhost:8910/?code=docs'
|
|
9
|
+
|
|
10
|
+
import { Effect, Schema } from 'effect'
|
|
11
|
+
import { defineServerless, ServerlessHttpError } from '@voltro/serverless'
|
|
12
|
+
|
|
13
|
+
const LINKS: Record<string, string> = {
|
|
14
|
+
docs: 'https://voltro.dev/docs',
|
|
15
|
+
gh: 'https://github.com/SinPP',
|
|
16
|
+
home: 'https://voltro.dev',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default defineServerless({
|
|
20
|
+
name: 'resolve-link',
|
|
21
|
+
method: 'GET',
|
|
22
|
+
input: Schema.Struct({ code: Schema.String.pipe(Schema.minLength(1)) }),
|
|
23
|
+
output: Schema.Struct({ code: Schema.String, url: Schema.String }),
|
|
24
|
+
handler: ({ code }, _ctx) =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const url = LINKS[code.toLowerCase()]
|
|
27
|
+
if (!url) {
|
|
28
|
+
return yield* Effect.fail(new ServerlessHttpError({ status: 404, message: 'unknown-code', detail: code }))
|
|
29
|
+
}
|
|
30
|
+
return { code, url }
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// shareLink — turn a resource id into a short-lived share URL.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: POST with a JSON body, schema-typed input/output, `ctx.env`
|
|
4
|
+
// config, and runtime hints (memory / timeout / region). Pure compute — no
|
|
5
|
+
// I/O — so it is trivially edge-safe and scales to zero.
|
|
6
|
+
//
|
|
7
|
+
// curl -X POST http://localhost:8910/ -d '{"resourceId":"doc-1","ttlMinutes":30}'
|
|
8
|
+
|
|
9
|
+
import { Effect, Schema } from 'effect'
|
|
10
|
+
import { defineServerless } from '@voltro/serverless'
|
|
11
|
+
|
|
12
|
+
export default defineServerless({
|
|
13
|
+
name: 'share-link',
|
|
14
|
+
method: 'POST',
|
|
15
|
+
input: Schema.Struct({
|
|
16
|
+
resourceId: Schema.String.pipe(Schema.minLength(1)),
|
|
17
|
+
ttlMinutes: Schema.Number.pipe(Schema.greaterThan(0)),
|
|
18
|
+
}),
|
|
19
|
+
output: Schema.Struct({
|
|
20
|
+
url: Schema.String,
|
|
21
|
+
expiresAt: Schema.Number,
|
|
22
|
+
}),
|
|
23
|
+
runtime: { memoryMb: 128, timeoutSeconds: 10, region: 'fr-par' },
|
|
24
|
+
handler: ({ resourceId, ttlMinutes }, ctx) =>
|
|
25
|
+
Effect.sync(() => {
|
|
26
|
+
const base = ctx.env.SHARE_BASE_URL ?? 'https://share.example'
|
|
27
|
+
const expiresAt = Date.now() + ttlMinutes * 60_000
|
|
28
|
+
return { url: `${base}/s/${encodeURIComponent(resourceId)}?exp=${expiresAt}`, expiresAt }
|
|
29
|
+
}),
|
|
30
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// slackNotify — post a message to a Slack incoming webhook, fire-and-forget.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: `ctx.waitUntil(...)` — schedule work that OUTLIVES the
|
|
4
|
+
// response. The caller gets `{ queued: true }` immediately; the actual
|
|
5
|
+
// delivery runs in the background (on Cloudflare via the real
|
|
6
|
+
// `ExecutionContext.waitUntil`, best-effort elsewhere). We use the global
|
|
7
|
+
// `fetch` here because a detached background POST needs no HttpClient features.
|
|
8
|
+
//
|
|
9
|
+
// SLACK_WEBHOOK_URL=https://hooks.slack.com/... voltro serverless dev functions/slackNotify.serverless.ts
|
|
10
|
+
// curl -X POST http://localhost:8910/ -d '{"text":"deploy finished"}'
|
|
11
|
+
|
|
12
|
+
import { Effect, Schema } from 'effect'
|
|
13
|
+
import { defineServerless, ServerlessHttpError } from '@voltro/serverless'
|
|
14
|
+
|
|
15
|
+
export default defineServerless({
|
|
16
|
+
name: 'slack-notify',
|
|
17
|
+
method: 'POST',
|
|
18
|
+
input: Schema.Struct({ text: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(3000)) }),
|
|
19
|
+
output: Schema.Struct({ queued: Schema.Boolean }),
|
|
20
|
+
handler: ({ text }, ctx) =>
|
|
21
|
+
Effect.gen(function* () {
|
|
22
|
+
const webhook = ctx.env.SLACK_WEBHOOK_URL
|
|
23
|
+
if (!webhook) {
|
|
24
|
+
return yield* Effect.fail(
|
|
25
|
+
new ServerlessHttpError({ status: 503, message: 'slack-not-configured', detail: 'Set SLACK_WEBHOOK_URL.' }),
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
// Schedule the delivery in the background and respond NOW.
|
|
29
|
+
ctx.waitUntil(
|
|
30
|
+
fetch(webhook, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'content-type': 'application/json' },
|
|
33
|
+
body: JSON.stringify({ text }),
|
|
34
|
+
}).catch(() => {
|
|
35
|
+
/* best-effort — a failed notification must not fail the request */
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
38
|
+
return { queued: true }
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// verifySignature — verify an HMAC-SHA256 signature over a payload.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: the Web Crypto API (`crypto.subtle`) — available on every
|
|
4
|
+
// serverless host (Workers, Scaleway node, your own node) without any import —
|
|
5
|
+
// wrapped in `Effect.promise`, plus controlling the HTTP status with
|
|
6
|
+
// `ServerlessHttpError` (401 when the signature does not match).
|
|
7
|
+
//
|
|
8
|
+
// SIGNING_SECRET=s3cret voltro serverless dev functions/verifySignature.serverless.ts
|
|
9
|
+
|
|
10
|
+
import { Effect, Schema } from 'effect'
|
|
11
|
+
import { defineServerless, ServerlessHttpError } from '@voltro/serverless'
|
|
12
|
+
|
|
13
|
+
const toHex = (buf: ArrayBuffer): string =>
|
|
14
|
+
[...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('')
|
|
15
|
+
|
|
16
|
+
const hmacHex = async (secret: string, payload: string): Promise<string> => {
|
|
17
|
+
const enc = new TextEncoder()
|
|
18
|
+
const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
|
|
19
|
+
return toHex(await crypto.subtle.sign('HMAC', key, enc.encode(payload)))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Constant-time-ish compare — avoid leaking match position via early return.
|
|
23
|
+
const safeEqual = (a: string, b: string): boolean => {
|
|
24
|
+
if (a.length !== b.length) return false
|
|
25
|
+
let diff = 0
|
|
26
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
|
|
27
|
+
return diff === 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default defineServerless({
|
|
31
|
+
name: 'verify-signature',
|
|
32
|
+
method: 'POST',
|
|
33
|
+
input: Schema.Struct({
|
|
34
|
+
payload: Schema.String,
|
|
35
|
+
signature: Schema.String.pipe(Schema.pattern(/^[0-9a-f]+$/i)),
|
|
36
|
+
}),
|
|
37
|
+
output: Schema.Struct({ valid: Schema.Literal(true) }),
|
|
38
|
+
handler: ({ payload, signature }, ctx) =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const secret = ctx.env.SIGNING_SECRET
|
|
41
|
+
if (!secret) {
|
|
42
|
+
return yield* Effect.fail(
|
|
43
|
+
new ServerlessHttpError({ status: 503, message: 'signing-not-configured', detail: 'Set SIGNING_SECRET.' }),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
const expected = yield* Effect.promise(() => hmacHex(secret, payload))
|
|
47
|
+
if (!safeEqual(expected, signature.toLowerCase())) {
|
|
48
|
+
return yield* Effect.fail(new ServerlessHttpError({ status: 401, message: 'invalid-signature' }))
|
|
49
|
+
}
|
|
50
|
+
return { valid: true as const }
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"list": "voltro serverless list .",
|
|
8
|
+
"build": "voltro serverless build .",
|
|
9
|
+
"serve": "voltro serverless serve .",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@effect/platform": "^0.96.1",
|
|
14
|
+
"@voltro/cli": "0.1.0",
|
|
15
|
+
"@voltro/serverless": "0.1.0",
|
|
16
|
+
"effect": "^3.21.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.7.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "edge-functions",
|
|
3
|
+
"kind": "serverless",
|
|
4
|
+
"summary": "A standalone library of `*.serverless.ts` functions covering the range of what edge functions do: pure compute, request-header/geo, outbound HTTP (fire-and-forget + fetch-and-transform), Web Crypto HMAC verify, an LLM call, and status-controlled errors. Run with `voltro serverless dev`, ship to node / Cloudflare / Scaleway. No long-running server, no port.",
|
|
5
|
+
"tags": ["serverless", "edge", "functions", "cloudflare", "scaleway", "node", "httpclient", "crypto", "ai"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frontend-admin
|
|
2
|
+
|
|
3
|
+
An **auto-admin back-office** that discovers your api's entities at runtime and
|
|
4
|
+
renders a live, reactive, permission-gated UI per entity — assembled from
|
|
5
|
+
already-shipped framework primitives, as **editable template code** (not a
|
|
6
|
+
sealed runtime feature).
|
|
7
|
+
|
|
8
|
+
## What it does
|
|
9
|
+
|
|
10
|
+
- **Discovers entities** from the api's capability map
|
|
11
|
+
(`useCapabilityManifest` + `deriveEntityAdmins`) — point it at any Voltro api
|
|
12
|
+
and the sidebar + pages adapt. No per-entity code.
|
|
13
|
+
- **Per entity**: a live `<DataTable>` (the list query) + an `<AutoForm>` (the
|
|
14
|
+
create mutation), fields/columns straight from the descriptors' schemas.
|
|
15
|
+
- **Permission-gated**: write actions are hidden via `useCan` on conventional
|
|
16
|
+
`<table>:create` / `<table>:delete` scopes (UX gating — the api's
|
|
17
|
+
`permission()` guards remain the real boundary).
|
|
18
|
+
- **Provenance**: a per-row "Why?" drawer (`useProvenance`, innovation/08).
|
|
19
|
+
- **Undo/redo**: the topbar bar wraps the `__voltro.undo.*` built-ins
|
|
20
|
+
(`useUndoLog`) — revert the last change across the whole admin.
|
|
21
|
+
|
|
22
|
+
## Use it
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
voltro create-project myApp --api=api-backend --web=frontend-admin
|
|
26
|
+
voltro dev # the admin discovers api-backend's entities
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Sign in at `/login` (demo cookie gate) → `/admin`.
|
|
30
|
+
|
|
31
|
+
## Make it yours
|
|
32
|
+
|
|
33
|
+
Everything under `src/pages/admin/` is yours to edit. Common first changes:
|
|
34
|
+
|
|
35
|
+
- **Real auth**: swap the cookie gate in `src/lib/auth.ts` + the demo scopes in
|
|
36
|
+
`src/lib/admin.ts` (feed your session's real scopes to `<PermissionProvider>`).
|
|
37
|
+
- **Undo off**: if you run with `VOLTRO_UNDO=off`, delete the `UndoBar` in
|
|
38
|
+
`src/pages/admin/layout.tsx`.
|
|
39
|
+
- **Custom per-entity views**: replace the generic `[entity].tsx` binding with a
|
|
40
|
+
hand-built page for an entity that needs more than CRUD.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Web app for the {{projectName}} project — an AUTO-ADMIN back-office.
|
|
2
|
+
//
|
|
3
|
+
// It points at an api (below) and, at runtime, reads that api's capability map
|
|
4
|
+
// (`/_voltro/inspect/manifest`) to discover every entity + the procedures that
|
|
5
|
+
// read/write it — then renders a live, reactive table + form per entity. Point
|
|
6
|
+
// it at ANY Voltro api and it adapts; there's no per-entity code to write (but
|
|
7
|
+
// the page code IS yours to edit — see src/pages/admin/).
|
|
8
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
9
|
+
|
|
10
|
+
export const env = defineEnv({
|
|
11
|
+
VOLTRO_PUBLIC_APP_NAME: envVar.string({ access: 'public', default: '{{capProjectName}} Admin' }),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
type: 'web' as const,
|
|
16
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
17
|
+
port: {{port}},
|
|
18
|
+
// Follow the visitor's OS theme, set before first paint (no flash).
|
|
19
|
+
theme: 'system' as const,
|
|
20
|
+
env,
|
|
21
|
+
// Bilingual by default. The framework auto-wires a cookie-driven
|
|
22
|
+
// <I18nProvider> from these codes and loads the matching
|
|
23
|
+
// `src/locales/<code>.ts` catalog — so `<T id="…" />` / `useT()` /
|
|
24
|
+
// `useLocale()` work app-wide with no manual setup. The <LocaleSwitcher>
|
|
25
|
+
// in the layout writes the `voltro:lang` cookie + reloads. Grow the app to
|
|
26
|
+
// more languages by adding a code here + a `src/locales/<code>.ts`.
|
|
27
|
+
locales: ['en', 'de'] as const,
|
|
28
|
+
defaultLocale: 'en' as const,
|
|
29
|
+
// The api this admin introspects + binds to. The NAME ('app') is the lookup
|
|
30
|
+
// key the hooks + components use (`<DataTable api="app" .../>`). The `package`
|
|
31
|
+
// resolves the sibling api in this project (scaffold it with
|
|
32
|
+
// `--api=api-backend`); codegen pulls its typed rpc surface — INCLUDING the
|
|
33
|
+
// __voltro.undo.* built-ins (when undo capture is on) the admin's undo bar uses.
|
|
34
|
+
apis: {
|
|
35
|
+
app: { package: '@{{projectName}}/api' },
|
|
36
|
+
},
|
|
37
|
+
}
|
|
@@ -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,8 @@
|
|
|
1
|
+
// The brand name shown in the marketing header + admin sidebar.
|
|
2
|
+
//
|
|
3
|
+
// A plain constant — not `publicEnv` — so the template typechecks BEFORE
|
|
4
|
+
// `voltro dev` generates the env types. `VOLTRO_PUBLIC_APP_NAME` is still
|
|
5
|
+
// declared in app.config.ts (validated + in the env manifest); to read it
|
|
6
|
+
// typed instead, after one `voltro dev` swap this for
|
|
7
|
+
// `publicEnv.VOLTRO_PUBLIC_APP_NAME` from '@voltro/env/public'.
|
|
8
|
+
export const APP_NAME = '{{capProjectName}} Admin'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* Plain CSS — no Tailwind, so there's no @source scanner to configure.
|
|
2
|
+
Swap in @voltro/ui-shadcn or Tailwind later if you want; see AGENTS.md. */
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #ffffff;
|
|
5
|
+
--fg: #0a0a0a;
|
|
6
|
+
--muted: #6b7280;
|
|
7
|
+
--accent: #4f46e5;
|
|
8
|
+
--danger: #dc2626;
|
|
9
|
+
--border: #e5e7eb;
|
|
10
|
+
--panel: #f9fafb;
|
|
11
|
+
}
|
|
12
|
+
:root.dark {
|
|
13
|
+
--bg: #0a0a0a;
|
|
14
|
+
--fg: #fafafa;
|
|
15
|
+
--muted: #9ca3af;
|
|
16
|
+
--accent: #818cf8;
|
|
17
|
+
--danger: #f87171;
|
|
18
|
+
--border: #1f2937;
|
|
19
|
+
--panel: #111827;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* { box-sizing: border-box; }
|
|
23
|
+
body {
|
|
24
|
+
margin: 0;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--fg);
|
|
27
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
28
|
+
line-height: 1.55;
|
|
29
|
+
}
|
|
30
|
+
a { color: var(--accent); }
|
|
31
|
+
|
|
32
|
+
/* Marketing */
|
|
33
|
+
.marketing { max-width: 44rem; margin: 0 auto; padding: 2.5rem 1.25rem; }
|
|
34
|
+
.marketing header { display: flex; gap: 1rem; align-items: center; margin-bottom: 2rem; }
|
|
35
|
+
.marketing header .brand { font-weight: 700; }
|
|
36
|
+
.marketing header .spacer { flex: 1; }
|
|
37
|
+
.hero h1 { font-size: 2rem; margin: 0 0 0.5rem; }
|
|
38
|
+
.btn {
|
|
39
|
+
display: inline-block; background: var(--accent); color: #fff; padding: 0.55rem 1rem;
|
|
40
|
+
border-radius: 8px; border: 0; font: inherit; font-weight: 600; cursor: pointer; text-decoration: none;
|
|
41
|
+
}
|
|
42
|
+
.btn.ghost { background: transparent; color: var(--accent); border: 1px solid var(--border); }
|
|
43
|
+
.btn.danger { background: transparent; color: var(--danger); border: 1px solid var(--border); }
|
|
44
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
45
|
+
|
|
46
|
+
/* Admin shell */
|
|
47
|
+
.admin { display: grid; grid-template-columns: 15rem 1fr; min-height: 100vh; }
|
|
48
|
+
.admin aside { border-right: 1px solid var(--border); background: var(--panel); padding: 1.25rem 1rem; }
|
|
49
|
+
.admin aside .brand { font-weight: 700; margin-bottom: 1rem; }
|
|
50
|
+
.admin nav { display: flex; flex-direction: column; gap: 0.15rem; }
|
|
51
|
+
.admin nav a { color: var(--fg); text-decoration: none; padding: 0.35rem 0.6rem; border-radius: 6px; font-size: 0.92rem; }
|
|
52
|
+
.admin nav a:hover { background: color-mix(in oklab, var(--fg) 7%, transparent); }
|
|
53
|
+
.admin nav a[aria-current='page'] { background: color-mix(in oklab, var(--accent) 16%, transparent); color: var(--accent); font-weight: 600; }
|
|
54
|
+
.admin main { padding: 1.75rem; min-width: 0; }
|
|
55
|
+
.admin .topbar { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1.5rem; }
|
|
56
|
+
.admin .topbar .spacer { flex: 1; }
|
|
57
|
+
|
|
58
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 1rem; }
|
|
59
|
+
.card { border: 1px solid var(--border); border-radius: 10px; padding: 1rem; background: var(--panel); text-decoration: none; color: inherit; display: block; }
|
|
60
|
+
.card:hover { border-color: var(--accent); }
|
|
61
|
+
.card .label { color: var(--muted); font-size: 0.8rem; }
|
|
62
|
+
.card .value { font-size: 1.4rem; font-weight: 700; }
|
|
63
|
+
.card .meta { color: var(--muted); font-size: 0.75rem; margin-top: 0.25rem; }
|
|
64
|
+
|
|
65
|
+
.muted { color: var(--muted); }
|
|
66
|
+
.skeleton { border: 1px solid var(--border); border-radius: 10px; height: 4rem; background: linear-gradient(90deg, var(--panel), color-mix(in oklab, var(--panel) 60%, var(--bg)), var(--panel)); }
|
|
67
|
+
.banner { border: 1px solid var(--border); border-radius: 8px; padding: 0.75rem 1rem; background: var(--panel); color: var(--muted); font-size: 0.9rem; }
|
|
68
|
+
code { background: color-mix(in oklab, var(--fg) 8%, transparent); padding: 0.1em 0.35em; border-radius: 4px; }
|
|
69
|
+
|
|
70
|
+
.section { margin-bottom: 2rem; }
|
|
71
|
+
.section h2 { font-size: 1rem; margin: 0 0 0.5rem; }
|
|
72
|
+
.row-actions { display: inline-flex; gap: 0.4rem; }
|
|
73
|
+
.drawer { border: 1px solid var(--border); border-radius: 10px; padding: 1rem; background: var(--panel); margin-top: 1rem; font-size: 0.85rem; }
|
|
74
|
+
.drawer h3 { margin: 0 0 0.5rem; font-size: 0.9rem; }
|
|
75
|
+
.drawer dl { display: grid; grid-template-columns: auto 1fr; gap: 0.2rem 0.75rem; margin: 0; }
|
|
76
|
+
.drawer dt { color: var(--muted); }
|
|
@@ -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'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The scopes the admin gates its write actions on (via useCan). The admin
|
|
2
|
+
// derives a CONVENTIONAL scope per entity action — `<table>:create`,
|
|
3
|
+
// `<table>:write`, `<table>:delete` (see deriveEntityAdmins) — and hides any
|
|
4
|
+
// action the current subject's scopes don't satisfy.
|
|
5
|
+
//
|
|
6
|
+
// DEMO: we feed `admin:full` (the blanket-bypass scope) so the demo shows every
|
|
7
|
+
// action. In a real app, REPLACE this with the current subject's scopes from
|
|
8
|
+
// your session — e.g. a reactive query:
|
|
9
|
+
//
|
|
10
|
+
// const { data } = useSubscription<{ scopes: string[] }>('app', 'auth.session')
|
|
11
|
+
// <PermissionProvider scopes={data?.scopes ?? []}>…</PermissionProvider>
|
|
12
|
+
//
|
|
13
|
+
// useCan reads these reactively, so the admin's affordances update the instant
|
|
14
|
+
// the subject's roles change. (This is UX gating, not enforcement — the api's
|
|
15
|
+
// own permission() guards remain the real authorization boundary.)
|
|
16
|
+
export const DEMO_SCOPES: ReadonlyArray<string> = ['admin:full']
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The demo auth gate. This is intentionally a COOKIE-ONLY stand-in so the
|
|
2
|
+
// template boots with zero backend wiring — `login` writes the cookie, the
|
|
3
|
+
// admin layout loader reads it. Swap `isSignedIn` for a real check (verify a
|
|
4
|
+
// signed session, call your api) when you wire auth for real. See AGENTS.md › Auth.
|
|
5
|
+
|
|
6
|
+
export const SESSION_COOKIE = 'demo_session'
|
|
7
|
+
|
|
8
|
+
// Browser-safe cookie parse — NO `node:*` import, so it's safe in the page
|
|
9
|
+
// module graph. Runs in BOTH places the gate needs it:
|
|
10
|
+
// - SSR: the loader's `headers['cookie']` (raw Cookie request header)
|
|
11
|
+
// - client nav: `document.cookie` (the loader's `headers` is undefined there)
|
|
12
|
+
export const readCookie = (
|
|
13
|
+
header: string | null | undefined,
|
|
14
|
+
name: string,
|
|
15
|
+
): string | undefined => {
|
|
16
|
+
if (!header) return undefined
|
|
17
|
+
const match = new RegExp(`(?:^|;\\s*)${name}=([^;]*)`).exec(header)
|
|
18
|
+
return match ? decodeURIComponent(match[1]!) : undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const isSignedIn = (headers: Record<string, string | undefined> | undefined): boolean => {
|
|
22
|
+
const cookieHeader = headers?.['cookie'] ?? (typeof document === 'undefined' ? '' : document.cookie)
|
|
23
|
+
return readCookie(cookieHeader, SESSION_COOKIE) !== undefined
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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 (`{table}`, `{id}`, `{entity}`, `{via}`, `{count}`) and
|
|
4
|
+
// rich tags (`<code>`, `<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.landing.title': 'Admin',
|
|
11
|
+
'meta.landing.description': 'Ein Auto-Admin-Backoffice, das die Entitäten deiner API entdeckt und pro Entität eine live, rechtegeprüfte und rückgängig-fähige UI rendert.',
|
|
12
|
+
'meta.login.title': 'Anmelden',
|
|
13
|
+
'meta.overview.title': 'Übersicht',
|
|
14
|
+
'meta.entity.title': 'Entität',
|
|
15
|
+
|
|
16
|
+
'nav.admin': 'Admin',
|
|
17
|
+
'nav.signIn': 'Anmelden',
|
|
18
|
+
'lang.label': 'Sprache',
|
|
19
|
+
|
|
20
|
+
'landing.heading': 'Das Backoffice, für dich erledigt.',
|
|
21
|
+
'landing.body': 'Dieser Admin liest zur Laufzeit die <strong>Capability-Map</strong> deiner API und rendert eine live, reaktive <code>DataTable</code> + <code>AutoForm</code> für jede Entität, die sie freigibt — rechtegeprüft mit <code>useCan</code>, mit einer Herkunfts-Lade pro Zeile und universellem Undo/Redo. Richte ihn auf eine beliebige Voltro-API und er passt sich an; der Seiten-Code unter <code>src/pages/admin/</code> gehört dir zum Bearbeiten.',
|
|
22
|
+
'landing.cta': 'Beim Admin anmelden →',
|
|
23
|
+
|
|
24
|
+
'login.heading': 'Anmelden',
|
|
25
|
+
'login.body': 'Keine echte Authentifizierung hier — dieser Button setzt ein Demo-Session-Cookie, damit du siehst, wie das Admin-Gate dich durchlässt.',
|
|
26
|
+
'login.cta': 'Als Demo-Admin fortfahren →',
|
|
27
|
+
|
|
28
|
+
'admin.undo': '↶ Rückgängig',
|
|
29
|
+
'admin.redo': '↷ Wiederholen',
|
|
30
|
+
'admin.nav.overview': 'Übersicht',
|
|
31
|
+
'admin.nav.discovering': 'wird erkannt…',
|
|
32
|
+
'admin.nav.backToSite': '← Zurück zur Seite',
|
|
33
|
+
|
|
34
|
+
'overview.heading': 'Entitäten',
|
|
35
|
+
'overview.intro': 'Erkannt aus der Capability-Map von <code>app</code>. Jede Karte ist eine live, rechtegeprüfte CRUD-Ansicht — ohne Code pro Entität.',
|
|
36
|
+
'overview.error': 'Die Capability-Map der API war nicht erreichbar. Läuft die API mit aktiviertem Inspect?',
|
|
37
|
+
'overview.columns': '{count, plural, one {# Spalte} other {# Spalten}}',
|
|
38
|
+
'overview.reactiveSuffix': ' · reaktiv',
|
|
39
|
+
'overview.readonly': 'schreibgeschützt',
|
|
40
|
+
'overview.empty': 'Diese API stellt keine Benutzer-Entitäten bereit.',
|
|
41
|
+
|
|
42
|
+
'entity.create': 'Erstellen',
|
|
43
|
+
'entity.addLabel': '{table} hinzufügen',
|
|
44
|
+
'entity.rows': 'Zeilen',
|
|
45
|
+
'entity.noList': 'Diese Entität stellt keine List-Query bereit.',
|
|
46
|
+
'entity.empty': 'Noch keine {table}.',
|
|
47
|
+
'entity.why': 'Warum?',
|
|
48
|
+
'entity.delete': 'Löschen',
|
|
49
|
+
'entity.unknown': 'Keine Entität <code>{entity}</code> in der Capability-Map dieser API.',
|
|
50
|
+
|
|
51
|
+
'prov.title': 'Herkunft · <code>{table}</code> #{id}',
|
|
52
|
+
'prov.close': 'schließen',
|
|
53
|
+
'prov.loading': 'lädt…',
|
|
54
|
+
'prov.none': 'Keine Herkunft (ist audit() auf dieser Tabelle + Inspect aktiviert?).',
|
|
55
|
+
'prov.lastWrite': 'letzte Änderung',
|
|
56
|
+
'prov.byActor': 'durch Akteur',
|
|
57
|
+
'prov.attribution': 'Zuordnung',
|
|
58
|
+
'prov.via': ' (über {via})',
|
|
59
|
+
'prov.derived': 'abgeleitet',
|
|
60
|
+
'prov.yes': 'ja',
|
|
61
|
+
|
|
62
|
+
'error.heading': 'Etwas ist schiefgelaufen',
|
|
63
|
+
'error.retry': 'Erneut versuchen',
|
|
64
|
+
'notFound.heading': 'Nicht gefunden',
|
|
65
|
+
'notFound.body': 'Diese Admin-Seite existiert nicht.',
|
|
66
|
+
'notFound.back': '← Zurück zur Übersicht',
|
|
67
|
+
})
|