@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,49 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro API scaffold (template: **api-backend-deactivation**) — the
|
|
4
|
+
`deactivation()` schema mixin: lock a user out without hiding their data.
|
|
5
|
+
|
|
6
|
+
## Boot
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
11
|
+
# → http://localhost:4000 (store: memory — zero infra)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## What this shows
|
|
15
|
+
|
|
16
|
+
- **`deactivation()` mixin** — `users.with(deactivation())` adds
|
|
17
|
+
`deactivatedAt` + `deactivatedBy` (→ `actors`) and pulls `audit()`
|
|
18
|
+
transitively. It's a pure schema mixin — no `plugins[]` entry.
|
|
19
|
+
- **Visible-but-locked-out** — set `deactivatedAt` with a normal
|
|
20
|
+
`ctx.store.update`. The row STAYS in the table and remains queryable; the
|
|
21
|
+
convention is that your auth layer refuses to authenticate a subject whose
|
|
22
|
+
`deactivatedAt` is set. This is the deliberate **opposite of
|
|
23
|
+
`softDelete()`**, which hides + anonymises the row (its `defaultWhere`
|
|
24
|
+
filters deleted rows out of every read).
|
|
25
|
+
|
|
26
|
+
## Try it
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# create a user, then read it (deactivatedAt is null):
|
|
30
|
+
ID=$(curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
|
|
31
|
+
-d '{"tag":"users.create","input":{"email":"ada@acme.com","name":"Ada"}}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["id"])')
|
|
32
|
+
curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
|
|
33
|
+
-d "{\"tag\":\"users.get\",\"input\":{\"id\":\"$ID\"}}"
|
|
34
|
+
# → { ok:true, result:{ …, deactivatedAt:null } }
|
|
35
|
+
|
|
36
|
+
# deactivate, then read AGAIN — still there, now stamped:
|
|
37
|
+
curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
|
|
38
|
+
-d "{\"tag\":\"users.deactivate\",\"input\":{\"id\":\"$ID\"}}" >/dev/null
|
|
39
|
+
curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
|
|
40
|
+
-d "{\"tag\":\"users.get\",\"input\":{\"id\":\"$ID\"}}"
|
|
41
|
+
# → { ok:true, result:{ …, deactivatedAt:"2026-…" } } (NOT null → still visible)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Full user lifecycle
|
|
45
|
+
|
|
46
|
+
Compose the lifecycle mixins for the complete column set:
|
|
47
|
+
`users.with(audit(), softDelete(), deactivation())` — who/when (audit),
|
|
48
|
+
hidden+anonymised on delete (softDelete), and visible-but-locked-out
|
|
49
|
+
(deactivation).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AppContext } from '@voltro/runtime'
|
|
2
|
+
import { eq } from '@voltro/database'
|
|
3
|
+
import { database } from '../database/schema'
|
|
4
|
+
|
|
5
|
+
const execute = async (input: { id: string }, ctx: AppContext) => {
|
|
6
|
+
const rows = await ctx.store.query(database.users.where(eq('id', input.id)).descriptor)
|
|
7
|
+
const u = rows[0]
|
|
8
|
+
if (!u) return null
|
|
9
|
+
return {
|
|
10
|
+
id: u['id'] as string,
|
|
11
|
+
email: u['email'] as string,
|
|
12
|
+
name: u['name'] as string,
|
|
13
|
+
deactivatedAt: (u['deactivatedAt'] as Date | null) ?? null,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default execute
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Reads one user by id — including a deactivated one. This is the whole point
|
|
2
|
+
// of deactivation() vs softDelete(): after deactivation the row is STILL here
|
|
3
|
+
// and readable, just stamped with `deactivatedAt`. (softDelete() would make
|
|
4
|
+
// this return null.)
|
|
5
|
+
|
|
6
|
+
import { defineAction } from '@voltro/protocol'
|
|
7
|
+
import { Schema } from 'effect'
|
|
8
|
+
|
|
9
|
+
export const getUser = defineAction({
|
|
10
|
+
name: 'users.get',
|
|
11
|
+
input: Schema.Struct({ id: Schema.String }),
|
|
12
|
+
output: Schema.NullOr(Schema.Struct({
|
|
13
|
+
id: Schema.String,
|
|
14
|
+
email: Schema.String,
|
|
15
|
+
name: Schema.String,
|
|
16
|
+
deactivatedAt: Schema.NullOr(Schema.Date),
|
|
17
|
+
})),
|
|
18
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Backend for the {{projectName}} project demonstrating the `deactivation()`
|
|
2
|
+
// schema mixin.
|
|
3
|
+
//
|
|
4
|
+
// deactivation() is a pure SCHEMA mixin (no plugins[] entry) — it adds
|
|
5
|
+
// `deactivatedAt` + `deactivatedBy` columns. A deactivated subject can't
|
|
6
|
+
// authenticate, but its row stays VISIBLE and queryable: no read-scoping, no
|
|
7
|
+
// delete interception. That's the deliberate opposite of softDelete(), which
|
|
8
|
+
// HIDES + anonymises. Set `deactivatedAt` with a normal `ctx.store.update`.
|
|
9
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
10
|
+
|
|
11
|
+
export const env = defineEnv({
|
|
12
|
+
LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
type: 'api' as const,
|
|
17
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
18
|
+
store: 'memory' as const,
|
|
19
|
+
env,
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Schema for the {{projectName}} backend — a `users` table with the
|
|
2
|
+
// `deactivation()` mixin. `actors` + `tenants` are the framework core tables.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
databaseHandle,
|
|
6
|
+
id,
|
|
7
|
+
table,
|
|
8
|
+
text,
|
|
9
|
+
timestamp,
|
|
10
|
+
type InferRow,
|
|
11
|
+
} from '@voltro/database'
|
|
12
|
+
import { deactivation } from '@voltro/plugin-deactivation'
|
|
13
|
+
|
|
14
|
+
export const actors = table('actors', {
|
|
15
|
+
id: id(),
|
|
16
|
+
kind: text().oneOf(['user', 'serviceAccount', 'apiKey', 'system']),
|
|
17
|
+
displayName: text().nullable(),
|
|
18
|
+
createdAt: timestamp().default('now'),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const tenants = table('tenants', {
|
|
22
|
+
id: id(),
|
|
23
|
+
name: text(),
|
|
24
|
+
createdAt: timestamp().default('now'),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const users = table('users', {
|
|
28
|
+
id: id(),
|
|
29
|
+
email: text().unique(),
|
|
30
|
+
name: text(),
|
|
31
|
+
})
|
|
32
|
+
// deactivation() adds deactivatedAt + deactivatedBy (→ actors) and pulls
|
|
33
|
+
// audit() transitively (createdAt/updatedAt/createdBy/updatedBy). NOTE: no
|
|
34
|
+
// defaultWhere — a deactivated user still shows up in queries, unlike
|
|
35
|
+
// softDelete() which would hide it.
|
|
36
|
+
.with(deactivation())
|
|
37
|
+
.reactive()
|
|
38
|
+
|
|
39
|
+
export type User = InferRow<typeof users>
|
|
40
|
+
|
|
41
|
+
export const database = databaseHandle({ actors, tenants, users })
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineMutation } from '@voltro/protocol'
|
|
2
|
+
import { Schema } from 'effect'
|
|
3
|
+
|
|
4
|
+
export const createUser = defineMutation({
|
|
5
|
+
name: 'users.create',
|
|
6
|
+
target: { table: 'users', op: 'insert' },
|
|
7
|
+
input: Schema.Struct({
|
|
8
|
+
email: Schema.NonEmptyString,
|
|
9
|
+
name: Schema.String,
|
|
10
|
+
}),
|
|
11
|
+
output: Schema.Struct({
|
|
12
|
+
id: Schema.String,
|
|
13
|
+
email: Schema.String,
|
|
14
|
+
name: Schema.String,
|
|
15
|
+
}),
|
|
16
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AppContext } from '@voltro/runtime'
|
|
2
|
+
|
|
3
|
+
const execute = async (input: { id: string }, ctx: AppContext) => {
|
|
4
|
+
// A plain update — set the timestamp. audit() stamps updatedAt/updatedBy.
|
|
5
|
+
const row = await ctx.store.update('users', input.id, { deactivatedAt: new Date() })
|
|
6
|
+
if (row === null) throw { status: 404, message: `user ${input.id} not found` }
|
|
7
|
+
return row
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default execute
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Deactivates a user — sets `deactivatedAt` with a normal update. The user's
|
|
2
|
+
// row stays in the table and remains visible to queries; the convention is
|
|
3
|
+
// that your auth layer refuses to authenticate a subject whose `deactivatedAt`
|
|
4
|
+
// is set. (Contrast softDelete(), which hides + anonymises the row.)
|
|
5
|
+
|
|
6
|
+
import { defineMutation } from '@voltro/protocol'
|
|
7
|
+
import { Schema } from 'effect'
|
|
8
|
+
|
|
9
|
+
export const deactivateUser = defineMutation({
|
|
10
|
+
name: 'users.deactivate',
|
|
11
|
+
target: { table: 'users', op: 'update' },
|
|
12
|
+
input: Schema.Struct({ id: Schema.String }),
|
|
13
|
+
output: Schema.Struct({
|
|
14
|
+
id: Schema.String,
|
|
15
|
+
email: Schema.String,
|
|
16
|
+
name: Schema.String,
|
|
17
|
+
deactivatedAt: Schema.NullOr(Schema.Date),
|
|
18
|
+
}),
|
|
19
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "voltro dev .",
|
|
8
|
+
"migrate": "voltro migrate",
|
|
9
|
+
"test": "voltro test",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@effect/platform": "^0.96.1",
|
|
14
|
+
"@effect/rpc": "^0.75.1",
|
|
15
|
+
"@voltro/cli": "0.1.0",
|
|
16
|
+
"@voltro/database": "0.1.0",
|
|
17
|
+
"@voltro/env": "0.1.0",
|
|
18
|
+
"@voltro/plugin-deactivation": "0.1.0",
|
|
19
|
+
"@voltro/protocol": "0.1.0",
|
|
20
|
+
"@voltro/runtime": "0.1.0",
|
|
21
|
+
"effect": "^3.21.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@voltro/testing": "0.1.0",
|
|
25
|
+
"typescript": "^5.7.0",
|
|
26
|
+
"vitest": "^3.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "api-backend-deactivation",
|
|
3
|
+
"kind": "api",
|
|
4
|
+
"summary": "The deactivation() schema mixin — adds deactivatedAt/deactivatedBy so a user can be locked out (your auth refuses a deactivated subject) while its row stays VISIBLE and queryable. The deliberate opposite of softDelete(), which hides + anonymises. Pure schema, zero infra.",
|
|
5
|
+
"tags": ["api", "deactivation", "user-lifecycle", "schema-mixin", "soft-delete"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Unit test with `@voltro/testing` — no database, no running server. Importing
|
|
2
|
+
// `../database/schema` registers the `users` / `actors` / `tenants` tables
|
|
3
|
+
// globally, so `makeTestContext` auto-wires them into the in-memory store.
|
|
4
|
+
// `ctx.store` is the SAME mixin-wrapped store the handlers get in production,
|
|
5
|
+
// so the `deactivation()` mixin columns behave exactly as at runtime.
|
|
6
|
+
//
|
|
7
|
+
// The POINT of this template: `deactivation()` is a columns-only mixin (adds
|
|
8
|
+
// `deactivatedAt` / `deactivatedBy`) with NO read scoping — a deactivated row
|
|
9
|
+
// stays fully VISIBLE, the deliberate opposite of `softDelete()`. These tests
|
|
10
|
+
// assert exactly that. Run with `voltro test` (vitest).
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'vitest'
|
|
13
|
+
import { makeTestContext, mockStore } from '@voltro/testing'
|
|
14
|
+
import { database } from '../database/schema' // registers users / actors / tenants
|
|
15
|
+
import createUser from '../mutations/users.create.mutation.server'
|
|
16
|
+
import deactivateUser from '../mutations/users.deactivate.mutation.server'
|
|
17
|
+
|
|
18
|
+
const subject = { type: 'user' as const, id: 'user_1', tenantId: null }
|
|
19
|
+
|
|
20
|
+
describe('users.create', () => {
|
|
21
|
+
it('inserts a user with an auto-injected id and no deactivation timestamp', async () => {
|
|
22
|
+
const ctx = makeTestContext({ subject, store: mockStore({ users: [] }) })
|
|
23
|
+
const row = await createUser({ email: 'ada@example.com', name: 'Ada' }, ctx)
|
|
24
|
+
|
|
25
|
+
expect(row['email']).toBe('ada@example.com')
|
|
26
|
+
expect(row['name']).toBe('Ada')
|
|
27
|
+
expect(row['id']).toBeTruthy() // TypeID auto-injected from id()
|
|
28
|
+
expect(row['deactivatedAt'] ?? null).toBeNull() // active on creation
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('users.deactivate', () => {
|
|
33
|
+
it('stamps deactivatedAt — and the row STAYS visible (unlike softDelete)', async () => {
|
|
34
|
+
const ctx = makeTestContext({ subject, store: mockStore({ users: [] }) })
|
|
35
|
+
const created = await createUser({ email: 'grace@example.com', name: 'Grace' }, ctx)
|
|
36
|
+
|
|
37
|
+
const updated = await deactivateUser({ id: String(created['id']) }, ctx)
|
|
38
|
+
expect(updated['deactivatedAt']).toBeInstanceOf(Date)
|
|
39
|
+
|
|
40
|
+
// deactivation() adds no defaultWhere, so a plain query still returns the
|
|
41
|
+
// deactivated row — this is the mixin's whole contract.
|
|
42
|
+
const rows = await ctx.store.query(database.users.descriptor)
|
|
43
|
+
expect(rows).toHaveLength(1)
|
|
44
|
+
expect(String(rows[0]!['id'])).toBe(String(created['id']))
|
|
45
|
+
expect(rows[0]!['deactivatedAt']).toBeInstanceOf(Date)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro backend scaffold (template: **api-backend-mail**).
|
|
4
|
+
|
|
5
|
+
## Boot
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install # at the repo root
|
|
9
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
10
|
+
# → http://localhost:4000
|
|
11
|
+
# → ws://localhost:4000/ws
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## What's in here
|
|
15
|
+
|
|
16
|
+
| File | Role |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `app.config.ts` | App declaration + `mailPlugin` wired (`console` provider). |
|
|
19
|
+
| `database/schema.ts` | One example table (`notes`) with the `tenant()` mixin. |
|
|
20
|
+
| `queries/` + `mutations/` | Streaming subscription + tenant-guarded mutation examples. |
|
|
21
|
+
| `emails/welcome.email.tsx` | A React-Email welcome template (edit/replace freely). |
|
|
22
|
+
| `actions/sendWelcome.{rpc,handler}.ts` | `mail.sendWelcome` — sends the welcome template. |
|
|
23
|
+
|
|
24
|
+
## Mail
|
|
25
|
+
|
|
26
|
+
Transactional email is wired out of the box via `@voltro/plugin-mail`:
|
|
27
|
+
|
|
28
|
+
- **Preview** templates in the dashboard's **Mail** tab (props box
|
|
29
|
+
pre-fills from each template's `preview`).
|
|
30
|
+
- **Send** with the `mail.sendWelcome` action, or `yield* MailService`
|
|
31
|
+
+ `mail.send({ template: 'welcome', props })` from any handler.
|
|
32
|
+
- The default `console` provider just **logs** the mail (zero-config, no
|
|
33
|
+
API key) and captures it in the dashboard outbox. For real delivery,
|
|
34
|
+
switch `provider` in `app.config.ts` to `'resend'` / `'postmark'` /
|
|
35
|
+
`'sendgrid'` / `'smtp'` and set the matching env var (e.g.
|
|
36
|
+
`RESEND_API_KEY`).
|
|
37
|
+
|
|
38
|
+
Drop more `*.email.tsx`, `*.query.ts` + `*.query.server.ts`, `*.mutation.ts` + `*.mutation.server.ts`, `*.action.ts` + `*.action.server.ts`, or `*.stream.ts` + `*.stream.server.ts`, or `*.workflow.tsx`
|
|
39
|
+
files anywhere in this tree — discovery is by file convention.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import { MailService } from '@voltro/plugin-mail'
|
|
3
|
+
|
|
4
|
+
const execute = (input: { email: string; name: string }) =>
|
|
5
|
+
Effect.gen(function* () {
|
|
6
|
+
const mail = yield* MailService
|
|
7
|
+
const res = yield* mail.send({
|
|
8
|
+
to: input.email,
|
|
9
|
+
template: 'welcome',
|
|
10
|
+
props: { name: input.name },
|
|
11
|
+
})
|
|
12
|
+
return { id: res.id, provider: res.provider }
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export default execute
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// `mail.sendWelcome` — example action that sends the `welcome` template
|
|
2
|
+
// via @voltro/plugin-mail. With the `console` provider nothing leaves the
|
|
3
|
+
// process — the send is logged + captured in the dev outbox (dashboard
|
|
4
|
+
// Mail panel). Swap the provider in app.config.ts for real delivery.
|
|
5
|
+
//
|
|
6
|
+
// Email is external I/O → an action, not a mutation.
|
|
7
|
+
|
|
8
|
+
import { Schema } from 'effect'
|
|
9
|
+
import { defineAction } from '@voltro/protocol'
|
|
10
|
+
|
|
11
|
+
export const sendWelcome = defineAction({
|
|
12
|
+
name: 'mail.sendWelcome',
|
|
13
|
+
input: Schema.Struct({
|
|
14
|
+
email: Schema.String,
|
|
15
|
+
name: Schema.String,
|
|
16
|
+
}),
|
|
17
|
+
output: Schema.Struct({
|
|
18
|
+
id: Schema.String,
|
|
19
|
+
provider: Schema.String,
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Backend app for the {{projectName}} project. Read by `voltro dev`.
|
|
2
|
+
//
|
|
3
|
+
// `store: 'memory'` is the dev-friendly default — fast startup, no
|
|
4
|
+
// docker. Switch to `'postgres'` when you wire `docker compose up`
|
|
5
|
+
// from the repo root.
|
|
6
|
+
//
|
|
7
|
+
// This variant ships transactional email out of the box via
|
|
8
|
+
// `@voltro/plugin-mail`. The `console` provider just logs what would be
|
|
9
|
+
// sent (zero-config, no API key) — swap to `'resend'` / `'postmark'` /
|
|
10
|
+
// `'sendgrid'` / `'smtp'` (+ the matching env var, e.g. RESEND_API_KEY)
|
|
11
|
+
// for real delivery. Templates live in `emails/*.email.tsx`; preview +
|
|
12
|
+
// send them from the dashboard's Mail tab.
|
|
13
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
14
|
+
import { mailPlugin } from '@voltro/plugin-mail'
|
|
15
|
+
|
|
16
|
+
// Typed environment — declare your env once; it's validated at boot (fail-fast)
|
|
17
|
+
// and read on the server via `serverEnv` / `getSecret` from '@voltro/env/server'.
|
|
18
|
+
// `access` is required: 'public' (browser-safe) or 'secret' (server-only, never
|
|
19
|
+
// bundled). Run `voltro env` to see the manifest. See the Environment docs.
|
|
20
|
+
export const env = defineEnv({
|
|
21
|
+
LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
|
|
22
|
+
// Add your own, e.g.:
|
|
23
|
+
// STRIPE_KEY: envVar.string({ access: 'secret' }),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
type: 'api' as const,
|
|
28
|
+
name: '{{projectNamePascal}}{{appNamePascal}}',
|
|
29
|
+
store: 'memory' as const,
|
|
30
|
+
env,
|
|
31
|
+
plugins: [
|
|
32
|
+
mailPlugin({ provider: 'console', from: '{{projectNamePascal}} <hello@example.com>' }),
|
|
33
|
+
],
|
|
34
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Schema for the {{projectName}} backend.
|
|
2
|
+
//
|
|
3
|
+
// Tables are auto-discovered: every exported `table(...)` in this file
|
|
4
|
+
// (or any `*.entity.ts` / `*.schema.ts` file) is registered by
|
|
5
|
+
// `voltro dev` — no manual barrel. `actors` + `tenants` are the
|
|
6
|
+
// framework's core tables: the built-in `audit()` / `tenant()` /
|
|
7
|
+
// `softDelete()` mixins reference them, so they must be declared (the
|
|
8
|
+
// CLI wires them into the mixin registry once it finds them).
|
|
9
|
+
//
|
|
10
|
+
// Add the `tenant()` mixin to a table to make it tenant-scoped — the
|
|
11
|
+
// runtime AND-merges `eq('tenantId', subject.tenantId)` into every
|
|
12
|
+
// subscription predicate, so cross-tenant reads can't leak.
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
boolean,
|
|
16
|
+
databaseHandle,
|
|
17
|
+
id,
|
|
18
|
+
table,
|
|
19
|
+
text,
|
|
20
|
+
timestamp,
|
|
21
|
+
type InferRow,
|
|
22
|
+
} from '@voltro/database'
|
|
23
|
+
import { tenant } from '@voltro/plugin-multitenancy'
|
|
24
|
+
|
|
25
|
+
// ---------- Core tables (required by the audit / tenant mixins) ----------
|
|
26
|
+
|
|
27
|
+
export const actors = table('actors', {
|
|
28
|
+
id: id(),
|
|
29
|
+
kind: text().oneOf(['user', 'serviceAccount', 'apiKey', 'system']),
|
|
30
|
+
displayName: text().nullable(),
|
|
31
|
+
createdAt: timestamp().default('now'),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const tenants = table('tenants', {
|
|
35
|
+
id: id(),
|
|
36
|
+
name: text(),
|
|
37
|
+
createdAt: timestamp().default('now'),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// ---------- Application tables — rename / replace with your own ----------
|
|
41
|
+
|
|
42
|
+
export const notes = table('notes', {
|
|
43
|
+
id: id({ prefix: 'note' }),
|
|
44
|
+
title: text(),
|
|
45
|
+
body: text(),
|
|
46
|
+
done: boolean().default(false),
|
|
47
|
+
})
|
|
48
|
+
// tenant() pulls audit() transitively → adds tenantId + createdAt /
|
|
49
|
+
// updatedAt / createdBy / updatedBy (auto-stamped by the runtime).
|
|
50
|
+
.with(tenant())
|
|
51
|
+
// Opt into the matcher engine: subscriptions against `notes` get a
|
|
52
|
+
// fresh snapshot/delta whenever a mutation writes the table.
|
|
53
|
+
.reactive()
|
|
54
|
+
|
|
55
|
+
export type Note = InferRow<typeof notes>
|
|
56
|
+
|
|
57
|
+
export const database = databaseHandle({ actors, tenants, notes })
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Default transactional email — a clean, ready-to-send welcome built with
|
|
2
|
+
// @react-email/components. Auto-discovered by `voltro dev` and registered
|
|
3
|
+
// with @voltro/plugin-mail, so `mail.send({ template: 'welcome', props })`
|
|
4
|
+
// works + the dashboard Mail panel previews it.
|
|
5
|
+
//
|
|
6
|
+
// This is a STARTER you replace: swap the copy, colours, and layout for
|
|
7
|
+
// your brand. The component kit (Container / Section / Button / …) handles
|
|
8
|
+
// the email-client quirks so it renders consistently across Gmail/Outlook/
|
|
9
|
+
// Apple Mail. Plain JSX tags also work if you'd rather not use the kit.
|
|
10
|
+
//
|
|
11
|
+
// Light + dark: inline styles are the LIGHT defaults; the <style> block in
|
|
12
|
+
// <Head> overrides colours when the client is in dark mode
|
|
13
|
+
// (`prefers-color-scheme`) — and, for the dashboard preview's Light/Dark
|
|
14
|
+
// switch, when an ancestor carries `[data-theme="dark"]`. `!important`
|
|
15
|
+
// beats the inline light values. Clients that strip <style> (older Outlook)
|
|
16
|
+
// just stay light.
|
|
17
|
+
|
|
18
|
+
import { defineEmail } from '@voltro/plugin-mail'
|
|
19
|
+
import {
|
|
20
|
+
Body,
|
|
21
|
+
Button,
|
|
22
|
+
Container,
|
|
23
|
+
Head,
|
|
24
|
+
Heading,
|
|
25
|
+
Hr,
|
|
26
|
+
Html,
|
|
27
|
+
Preview,
|
|
28
|
+
Section,
|
|
29
|
+
Text,
|
|
30
|
+
} from '@react-email/components'
|
|
31
|
+
import { Schema } from 'effect'
|
|
32
|
+
|
|
33
|
+
// Your product/brand name. Defaults to the project name; edit freely.
|
|
34
|
+
const BRAND = '{{projectNamePascal}}'
|
|
35
|
+
|
|
36
|
+
const darkCss = `
|
|
37
|
+
.e-body { background-color: #f4f4f5; }
|
|
38
|
+
@media (prefers-color-scheme: dark) {
|
|
39
|
+
.e-body { background-color: #0a0a0a !important; }
|
|
40
|
+
.e-card { background-color: #18181b !important; border-color: #27272a !important; }
|
|
41
|
+
.e-heading { color: #fafafa !important; }
|
|
42
|
+
.e-text { color: #d4d4d8 !important; }
|
|
43
|
+
.e-footer { color: #a1a1aa !important; }
|
|
44
|
+
}
|
|
45
|
+
[data-theme="dark"] .e-body { background-color: #0a0a0a !important; }
|
|
46
|
+
[data-theme="dark"] .e-card { background-color: #18181b !important; border-color: #27272a !important; }
|
|
47
|
+
[data-theme="dark"] .e-heading { color: #fafafa !important; }
|
|
48
|
+
[data-theme="dark"] .e-text { color: #d4d4d8 !important; }
|
|
49
|
+
[data-theme="dark"] .e-footer { color: #a1a1aa !important; }
|
|
50
|
+
[data-theme="light"] .e-body { background-color: #f4f4f5 !important; }
|
|
51
|
+
[data-theme="light"] .e-card { background-color: #ffffff !important; border-color: #e4e4e7 !important; }
|
|
52
|
+
[data-theme="light"] .e-heading { color: #18181b !important; }
|
|
53
|
+
[data-theme="light"] .e-text { color: #3f3f46 !important; }
|
|
54
|
+
[data-theme="light"] .e-footer { color: #a1a1aa !important; }
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
const styles = {
|
|
58
|
+
// No backgroundColor here on purpose — react-email copies the Body inline
|
|
59
|
+
// style onto an inner wrapper <td>, which would then keep the light bg even
|
|
60
|
+
// in dark mode. The body bg lives in the `.e-body` class (light + dark) so
|
|
61
|
+
// a single rule controls it. (margin/padding/font stay inline.)
|
|
62
|
+
body: { fontFamily: 'ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif', margin: 0, padding: '32px 0' },
|
|
63
|
+
container: { backgroundColor: '#ffffff', borderRadius: '12px', maxWidth: '480px', margin: '0 auto', padding: '40px', border: '1px solid #e4e4e7' },
|
|
64
|
+
heading: { fontSize: '22px', fontWeight: 700, color: '#18181b', margin: '0 0 16px' },
|
|
65
|
+
text: { fontSize: '15px', lineHeight: '24px', color: '#3f3f46', margin: '0 0 16px' },
|
|
66
|
+
button: { backgroundColor: '#6d28d9', color: '#ffffff', borderRadius: '8px', fontSize: '15px', fontWeight: 600, textDecoration: 'none', padding: '12px 20px', display: 'inline-block' },
|
|
67
|
+
hr: { borderColor: '#e4e4e7', margin: '28px 0' },
|
|
68
|
+
footer: { fontSize: '13px', lineHeight: '20px', color: '#a1a1aa', margin: 0 },
|
|
69
|
+
} as const
|
|
70
|
+
|
|
71
|
+
export const welcome = defineEmail({
|
|
72
|
+
name: 'welcome',
|
|
73
|
+
props: Schema.Struct({ name: Schema.String }),
|
|
74
|
+
// Realistic example props — the dashboard preview box pre-fills with this.
|
|
75
|
+
preview: { name: 'Mario' },
|
|
76
|
+
subject: (p) => `Welcome to ${BRAND}, ${p.name}!`,
|
|
77
|
+
render: (p) => (
|
|
78
|
+
<Html>
|
|
79
|
+
<Head>
|
|
80
|
+
<meta name="color-scheme" content="light dark" />
|
|
81
|
+
<meta name="supported-color-schemes" content="light dark" />
|
|
82
|
+
<style dangerouslySetInnerHTML={{ __html: darkCss }} />
|
|
83
|
+
</Head>
|
|
84
|
+
<Preview>Your {BRAND} account is ready — let's get you started.</Preview>
|
|
85
|
+
<Body className="e-body" style={styles.body}>
|
|
86
|
+
<Container className="e-card" style={styles.container}>
|
|
87
|
+
<Heading className="e-heading" style={styles.heading}>Welcome, {p.name} 👋</Heading>
|
|
88
|
+
<Text className="e-text" style={styles.text}>
|
|
89
|
+
Thanks for joining {BRAND}. Your workspace is ready — jump in and
|
|
90
|
+
create your first project whenever you're set.
|
|
91
|
+
</Text>
|
|
92
|
+
<Section>
|
|
93
|
+
<Button href="https://example.com/dashboard" style={styles.button}>
|
|
94
|
+
Open your dashboard
|
|
95
|
+
</Button>
|
|
96
|
+
</Section>
|
|
97
|
+
<Hr style={styles.hr} />
|
|
98
|
+
<Text className="e-footer" style={styles.footer}>
|
|
99
|
+
You're receiving this because you signed up for {BRAND}. If this
|
|
100
|
+
wasn't you, just ignore this email.
|
|
101
|
+
</Text>
|
|
102
|
+
</Container>
|
|
103
|
+
</Body>
|
|
104
|
+
</Html>
|
|
105
|
+
),
|
|
106
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { assertOwnTenant } from '@voltro/plugin-multitenancy/guard'
|
|
2
|
+
import type { AppContext } from '@voltro/runtime'
|
|
3
|
+
|
|
4
|
+
const execute = async (
|
|
5
|
+
input: { tenantId: string; title: string; body: string },
|
|
6
|
+
ctx: AppContext,
|
|
7
|
+
) => {
|
|
8
|
+
assertOwnTenant(input.tenantId, ctx.request.subject)
|
|
9
|
+
// Framework auto-injects a `note_…` id from the table's id() decl.
|
|
10
|
+
return ctx.store.insert('notes', {
|
|
11
|
+
title: input.title,
|
|
12
|
+
body: input.body,
|
|
13
|
+
done: false,
|
|
14
|
+
tenantId: input.tenantId,
|
|
15
|
+
createdAt: new Date(),
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default execute
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Creates a note. The handler's `assertOwnTenant` guard rejects
|
|
2
|
+
// cross-tenant spoofing — input.tenantId MUST match the resolved
|
|
3
|
+
// subject's tenantId, else the rpc layer surfaces a typed
|
|
4
|
+
// `TenantMismatch` error.
|
|
5
|
+
|
|
6
|
+
import { defineMutation } from '@voltro/protocol'
|
|
7
|
+
import { TenantMismatch } from '@voltro/plugin-multitenancy/guard'
|
|
8
|
+
import { Schema } from 'effect'
|
|
9
|
+
|
|
10
|
+
export const createNote = defineMutation({
|
|
11
|
+
name: 'notes.create',
|
|
12
|
+
target: {
|
|
13
|
+
table: 'notes',
|
|
14
|
+
op: 'insert',
|
|
15
|
+
shape: (input: { tenantId: string; title: string; body: string }) => ({
|
|
16
|
+
title: input.title,
|
|
17
|
+
body: input.body,
|
|
18
|
+
done: false,
|
|
19
|
+
tenantId: input.tenantId,
|
|
20
|
+
createdAt: new Date(),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
input: Schema.Struct({
|
|
24
|
+
tenantId: Schema.String,
|
|
25
|
+
title: Schema.NonEmptyString,
|
|
26
|
+
body: Schema.String,
|
|
27
|
+
}),
|
|
28
|
+
output: Schema.Struct({
|
|
29
|
+
id: Schema.String,
|
|
30
|
+
title: Schema.String,
|
|
31
|
+
body: Schema.String,
|
|
32
|
+
done: Schema.Boolean,
|
|
33
|
+
tenantId: Schema.String,
|
|
34
|
+
createdAt: Schema.Date,
|
|
35
|
+
}),
|
|
36
|
+
error: TenantMismatch,
|
|
37
|
+
})
|