@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,21 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import type { AppContext } from '@voltro/runtime'
|
|
3
|
+
import { StorageService } from '@voltro/plugin-storage'
|
|
4
|
+
|
|
5
|
+
const execute = (input: { bytesBase64: string; contentType: string; filename: string }, ctx: AppContext) =>
|
|
6
|
+
Effect.gen(function* () {
|
|
7
|
+
const storage = yield* StorageService
|
|
8
|
+
const bytes = new Uint8Array(Buffer.from(input.bytesBase64, 'base64'))
|
|
9
|
+
const ref = yield* storage.put({
|
|
10
|
+
bytes,
|
|
11
|
+
contentType: input.contentType,
|
|
12
|
+
visibility: 'private',
|
|
13
|
+
ownerId: ctx.request.subject.id,
|
|
14
|
+
tenantId: ctx.request.subject.tenantId,
|
|
15
|
+
key: `documents/${ctx.request.subject.id ?? 'anon'}/${input.filename}`,
|
|
16
|
+
access: [{ roles: ['admin'] }, { tenant: true }],
|
|
17
|
+
})
|
|
18
|
+
return { id: ref.id }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export default execute
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// `storage.uploadDocument` — stores a PRIVATE document. The owner is
|
|
2
|
+
// always allowed; this policy additionally grants the `admin` role and
|
|
3
|
+
// same-tenant subjects. Fetch the bytes via the plugin's access-checked
|
|
4
|
+
// `storage.mintUrl` route, or grant a specific user/group from the
|
|
5
|
+
// dashboard's Storage tab.
|
|
6
|
+
|
|
7
|
+
import { Schema } from 'effect'
|
|
8
|
+
import { defineAction } from '@voltro/protocol'
|
|
9
|
+
|
|
10
|
+
export const uploadDocument = defineAction({
|
|
11
|
+
name: 'storage.uploadDocument',
|
|
12
|
+
input: Schema.Struct({
|
|
13
|
+
bytesBase64: Schema.String,
|
|
14
|
+
contentType: Schema.String,
|
|
15
|
+
filename: Schema.String,
|
|
16
|
+
}),
|
|
17
|
+
output: Schema.Struct({
|
|
18
|
+
id: Schema.String,
|
|
19
|
+
}),
|
|
20
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
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 (file metadata + grants persist there).
|
|
6
|
+
//
|
|
7
|
+
// This variant ships file storage out of the box via
|
|
8
|
+
// `@voltro/plugin-storage`. The `memory` provider keeps blobs in-process
|
|
9
|
+
// (zero-config, dev only) — swap to `'s3'` / `'minio'` (+ S3_* env) for
|
|
10
|
+
// real object storage, or `'filesystem'` for on-disk dev.
|
|
11
|
+
//
|
|
12
|
+
// - PUBLIC objects are served direct from the bucket/CDN (set
|
|
13
|
+
// `cdnBaseUrl`); the app's serve route is only a dev fallback.
|
|
14
|
+
// - PRIVATE objects are gated by an access policy (owner / roles /
|
|
15
|
+
// groups / scopes / tenant / api-key / password / custom guard) PLUS
|
|
16
|
+
// per-object grants you manage from the dashboard's Storage tab.
|
|
17
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
18
|
+
import { storagePlugin } from '@voltro/plugin-storage'
|
|
19
|
+
|
|
20
|
+
// Typed environment — declare your env once; it's validated at boot (fail-fast)
|
|
21
|
+
// and read on the server via `serverEnv` / `getSecret` from '@voltro/env/server'.
|
|
22
|
+
// `access` is required: 'public' (browser-safe) or 'secret' (server-only, never
|
|
23
|
+
// bundled). Run `voltro env` to see the manifest. See the Environment docs.
|
|
24
|
+
export const env = defineEnv({
|
|
25
|
+
LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
|
|
26
|
+
// Add your own, e.g.:
|
|
27
|
+
// STRIPE_KEY: envVar.string({ access: 'secret' }),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
type: 'api' as const,
|
|
32
|
+
name: '{{projectNamePascal}}{{appNamePascal}}',
|
|
33
|
+
store: 'memory' as const,
|
|
34
|
+
env,
|
|
35
|
+
plugins: [
|
|
36
|
+
storagePlugin({ provider: 'memory' }),
|
|
37
|
+
],
|
|
38
|
+
}
|
|
@@ -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,27 @@
|
|
|
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
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"test": "voltro test"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@voltro/cli": "0.1.0",
|
|
14
|
+
"@voltro/database": "0.1.0",
|
|
15
|
+
"@voltro/env": "0.1.0",
|
|
16
|
+
"@voltro/plugin-multitenancy": "0.1.0",
|
|
17
|
+
"@voltro/plugin-storage": "0.1.0",
|
|
18
|
+
"@voltro/protocol": "0.1.0",
|
|
19
|
+
"@voltro/runtime": "0.1.0",
|
|
20
|
+
"effect": "^3.21.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@voltro/testing": "0.1.0",
|
|
24
|
+
"typescript": "^5.7.0",
|
|
25
|
+
"vitest": "^3.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -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,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "api-backend-storage",
|
|
3
|
+
"kind": "api",
|
|
4
|
+
"summary": "Voltro backend with file storage wired: @voltro/plugin-storage with public (CDN-direct) + private (policy + per-object grants) objects, plus upload examples you browse/share from the dashboard. Same minimal base as api-backend.",
|
|
5
|
+
"tags": ["api", "backend", "storage", "files", "uploads", "starter"]
|
|
6
|
+
}
|
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# DEV-ONLY environment for the {{projectName}} api.
|
|
2
|
+
#
|
|
3
|
+
# `voltro dev` loads this file into the process environment at boot, BEFORE the
|
|
4
|
+
# env-validation gate and BEFORE plugins activate. That's how the governance
|
|
5
|
+
# plugin's field-encryption cipher resolves its key (it reads the Secrets-
|
|
6
|
+
# Resolver, i.e. process.env — a typed-env `default` would NOT reach it).
|
|
7
|
+
#
|
|
8
|
+
# ⚠️ The key below is a PUBLIC, DEV-ONLY placeholder shipped with the template.
|
|
9
|
+
# NEVER use it in production. Generate a real one and set it in your
|
|
10
|
+
# deployment's environment / secrets manager:
|
|
11
|
+
#
|
|
12
|
+
# openssl rand -hex 32
|
|
13
|
+
#
|
|
14
|
+
# Lose the key → lose the data (AES-256-GCM fails closed, never silent
|
|
15
|
+
# corruption). Rotating it makes every existing `.encrypted()` value
|
|
16
|
+
# unreadable.
|
|
17
|
+
VOLTRO_FIELD_ENCRYPTION_KEY=00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro backend scaffold (template: **api-data-advanced**).
|
|
4
|
+
|
|
5
|
+
A small **library/catalog** (authors + books) that closes the advanced
|
|
6
|
+
schema-DSL gaps — features that are documented but shown in no other
|
|
7
|
+
template: the `*.entity.ts` / `*.relations.ts` split with eager loading,
|
|
8
|
+
full-text search, `dbEnum`, array / generated / encrypted columns, and
|
|
9
|
+
declarative query caching.
|
|
10
|
+
|
|
11
|
+
## Boot
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm install # at the repo root
|
|
15
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
16
|
+
# → http://localhost:4000
|
|
17
|
+
# → ws://localhost:4000/ws
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Boots with **zero setup**: the `.encrypted()` `authors.bio` column needs an
|
|
21
|
+
AES-256-GCM key, and the shipped `.env` supplies a **DEV-ONLY** placeholder so
|
|
22
|
+
`voltro dev` comes up immediately. `voltro dev` then auto-migrates the schema
|
|
23
|
+
and runs the boot seed, so the catalog has two authors + three books right away.
|
|
24
|
+
|
|
25
|
+
> ⚠️ **Before production**, replace the key in `.env` with a real one and move
|
|
26
|
+
> it into your deployment's secret store — never ship the placeholder. Generate
|
|
27
|
+
> one with `openssl rand -hex 32`. **Lose the key → lose the ciphertext** (GCM
|
|
28
|
+
> fails closed); **rotating it** makes every existing `.encrypted()` value
|
|
29
|
+
> unreadable.
|
|
30
|
+
|
|
31
|
+
## Environment
|
|
32
|
+
|
|
33
|
+
| Var | Access | Why |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `VOLTRO_FIELD_ENCRYPTION_KEY` | secret | AES-256-GCM key for the `.encrypted()` `authors.bio` column (32-byte hex). `governancePlugin({ fieldEncryption: true })` resolves it through the **Secrets-Resolver** — i.e. the process environment, which the shipped `.env` populates (a `defineEnv` `default` would NOT reach the cipher). Boot fails loud if an `.encrypted()` column exists but the cipher can't resolve a key. |
|
|
36
|
+
| `LOG_LEVEL` | public | `debug \| info \| warn \| error` (default `info`). |
|
|
37
|
+
|
|
38
|
+
## What this demonstrates
|
|
39
|
+
|
|
40
|
+
| Feature | Where | Notes |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `*.entity.ts` / `*.relations.ts` split | `database/*.entity.ts`, `database/*.relations.ts` | One table per file; relations declared OUTSIDE the table descriptor. |
|
|
43
|
+
| `relations()` + eager `.with()` | `authors.relations.ts`, `books.relations.ts`, `queries/authors.withBooks.query.server.ts` | `database.authors.with({ books: true })` → each author carries a `books[]` array in ONE SQL roundtrip. |
|
|
44
|
+
| Full-text search | `books.entity.ts` (`.fullTextIndex('bookSearch', ['title','summary'])`) + `queries/books.search.query.server.ts` (`.matching('bookSearch', q)`) | One declaration, three backends (postgres tsvector, mysql/mariadb FULLTEXT, sqlite FTS5, mssql ranked-LIKE). |
|
|
45
|
+
| `dbEnum(...)` | `books.entity.ts` (`bookGenre`) | Native `CREATE TYPE … ENUM` on postgres; native `ENUM(...)` on mysql/mariadb; CHECK fallback elsewhere. |
|
|
46
|
+
| `array(text())` | `books.entity.ts` (`tags`) | Native `text[]` on postgres; JSON-string codec on other dialects — the API stays "a JS array". |
|
|
47
|
+
| `.generatedAs(expr, { stored })` | `books.entity.ts` (`slug`) | DB-computed, persisted, indexable column derived from `title`. Don't write it — the engine fills it. |
|
|
48
|
+
| `.encrypted()` | `authors.entity.ts` (`bio`) | Transparent AES-256-GCM at rest via `governancePlugin({ fieldEncryption: true })` in `app.config.ts`. |
|
|
49
|
+
| Declarative query caching | `queries/books.search.query.ts` (`cache: { ttl, swr, scope }`) | Server snapshot cache with auto-invalidation on writes to `books`; `scope: 'subject'` because the query is tenant-filtered. |
|
|
50
|
+
| Boot seed | `seeds/catalog.seed.ts` | Idempotent `upsertByUnique` authors + books. |
|
|
51
|
+
|
|
52
|
+
## Files
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
app.config.ts governancePlugin({ fieldEncryption: true })
|
|
56
|
+
database/
|
|
57
|
+
actors.entity.ts core audit-subject table
|
|
58
|
+
tenants.entity.ts core tenant boundary
|
|
59
|
+
authors.entity.ts name + .encrypted() bio
|
|
60
|
+
books.entity.ts dbEnum genre, array tags, generated slug, FTS index
|
|
61
|
+
authors.relations.ts author → many books
|
|
62
|
+
books.relations.ts book → one author
|
|
63
|
+
index.ts databaseHandle({...}) + relation registration
|
|
64
|
+
queries/
|
|
65
|
+
books.search.query.ts(.server) FTS via .matching(...) + cache
|
|
66
|
+
authors.withBooks.query.ts(.server) eager-load via .with({ books: true })
|
|
67
|
+
seeds/
|
|
68
|
+
catalog.seed.ts demo authors + books
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Try it
|
|
72
|
+
|
|
73
|
+
The dev AuthMiddleware resolves the `acme` tenant from the `x-tenant`
|
|
74
|
+
header (the seed writes under `acme`). Subscribe to the queries from a
|
|
75
|
+
web app via `useSubscription('app', 'books.search', { q: 'magic' })` and
|
|
76
|
+
`useSubscription('app', 'authors.withBooks')`, or invoke them over HTTP
|
|
77
|
+
through `POST /_voltro/inspect/invoke` for a one-shot snapshot.
|
|
78
|
+
|
|
79
|
+
## Switching to a real SQL store
|
|
80
|
+
|
|
81
|
+
`store: 'memory'` keeps boot zero-infra. Switch `app.config.ts` to
|
|
82
|
+
`store: 'postgres'` (and `docker compose up` a postgres) to see the
|
|
83
|
+
native DDL the migrator emits for each feature — `CREATE TYPE`,
|
|
84
|
+
`text[]`, the STORED generated column, and the tsvector + GIN FTS index.
|
|
85
|
+
The handler code is unchanged across every dialect.
|
|
86
|
+
|
|
87
|
+
Drop more `*.entity.ts` / `*.query.ts` / `*.mutation.ts` / `*.action.ts`
|
|
88
|
+
files anywhere in this tree — discovery is by file convention.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Backend app for the {{projectName}} project. Read by `voltro dev`.
|
|
2
|
+
//
|
|
3
|
+
// This template demonstrates the framework's ADVANCED schema-DSL surface
|
|
4
|
+
// on a small library/catalog domain (authors + books):
|
|
5
|
+
// - the *.entity.ts / *.relations.ts split (one table per file),
|
|
6
|
+
// - relations() + eager `.with({ books: true })` loading,
|
|
7
|
+
// - full-text search via `.fullTextIndex(...)` + `.matching(...)`,
|
|
8
|
+
// - `dbEnum(...)` native enum columns,
|
|
9
|
+
// - `array(text())` columns + a stored `.generatedAs(...)` column,
|
|
10
|
+
// - `.encrypted()` field encryption (AES-256-GCM at rest),
|
|
11
|
+
// - declarative query result caching (`cache` on `defineQuery`).
|
|
12
|
+
//
|
|
13
|
+
// `store: 'memory'` keeps boot zero-infra (no docker). The schema DSL is
|
|
14
|
+
// identical across every SQL backend — switch to `'postgres'` to see the
|
|
15
|
+
// native ENUM / tsvector-FTS / text[] DDL the migrator emits. On `memory`
|
|
16
|
+
// the same features run through the in-process store (FTS degrades to a
|
|
17
|
+
// substring scan, arrays round-trip as JS arrays, encryption still applies).
|
|
18
|
+
//
|
|
19
|
+
// `governancePlugin({ fieldEncryption: true })` registers the AES-256-GCM
|
|
20
|
+
// cipher that makes the `authors.bio` `.encrypted()` column work — without
|
|
21
|
+
// it, boot fails loud ("an `.encrypted()` column exists but no cipher is
|
|
22
|
+
// registered"). The key is read from the secret `VOLTRO_FIELD_ENCRYPTION_KEY`
|
|
23
|
+
// via the Secrets-Resolver (which reads the process environment). The shipped
|
|
24
|
+
// `.env` carries a DEV-ONLY key so the template boots out of the box — see the
|
|
25
|
+
// README to generate a real one for production. Lose the key → lose the
|
|
26
|
+
// ciphertext (GCM fails closed).
|
|
27
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
28
|
+
import { governancePlugin } from '@voltro/plugin-governance'
|
|
29
|
+
|
|
30
|
+
// Typed environment — declare your env once; it's validated at boot (fail-fast)
|
|
31
|
+
// and read on the server via `serverEnv` / `getSecret` from '@voltro/env/server'.
|
|
32
|
+
// `access` is required: 'public' (browser-safe) or 'secret' (server-only, never
|
|
33
|
+
// bundled). Run `voltro env` to see the manifest. See the Environment docs.
|
|
34
|
+
export const env = defineEnv({
|
|
35
|
+
LOG_LEVEL: envVar.enum(['debug', 'info', 'warn', 'error'], { access: 'public', default: 'info' }),
|
|
36
|
+
// The 32-byte hex key the field-encryption cipher uses. NO `default` here on
|
|
37
|
+
// purpose: governancePlugin resolves it through the Secrets-Resolver (which
|
|
38
|
+
// reads `process.env`), NOT this typed-env default — a default would satisfy
|
|
39
|
+
// the boot gate while the cipher still failed to resolve. The shipped `.env`
|
|
40
|
+
// supplies a DEV-ONLY value so `voltro dev` boots; for production generate a
|
|
41
|
+
// real key (`openssl rand -hex 32`) and set VOLTRO_FIELD_ENCRYPTION_KEY in
|
|
42
|
+
// the environment. Lose the key → lose the ciphertext (GCM fails closed).
|
|
43
|
+
VOLTRO_FIELD_ENCRYPTION_KEY: envVar.string({ access: 'secret' }),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
type: 'api' as const,
|
|
48
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
49
|
+
store: 'memory' as const,
|
|
50
|
+
env,
|
|
51
|
+
plugins: [
|
|
52
|
+
// Registers the cipher for `.encrypted()` columns. Key from the secret
|
|
53
|
+
// VOLTRO_FIELD_ENCRYPTION_KEY (or pass { fieldEncryption: { secretKey } }).
|
|
54
|
+
governancePlugin({ fieldEncryption: true }),
|
|
55
|
+
],
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Core table — the universal audit subject (whoever/whatever initiated a
|
|
2
|
+
// write: a user, a service account, an API key, or the system). Every
|
|
3
|
+
// `createdBy` / `updatedBy` / `deletedBy` references it, so the `audit()` /
|
|
4
|
+
// `tenant()` / `softDelete()` mixins require it to exist. Declared one-per-
|
|
5
|
+
// file with the `*.entity.ts` convention — the CLI auto-discovers it.
|
|
6
|
+
|
|
7
|
+
import { id, table, text, timestamp } from '@voltro/database'
|
|
8
|
+
|
|
9
|
+
export const actors = table('actors', {
|
|
10
|
+
id: id(),
|
|
11
|
+
kind: text().oneOf(['user', 'serviceAccount', 'apiKey', 'system']),
|
|
12
|
+
displayName: text().nullable(),
|
|
13
|
+
createdAt: timestamp().default('now'),
|
|
14
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Authors — demonstrates `.encrypted()` field encryption.
|
|
2
|
+
//
|
|
3
|
+
// `bio` is flagged `.encrypted()`: the store middleware transparently
|
|
4
|
+
// encrypts it on write (AES-256-GCM) and decrypts on read, so handlers
|
|
5
|
+
// always see plaintext while the column stores an opaque `enc:v1:…` string
|
|
6
|
+
// on disk on every dialect.
|
|
7
|
+
//
|
|
8
|
+
// ‼️ This column ONLY works because `app.config.ts` wires
|
|
9
|
+
// `governancePlugin({ fieldEncryption: true })`, which registers the
|
|
10
|
+
// cipher (key from the secret VOLTRO_FIELD_ENCRYPTION_KEY). Boot fails
|
|
11
|
+
// loud if an `.encrypted()` column exists but no cipher is registered.
|
|
12
|
+
//
|
|
13
|
+
// An `.encrypted()` column is ciphertext on disk — you CANNOT filter or
|
|
14
|
+
// sort by its plaintext in SQL. Encrypt only what you read back whole
|
|
15
|
+
// (PII, tokens, notes). A book search never touches `bio`, so this is safe.
|
|
16
|
+
|
|
17
|
+
import { id, table, text } from '@voltro/database'
|
|
18
|
+
import { tenant } from '@voltro/plugin-multitenancy/mixin'
|
|
19
|
+
|
|
20
|
+
export const authors = table('authors', {
|
|
21
|
+
id: id({ prefix: 'author' }),
|
|
22
|
+
name: text(),
|
|
23
|
+
// Encrypted at rest. Requires governancePlugin({ fieldEncryption: true }).
|
|
24
|
+
bio: text().encrypted().nullable(),
|
|
25
|
+
})
|
|
26
|
+
// tenant() pulls audit() transitively → adds tenantId + createdAt /
|
|
27
|
+
// updatedAt / createdBy / updatedBy (auto-stamped by the runtime).
|
|
28
|
+
.with(tenant())
|
|
29
|
+
// Opt into the matcher engine so subscriptions react to writes.
|
|
30
|
+
.reactive()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Relations are declared OUTSIDE the table descriptor, in a `*.relations.ts`
|
|
2
|
+
// file (convention). The framework registers them at boot; the query
|
|
3
|
+
// builder's `.with({ books: true })` chain uses them to eager-load.
|
|
4
|
+
//
|
|
5
|
+
// An author has MANY books. The `foreignKey` auto-derives because exactly
|
|
6
|
+
// one `reference()` column on `books` (`authorId`) points back at `authors`
|
|
7
|
+
// — no explicit override needed.
|
|
8
|
+
|
|
9
|
+
import { relations } from '@voltro/database'
|
|
10
|
+
import { authors } from './authors.entity'
|
|
11
|
+
import { books } from './books.entity'
|
|
12
|
+
|
|
13
|
+
export const authorsRelations = relations(authors, ({ many }) => ({
|
|
14
|
+
books: many(books),
|
|
15
|
+
}))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Books — the centrepiece of the advanced schema-DSL tour. One table,
|
|
2
|
+
// five specialized column features:
|
|
3
|
+
//
|
|
4
|
+
// - `authorId` → reference(() => authors) (FK + auto B-tree index)
|
|
5
|
+
// - `genre` → dbEnum(...).column() (native ENUM type)
|
|
6
|
+
// - `tags` → array(text()) (native text[] on postgres,
|
|
7
|
+
// JSON fallback elsewhere)
|
|
8
|
+
// - `slug` → text().generatedAs(..., { stored: true })
|
|
9
|
+
// (DB-computed, persisted, indexable)
|
|
10
|
+
// - `.fullTextIndex('bookSearch', [...])` (FTS over title + summary;
|
|
11
|
+
// query with `.matching(...)`)
|
|
12
|
+
|
|
13
|
+
import { array, dbEnum, id, reference, table, text } from '@voltro/database'
|
|
14
|
+
import { tenant } from '@voltro/plugin-multitenancy/mixin'
|
|
15
|
+
import { authors } from './authors.entity'
|
|
16
|
+
|
|
17
|
+
// A native ENUM type. Declared once (module-level) so it's a reusable handle:
|
|
18
|
+
// - postgres → CREATE TYPE book_genre AS ENUM (...). ADD VALUE is O(1).
|
|
19
|
+
// - mysql / mariadb → native ENUM('fiction', ...) column.
|
|
20
|
+
// - mssql / sqlite → NVARCHAR / TEXT + CHECK fallback.
|
|
21
|
+
// Distinct from `text().oneOf([...])`, which is a CROSS-DIALECT CHECK on every
|
|
22
|
+
// backend — reach for `dbEnum` on postgres-primary apps for the cheap
|
|
23
|
+
// ADD-VALUE migration. The `as const` is required for the literal-union type.
|
|
24
|
+
export const bookGenre = dbEnum('book_genre', [
|
|
25
|
+
'fiction',
|
|
26
|
+
'nonfiction',
|
|
27
|
+
'fantasy',
|
|
28
|
+
'sciFi',
|
|
29
|
+
'mystery',
|
|
30
|
+
'biography',
|
|
31
|
+
] as const)
|
|
32
|
+
|
|
33
|
+
export const books = table('books', {
|
|
34
|
+
id: id({ prefix: 'book' }),
|
|
35
|
+
authorId: reference(() => authors), // FK → authors, auto-indexed
|
|
36
|
+
title: text(),
|
|
37
|
+
summary: text(),
|
|
38
|
+
// Native ENUM column, defaulting to 'fiction'.
|
|
39
|
+
genre: bookGenre.column().default('fiction'),
|
|
40
|
+
// Array column → `text[]` on postgres; JSON-string codec elsewhere. The
|
|
41
|
+
// user-facing API stays "I get back a JS string array" on every dialect.
|
|
42
|
+
tags: array(text()).default([]),
|
|
43
|
+
// DB-computed STORED column — a URL-friendly slug derived from `title` in
|
|
44
|
+
// the DB engine on INSERT + UPDATE. `stored: true` persists it on disk so
|
|
45
|
+
// it's indexable. Same-row columns only (it can't see subject/tenant —
|
|
46
|
+
// that's what `.computed(row => ...)` is for). The expression is raw SQL
|
|
47
|
+
// emitted verbatim, so it must stay portable (double-quoted identifier).
|
|
48
|
+
slug: text().generatedAs(`lower("title")`, { stored: true }),
|
|
49
|
+
})
|
|
50
|
+
// Full-text search index over title + summary. One declaration, three
|
|
51
|
+
// backends (postgres tsvector+GIN, mysql/mariadb FULLTEXT, sqlite FTS5;
|
|
52
|
+
// mssql ranked-LIKE fallback). Query it with `.matching('bookSearch', q)`.
|
|
53
|
+
.fullTextIndex('bookSearch', ['title', 'summary'], {
|
|
54
|
+
config: 'english',
|
|
55
|
+
weights: { title: 'A', summary: 'B' },
|
|
56
|
+
})
|
|
57
|
+
// tenant() pulls audit() transitively (tenantId + audit columns).
|
|
58
|
+
.with(tenant())
|
|
59
|
+
.reactive()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// A book belongs to ONE author. The inverse of authors→books.
|
|
2
|
+
// `one(authors)` resolves the `authorId` FK automatically.
|
|
3
|
+
|
|
4
|
+
import { relations } from '@voltro/database'
|
|
5
|
+
import { authors } from './authors.entity'
|
|
6
|
+
import { books } from './books.entity'
|
|
7
|
+
|
|
8
|
+
export const booksRelations = relations(books, ({ one }) => ({
|
|
9
|
+
author: one(authors),
|
|
10
|
+
}))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The database handle + barrel. Imports every entity + exports
|
|
2
|
+
// `databaseHandle({...})`. Tables are auto-discovered by the CLI from the
|
|
3
|
+
// `*.entity.ts` files; the handle is what server code queries through
|
|
4
|
+
// (`database.books.matching(...)`, `database.authors.with({ books: true })`).
|
|
5
|
+
//
|
|
6
|
+
// Importing the `*.relations.ts` files here is what registers the relations
|
|
7
|
+
// at boot so the eager-load `.with(...)` walker can resolve them.
|
|
8
|
+
|
|
9
|
+
import { databaseHandle, type InferRow } from '@voltro/database'
|
|
10
|
+
import { actors } from './actors.entity'
|
|
11
|
+
import { tenants } from './tenants.entity'
|
|
12
|
+
import { authors } from './authors.entity'
|
|
13
|
+
import { books } from './books.entity'
|
|
14
|
+
|
|
15
|
+
// Register relations (side-effecting imports).
|
|
16
|
+
import './authors.relations'
|
|
17
|
+
import './books.relations'
|
|
18
|
+
|
|
19
|
+
export { actors, tenants, authors, books }
|
|
20
|
+
export type Author = InferRow<typeof authors>
|
|
21
|
+
export type Book = InferRow<typeof books>
|
|
22
|
+
|
|
23
|
+
export const database = databaseHandle({ actors, tenants, authors, books })
|