@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,109 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro backend scaffold (template: **api-auth**).
|
|
4
|
+
|
|
5
|
+
Real user authentication, wired turnkey with
|
|
6
|
+
[`@voltro/plugin-auth`](https://voltro.cloud/docs/plugins/auth): password
|
|
7
|
+
**sign-up / sign-in / sign-out** over HttpOnly **session cookies**, a strategy
|
|
8
|
+
that resolves the session into a typed `Subject` on every rpc call, and a
|
|
9
|
+
`session.me` action that proves the loop end-to-end. Boots with **zero infra**
|
|
10
|
+
(`memoryUserStore()`).
|
|
11
|
+
|
|
12
|
+
## Boot
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm install
|
|
16
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
17
|
+
# → http://localhost:4000 (rpc + /auth/* HTTP routes)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The shipped `.env` supplies a DEV-ONLY `VOLTRO_SESSION_SECRET` so it boots out
|
|
21
|
+
of the box. **Replace it before production** (`openssl rand -hex 32`) — see
|
|
22
|
+
Environment below.
|
|
23
|
+
|
|
24
|
+
## The two pieces
|
|
25
|
+
|
|
26
|
+
`app.config.ts` wires both halves with ONE shared secret:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
plugins: [
|
|
30
|
+
authRoutesPlugin({ store: memoryUserStore(), secret: SECRET, defaultTenantId: 'acme' }),
|
|
31
|
+
],
|
|
32
|
+
auth: {
|
|
33
|
+
strategies: [voltroPasswordStrategy({ secret: SECRET })],
|
|
34
|
+
},
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **`authRoutesPlugin`** mounts the HTTP auth surface under `/auth/*` and
|
|
38
|
+
**signs** the session cookie on success.
|
|
39
|
+
- **`voltroPasswordStrategy`** runs in the AuthMiddleware chain on every
|
|
40
|
+
rpc/ws call, **verifies** that cookie, and resolves it to a `Subject` — so
|
|
41
|
+
handlers read the user via `ctx.request.subject`.
|
|
42
|
+
|
|
43
|
+
They MUST share the same `secret` (one signs, the other verifies). The template
|
|
44
|
+
reads `VOLTRO_SESSION_SECRET` once and passes it to both.
|
|
45
|
+
|
|
46
|
+
## Try the loop (curl)
|
|
47
|
+
|
|
48
|
+
State-changing routes are CSRF-protected, so grab a token first. (`session.me`
|
|
49
|
+
is an rpc procedure — invoke it over HTTP via the dev inspect endpoint, which
|
|
50
|
+
forwards your session cookie; from a browser you'd use `useAction('app',
|
|
51
|
+
'session.me')`.)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 1. Get a CSRF token (also sets the csrf cookie into the jar)
|
|
55
|
+
curl -s -c jar.txt http://localhost:4000/auth/csrf
|
|
56
|
+
# → { "csrfToken": "<csrf>" }
|
|
57
|
+
|
|
58
|
+
# 2. Sign up (sets the HttpOnly session cookie into the jar)
|
|
59
|
+
curl -s -b jar.txt -c jar.txt -X POST http://localhost:4000/auth/sign-up \
|
|
60
|
+
-H 'content-type: application/json' -H 'x-csrf-token: <csrf>' \
|
|
61
|
+
-d '{"email":"ada@example.com","password":"hunter2hunter2"}'
|
|
62
|
+
# → { "ok": true, "subject": { "type": "user", "id": "u_…", "tenantId": "acme" } }
|
|
63
|
+
|
|
64
|
+
# 3. Call session.me, forwarding the session cookie → you're a `user`
|
|
65
|
+
curl -s -b jar.txt -X POST http://localhost:4000/_voltro/inspect/invoke \
|
|
66
|
+
-H 'content-type: application/json' \
|
|
67
|
+
-d '{"tag":"session.me","input":{}}'
|
|
68
|
+
# → { "ok": true, "result": { "type": "user", "id": "u_…", "tenantId": "acme" } }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Call `session.me` WITHOUT the cookie and `type` is `"anonymous"` with a null
|
|
72
|
+
`id` — that's the strategy resolving (or not resolving) the session. (Note: the
|
|
73
|
+
session cookie is `Secure` only in production — see `cookieSecure` in
|
|
74
|
+
`app.config.ts` — so the loop works over plain-HTTP `localhost` in dev.)
|
|
75
|
+
|
|
76
|
+
`/auth/*` also exposes `sign-in`, `sign-out`, `password-reset`,
|
|
77
|
+
`magic-link`, session management (`/auth/sessions`), and tenant memberships —
|
|
78
|
+
see the plugin docs.
|
|
79
|
+
|
|
80
|
+
## Environment
|
|
81
|
+
|
|
82
|
+
| Var | Access | Why |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `VOLTRO_SESSION_SECRET` | secret | HMAC key signing + verifying the session cookie. The shipped `.env` is a DEV-ONLY placeholder; generate a real one (`openssl rand -hex 32`) and set it in your deployment's secrets. Rotating it invalidates all existing sessions. |
|
|
85
|
+
| `LOG_LEVEL` | public | `debug \| info \| warn \| error` (default `info`). |
|
|
86
|
+
|
|
87
|
+
## Files
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
app.config.ts authRoutesPlugin + voltroPasswordStrategy + memoryUserStore
|
|
91
|
+
.env DEV-ONLY VOLTRO_SESSION_SECRET
|
|
92
|
+
database/schema.ts core actors + tenants (users live in the memory store)
|
|
93
|
+
actions/
|
|
94
|
+
me.action.ts(.server) session.me — returns the resolved Subject
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Going to production
|
|
98
|
+
|
|
99
|
+
- **Durable accounts** — swap `memoryUserStore()` for `postgresUserStore({ sql })`
|
|
100
|
+
and `store: 'postgres'`. The postgres store manages its own auth tables
|
|
101
|
+
(auto-migrated).
|
|
102
|
+
- **Email flows** — pass `sendEmail: mailSender(mailService)` (from
|
|
103
|
+
`@voltro/plugin-mail`) to `authRoutesPlugin` so magic-link + password-reset
|
|
104
|
+
can deliver. Without it those routes still mint tokens but return `202`.
|
|
105
|
+
- **Cookie hardening** — set `cookieSecure: true` (and `cookieDomain`) for
|
|
106
|
+
HTTPS deployments.
|
|
107
|
+
- **Pair with a frontend** — `frontend-app` shows the reactive client; add a
|
|
108
|
+
login form that POSTs to `/auth/sign-in`, and authenticated rpc calls carry
|
|
109
|
+
the session cookie automatically.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Executor for `session.me`. Reads the Subject the AuthMiddleware resolved
|
|
2
|
+
// (via voltroPasswordStrategy reading the session cookie) and returns it.
|
|
3
|
+
import type { AppContext } from '@voltro/runtime'
|
|
4
|
+
|
|
5
|
+
const execute = async (_input: Record<string, never>, ctx: AppContext) => {
|
|
6
|
+
// The Subject union: { type: 'anonymous' | 'user' | 'apiKey' | 'serviceAccount',
|
|
7
|
+
// id: string | null, tenantId: string | null }.
|
|
8
|
+
const subject = ctx.request.subject as {
|
|
9
|
+
readonly type: string
|
|
10
|
+
readonly id: string | null
|
|
11
|
+
readonly tenantId: string | null
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
type: subject.type,
|
|
15
|
+
id: subject.id ?? null,
|
|
16
|
+
tenantId: subject.tenantId ?? null,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default execute
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// `session.me` — returns the caller's resolved identity. This is the proof the
|
|
2
|
+
// auth loop works end-to-end: call it with no session cookie and you get an
|
|
3
|
+
// anonymous Subject; sign in via POST /auth/sign-in (which sets the cookie),
|
|
4
|
+
// call it again, and `voltroPasswordStrategy` has resolved that cookie into a
|
|
5
|
+
// `user` Subject. Browser-safe descriptor — no server imports here.
|
|
6
|
+
import { defineAction } from '@voltro/protocol'
|
|
7
|
+
import { Schema } from 'effect'
|
|
8
|
+
|
|
9
|
+
export const me = defineAction({
|
|
10
|
+
name: 'session.me',
|
|
11
|
+
input: Schema.Struct({}),
|
|
12
|
+
output: Schema.Struct({
|
|
13
|
+
// 'anonymous' | 'user' | 'apiKey' | 'serviceAccount'
|
|
14
|
+
type: Schema.String,
|
|
15
|
+
// null while anonymous; the user id once signed in.
|
|
16
|
+
id: Schema.NullOr(Schema.String),
|
|
17
|
+
tenantId: Schema.NullOr(Schema.String),
|
|
18
|
+
}),
|
|
19
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Backend app for the {{projectName}} project. Read by `voltro dev`.
|
|
2
|
+
//
|
|
3
|
+
// This template wires REAL user authentication with @voltro/plugin-auth:
|
|
4
|
+
// - `authRoutesPlugin(...)` mounts the password auth surface under /auth/*
|
|
5
|
+
// (POST /auth/sign-up, /auth/sign-in, /auth/sign-out, GET /auth/csrf,
|
|
6
|
+
// password-reset, magic-link, session management, …). It sets an HttpOnly
|
|
7
|
+
// session cookie on success.
|
|
8
|
+
// - `voltroPasswordStrategy(...)` runs in the AuthMiddleware chain: it reads
|
|
9
|
+
// that session cookie off every rpc/ws call and resolves it to a typed
|
|
10
|
+
// `Subject` — so handlers see the authenticated user via
|
|
11
|
+
// `ctx.request.subject` (see actions/me.action.server.ts).
|
|
12
|
+
//
|
|
13
|
+
// `memoryUserStore()` keeps users/sessions in-process, so it boots with ZERO
|
|
14
|
+
// infra. Swap it for `postgresUserStore({ sql })` (and `store: 'postgres'`)
|
|
15
|
+
// for durable accounts.
|
|
16
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
17
|
+
import { authRoutesPlugin, memoryUserStore, voltroPasswordStrategy } from '@voltro/plugin-auth'
|
|
18
|
+
|
|
19
|
+
export const env = defineEnv({
|
|
20
|
+
LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
|
|
21
|
+
// The HMAC key that signs + verifies session cookies. Declared here so it
|
|
22
|
+
// shows in `voltro env` and the boot gate fails loud if it's unset. The
|
|
23
|
+
// shipped `.env` supplies a DEV-ONLY value; generate a real one for prod
|
|
24
|
+
// (`openssl rand -hex 32`).
|
|
25
|
+
VOLTRO_SESSION_SECRET: envVar.string({ access: 'secret' }),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Read once at config time and pass to BOTH the routes plugin (which signs
|
|
29
|
+
// sessions) and the strategy (which verifies them) so they can never drift.
|
|
30
|
+
// process.env is populated by the loaded `.env`; this is the documented shape
|
|
31
|
+
// for a config-time secret (the typed `serverEnv` snapshot isn't ready yet).
|
|
32
|
+
const SECRET = process.env.VOLTRO_SESSION_SECRET ?? 'dev-only-unsafe-session-secret-change-me'
|
|
33
|
+
|
|
34
|
+
// In-process user store — zero infra. `postgresUserStore({ sql })` for durable.
|
|
35
|
+
const userStore = memoryUserStore()
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
type: 'api' as const,
|
|
39
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
40
|
+
store: 'memory' as const,
|
|
41
|
+
env,
|
|
42
|
+
plugins: [
|
|
43
|
+
authRoutesPlugin({
|
|
44
|
+
store: userStore,
|
|
45
|
+
secret: SECRET,
|
|
46
|
+
defaultTenantId: 'acme', // tenant new sign-ups land in
|
|
47
|
+
successRedirect: '/', // where the browser-form flow redirects after sign-in
|
|
48
|
+
// `Secure` cookies are HTTPS-only — so OFF in dev (http://localhost would
|
|
49
|
+
// silently drop the session cookie), ON in production. This is what makes
|
|
50
|
+
// the sign-in → session → authenticated-call loop actually work locally.
|
|
51
|
+
cookieSecure: process.env.NODE_ENV === 'production',
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
auth: {
|
|
55
|
+
// Runs after the built-in signed-cookie strategy; resolves the session
|
|
56
|
+
// cookie this app's /auth routes mint into the request's Subject.
|
|
57
|
+
strategies: [voltroPasswordStrategy({ secret: SECRET })],
|
|
58
|
+
},
|
|
59
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Minimal schema. Auth users + sessions live in the `memoryUserStore()`
|
|
2
|
+
// (in-process), NOT in these tables — so there's no users table here. These
|
|
3
|
+
// are the framework core tables every app declares.
|
|
4
|
+
//
|
|
5
|
+
// When you switch to `postgresUserStore({ sql })`, that store manages its own
|
|
6
|
+
// `_voltro_auth_*` tables (auto-migrated); you still keep these core tables for
|
|
7
|
+
// your own domain data.
|
|
8
|
+
import { databaseHandle, id, table, text, timestamp } from '@voltro/database'
|
|
9
|
+
|
|
10
|
+
// The universal audit subject (user / serviceAccount / apiKey / system).
|
|
11
|
+
export const actors = table('actors', {
|
|
12
|
+
id: id(),
|
|
13
|
+
kind: text().oneOf(['user', 'serviceAccount', 'apiKey', 'system']),
|
|
14
|
+
displayName: text().nullable(),
|
|
15
|
+
createdAt: timestamp().default('now'),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// The multi-tenancy boundary. New sign-ups land in `defaultTenantId` (see
|
|
19
|
+
// app.config.ts → authRoutesPlugin).
|
|
20
|
+
export const tenants = table('tenants', {
|
|
21
|
+
id: id(),
|
|
22
|
+
name: text(),
|
|
23
|
+
createdAt: timestamp().default('now'),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const database = databaseHandle({ actors, tenants })
|
|
@@ -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-auth": "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-auth",
|
|
3
|
+
"kind": "api",
|
|
4
|
+
"summary": "Real user auth — @voltro/plugin-auth wired turnkey: password sign-up/in/out + HttpOnly session cookies over /auth/*, a voltroPasswordStrategy that resolves the session to a typed Subject, and a session.me action proving it end-to-end. Boots zero-infra (memoryUserStore).",
|
|
5
|
+
"tags": ["api", "auth", "authentication", "sessions", "login", "plugin-auth", "cookies"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Unit test with `@voltro/testing`. `session.me` returns the caller's resolved
|
|
2
|
+
// identity — the proof the auth loop threads the Subject through to handlers.
|
|
3
|
+
// `makeTestContext({ subject })` sets `ctx.request.subject` to exactly what the
|
|
4
|
+
// AuthMiddleware would resolve at runtime, so the executor is fully runnable
|
|
5
|
+
// with no cookie, no session store, no running server.
|
|
6
|
+
//
|
|
7
|
+
// (This template has no DB-write primitive — the identity read IS the runnable
|
|
8
|
+
// unit here.) Run with `voltro test` (vitest).
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest'
|
|
11
|
+
import { makeTestContext } from '@voltro/testing'
|
|
12
|
+
import '../database/schema' // registers actors / tenants
|
|
13
|
+
import me from '../actions/me.action.server'
|
|
14
|
+
|
|
15
|
+
describe('session.me', () => {
|
|
16
|
+
it('reflects a signed-in user Subject', async () => {
|
|
17
|
+
const ctx = makeTestContext({
|
|
18
|
+
subject: { type: 'user', id: 'user_1', tenantId: 'acme' },
|
|
19
|
+
})
|
|
20
|
+
const identity = await me({}, ctx)
|
|
21
|
+
|
|
22
|
+
expect(identity.type).toBe('user')
|
|
23
|
+
expect(identity.id).toBe('user_1')
|
|
24
|
+
expect(identity.tenantId).toBe('acme')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('returns an anonymous identity with null id/tenant when no session is present', async () => {
|
|
28
|
+
// Default `makeTestContext()` subject is the anonymous Subject
|
|
29
|
+
// ({ type: 'anonymous', id: null, tenantId: null }).
|
|
30
|
+
const ctx = makeTestContext()
|
|
31
|
+
const identity = await me({}, ctx)
|
|
32
|
+
|
|
33
|
+
expect(identity.type).toBe('anonymous')
|
|
34
|
+
expect(identity.id).toBeNull()
|
|
35
|
+
expect(identity.tenantId).toBeNull()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro backend scaffold (template: **api-backend**).
|
|
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; `voltro dev` reads this first. |
|
|
19
|
+
| `database/schema.ts` | One example table (`notes`) with the `tenant()` mixin. |
|
|
20
|
+
| `routes/notes.route.tsx` | Streaming subscription example. |
|
|
21
|
+
| `mutations/notes.create.mutation.tsx` | Tenant-guarded mutation example. |
|
|
22
|
+
|
|
23
|
+
Drop more `*.route.tsx`, `*.mutation.tsx`, `*.action.tsx`, or
|
|
24
|
+
`*.workflow.tsx` files anywhere in this tree — discovery is by file
|
|
25
|
+
convention.
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
// `plugins` composes cross-cutting behaviour. Drop entries here to
|
|
8
|
+
// audit-log mutations, run authz checks, ship webhooks, etc. See
|
|
9
|
+
// @voltro/plugin-audit for a reference plugin.
|
|
10
|
+
//
|
|
11
|
+
// OPTIONAL infra knobs — both are ON by default with a zero-infra backend,
|
|
12
|
+
// so you only touch them to go distributed (see the commented lines below):
|
|
13
|
+
// • `cache:` — the query/result cache (`ctx.cache`). Default `memory`
|
|
14
|
+
// (in-process). Set `'redis'` for a cross-instance cache — or just run
|
|
15
|
+
// `voltro add redis`, which flips this and provisions the infra for you.
|
|
16
|
+
// • `kv:` — the durable key-value store (`ctx.kv`, for state you CAN'T
|
|
17
|
+
// recompute: cursors, onboarding progress, tokens). Default `database`
|
|
18
|
+
// (durable, shared across replicas). `'redis'` needs a PERSISTENT redis;
|
|
19
|
+
// `'memory'` is dev-only. Env `CACHE_BACKEND` / `KV_BACKEND` override.
|
|
20
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
21
|
+
|
|
22
|
+
// Typed environment — declare your env once; it's validated at boot (fail-fast)
|
|
23
|
+
// and read on the server via `serverEnv` / `getSecret` from '@voltro/env/server'.
|
|
24
|
+
// `access` is required: 'public' (browser-safe) or 'secret' (server-only, never
|
|
25
|
+
// bundled). Run `voltro env` to see the manifest. See the Environment docs.
|
|
26
|
+
export const env = defineEnv({
|
|
27
|
+
LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
|
|
28
|
+
// Add your own, e.g.:
|
|
29
|
+
// STRIPE_KEY: envVar.string({ access: 'secret' }),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
type: 'api' as const,
|
|
34
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
35
|
+
store: 'memory' as const,
|
|
36
|
+
// cache: 'redis' as const, // distributed query/result cache (or: voltro add redis)
|
|
37
|
+
// kv: 'redis' as const, // durable ctx.kv on Redis (default 'database' — durable)
|
|
38
|
+
env,
|
|
39
|
+
}
|
|
@@ -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,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
|
+
})
|
|
@@ -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-multitenancy": "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,14 @@
|
|
|
1
|
+
import type { AppContext } from '@voltro/runtime'
|
|
2
|
+
|
|
3
|
+
const execute = (_input: Record<string, never>, _ctx: AppContext) => ({
|
|
4
|
+
descriptor: {
|
|
5
|
+
table: 'notes' as const,
|
|
6
|
+
predicate: undefined,
|
|
7
|
+
order: [{ column: 'createdAt' as const, direction: 'desc' as const }],
|
|
8
|
+
take: 100,
|
|
9
|
+
skip: undefined,
|
|
10
|
+
projection: undefined,
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default execute
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Streaming subscription: every change to `notes` for the caller's
|
|
2
|
+
// tenant lands as a delta. The runtime AND-merges tenant scope into
|
|
3
|
+
// the predicate via the `tenant()` mixin on the table — no manual
|
|
4
|
+
// `eq('tenantId', ...)` needed in the handler.
|
|
5
|
+
|
|
6
|
+
import { defineQuery } from '@voltro/protocol'
|
|
7
|
+
import { Schema } from 'effect'
|
|
8
|
+
|
|
9
|
+
export const listNotes = defineQuery({
|
|
10
|
+
name: 'notes.list',
|
|
11
|
+
input: Schema.Struct({}),
|
|
12
|
+
output: Schema.Struct({
|
|
13
|
+
id: Schema.String,
|
|
14
|
+
title: Schema.String,
|
|
15
|
+
body: Schema.String,
|
|
16
|
+
done: Schema.Boolean,
|
|
17
|
+
tenantId: Schema.String,
|
|
18
|
+
createdAt: Schema.Date,
|
|
19
|
+
}),
|
|
20
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Unit test with `@voltro/testing` — no database, no running server. Importing
|
|
2
|
+
// `../database/schema` registers the `notes` / `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 handler gets in production,
|
|
5
|
+
// so tenant auto-scoping + audit auto-fill behave exactly as at runtime.
|
|
6
|
+
// Run with `voltro test` (vitest).
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from 'vitest'
|
|
9
|
+
import { makeTestContext, mockStore } from '@voltro/testing'
|
|
10
|
+
import { database } from '../database/schema' // registers notes / actors / tenants
|
|
11
|
+
import { TenantMismatch } from '@voltro/plugin-multitenancy/guard'
|
|
12
|
+
import createNote from '../mutations/notes.create.mutation.server'
|
|
13
|
+
|
|
14
|
+
describe('notes.create', () => {
|
|
15
|
+
it('inserts a note stamped to the caller tenant', async () => {
|
|
16
|
+
const ctx = makeTestContext({
|
|
17
|
+
subject: { type: 'user', id: 'user_1', tenantId: 'acme' },
|
|
18
|
+
store: mockStore({ notes: [] }),
|
|
19
|
+
})
|
|
20
|
+
const row = await createNote({ tenantId: 'acme', title: 'Hello', body: 'World' }, ctx)
|
|
21
|
+
|
|
22
|
+
expect(row['title']).toBe('Hello')
|
|
23
|
+
expect(row['tenantId']).toBe('acme') // assertOwnTenant let it through + stamped
|
|
24
|
+
expect(row['done']).toBe(false)
|
|
25
|
+
expect(String(row['id'])).toMatch(/^note_/) // TypeID auto-injected from id()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('rejects a cross-tenant spoof — input.tenantId must match the subject', async () => {
|
|
29
|
+
const ctx = makeTestContext({
|
|
30
|
+
subject: { type: 'user', id: 'user_1', tenantId: 'acme' },
|
|
31
|
+
store: mockStore({ notes: [] }),
|
|
32
|
+
})
|
|
33
|
+
await expect(
|
|
34
|
+
createNote({ tenantId: 'other-tenant', title: 'spoof', body: '' }, ctx),
|
|
35
|
+
).rejects.toBeInstanceOf(TenantMismatch)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('isolates tenants — t2 never sees t1 rows (REAL tenant scoping)', async () => {
|
|
39
|
+
const ctx = makeTestContext({
|
|
40
|
+
subject: { type: 'user', id: 'u1', tenantId: 't1' },
|
|
41
|
+
store: mockStore({ notes: [] }),
|
|
42
|
+
})
|
|
43
|
+
await createNote({ tenantId: 't1', title: 'secret', body: '' }, ctx)
|
|
44
|
+
|
|
45
|
+
// Re-scope the SAME store to a different tenant — the real WHERE filter
|
|
46
|
+
// (eq tenantId) hides t1's row from t2.
|
|
47
|
+
const seenByT2 = await ctx.withTenant('t2', (c) => c.store.query(database.notes.descriptor))
|
|
48
|
+
expect(seenByT2).toHaveLength(0)
|
|
49
|
+
})
|
|
50
|
+
})
|