@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,607 @@
|
|
|
1
|
+
# Schema-driven UI
|
|
2
|
+
|
|
3
|
+
> Project the typed descriptor graph into UI — forms, tables, pickers, and reactive components, all bound to a descriptor with near-zero glue.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!-- source: en/ui/overview.md -->
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
_Project the typed descriptor graph into UI — forms, tables, pickers, and reactive components, all bound to a descriptor with near-zero glue._
|
|
13
|
+
|
|
14
|
+
You already own a typed schema on **both** sides of the wire: the entity DSL
|
|
15
|
+
(`*.entity.ts`) and the descriptor schemas (`defineQuery` / `defineMutation`
|
|
16
|
+
carry `effect/Schema` for input, output, and `target`). Schema-driven UI
|
|
17
|
+
**projects that into the frontend** — so a form, a table, or a picker is a
|
|
18
|
+
*binding to a descriptor* the framework already has, not hand-written glue.
|
|
19
|
+
|
|
20
|
+
Here's that idea live — a form bound to a mutation and a table bound to a query,
|
|
21
|
+
sharing one reactive backend. Add a row and it appears instantly, pushed from
|
|
22
|
+
the server (it's your own throwaway sandbox; it resets on refresh):
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
<AutoForm api="app" mutation="todos.create" submitLabel="Add todo" />
|
|
26
|
+
<DataTable api="app" query="todos.list" />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Why this isn't "yet another admin generator"
|
|
30
|
+
|
|
31
|
+
Auto-generated CRUD UIs fail because they generate against a *dead* REST/SQL
|
|
32
|
+
schema and hit a wall the moment you need anything custom. Voltro's version is
|
|
33
|
+
different on one axis: the generated UI binds to a **reactive, auto-optimistic
|
|
34
|
+
backend**. A `<DataTable>` is not a dead grid you refetch — it's a live
|
|
35
|
+
subscription that updates on any write, with optimistic patches derived from
|
|
36
|
+
the mutation descriptor's `target`, for free. The generation is the *delivery*;
|
|
37
|
+
the reactivity is the *value*.
|
|
38
|
+
|
|
39
|
+
## Binding symmetry — everything binds to a descriptor
|
|
40
|
+
|
|
41
|
+
| UI piece | binds to | gives you |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| a **form** | a **mutation** (`todos.create`) | fields from its input Schema, validation, submit, op-correct optimistic |
|
|
44
|
+
| a **field / picker** | a **query** (`users.search`) | live options, debounce, pagination |
|
|
45
|
+
| a **table** | a **query** (`todos.list`) | live rows, sort, pagination, row-action mutations |
|
|
46
|
+
| a **filter panel** | a **query**'s input | faceted controls + a live result count |
|
|
47
|
+
| a **detail view** | a **query** + relations | a live record + its eager-loaded relations |
|
|
48
|
+
|
|
49
|
+
There's no new data layer to learn. Customization is only ever choosing *how* a
|
|
50
|
+
binding renders — never re-plumbing the binding.
|
|
51
|
+
|
|
52
|
+
## The customization ladder (never lose the binding)
|
|
53
|
+
|
|
54
|
+
This is where 99% of schema→UI generators die: they nail the happy path, then
|
|
55
|
+
force a config DSL until it's worse than hand-written JSX — or you eject and
|
|
56
|
+
lose everything. The one rule that avoids both:
|
|
57
|
+
|
|
58
|
+
> Customization is a ladder of ever-smaller overrides, and on **no rung do you
|
|
59
|
+
> lose the binding** (schema validation + mutation submit + optimistic; for a
|
|
60
|
+
> picker, the live query). Eject from the *rendering* is never eject from the
|
|
61
|
+
> *binding*.
|
|
62
|
+
|
|
63
|
+
| Rung | How | You lose |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| 0 — schema annotation | `widget` / `label` on the field | nothing (zero form code) |
|
|
66
|
+
| 1 — per-field render-prop | `<Field name="x">{f => <MyWidget .../>}</Field>` | nothing — state/validation/submit stay |
|
|
67
|
+
| 2 — widget registry | map a widget-kind → component app-wide | nothing |
|
|
68
|
+
| 3 — layout override | arrange `<Field/>`s in your own JSX | nothing |
|
|
69
|
+
| eject — headless | `useFormBinding` / `useDataTable` + your JSX | only the auto-rendering — the binding stays |
|
|
70
|
+
|
|
71
|
+
`<AutoForm>` / `<DataTable>` are sugar over a **headless core**
|
|
72
|
+
(`useFormBinding` / `useDataTable`) — which is exactly why ejecting from the
|
|
73
|
+
rendering keeps everything underneath.
|
|
74
|
+
|
|
75
|
+
## Localizing the built-in strings
|
|
76
|
+
|
|
77
|
+
The components render a handful of user-facing labels — `Clear`, `Actions`,
|
|
78
|
+
`Search…`, the workflow status words (`Running` / `Succeeded` / …), presence's
|
|
79
|
+
"X is editing" sentence, and so on — with **English defaults**. Localize them
|
|
80
|
+
all at once by wrapping the app in `UiStringsProvider`, instead of threading a
|
|
81
|
+
prop through every component:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { UiStringsProvider } from '@voltro/web'
|
|
85
|
+
|
|
86
|
+
<UiStringsProvider
|
|
87
|
+
strings={{
|
|
88
|
+
filters: { clear: 'Zurücksetzen', resultCount: (n) => `${n} Ergebnisse` },
|
|
89
|
+
dataTable: { actions: 'Aktionen' },
|
|
90
|
+
workflow: { status: { running: 'Läuft' } },
|
|
91
|
+
presence: { editingOne: (name, verb) => `${name} ist am ${verb}` },
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<App />
|
|
95
|
+
</UiStringsProvider>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Overrides **deep-merge** onto the defaults — supply only the sections and keys
|
|
99
|
+
you change; everything else stays English. Interpolated strings (counts, names)
|
|
100
|
+
are functions so a locale can reorder or pluralize. Providers nest (an inner one
|
|
101
|
+
merges onto the outer), and a per-component prop (e.g. `<AgentChat placeholder>`)
|
|
102
|
+
still wins over the context for a one-off tweak. `useUiStrings()` reads the
|
|
103
|
+
active strings; `defaultUiStrings` is the English baseline.
|
|
104
|
+
|
|
105
|
+
## Packaging
|
|
106
|
+
|
|
107
|
+
Three layers, all re-exported by `@voltro/web` so apps import everything from
|
|
108
|
+
the framework's web entry:
|
|
109
|
+
|
|
110
|
+
- **Headless bindings** (`useFormBinding`, `useDataTable`, `useQueryField`, …)
|
|
111
|
+
→ `@voltro/client` — pure logic, no kit dependency; works with any
|
|
112
|
+
presentation (incl. an MUI/Mantine adapter).
|
|
113
|
+
- **The widget seam + components** (`<AutoForm>` / `<Field>` / `<DataTable>` /
|
|
114
|
+
`<AsyncSelect>` + the registry + accessible HTML defaults) → `@voltro/ui`.
|
|
115
|
+
- **The styled kit** (Tailwind `shadcnWidgets` + tokens) → `@voltro/ui-shadcn`,
|
|
116
|
+
which plugs into the seam via `<WidgetRegistryProvider widgets={shadcnWidgets}>`.
|
|
117
|
+
|
|
118
|
+
## In this section
|
|
119
|
+
|
|
120
|
+
- **[Forms & tables](./forms-and-tables)** — `<AutoForm>`, `<DataTable>`,
|
|
121
|
+
query-bound pickers, the widget seam, skeletons, filters.
|
|
122
|
+
- **[Reactive components](./reactive-components)** — workflow progress, presence
|
|
123
|
+
/ multiplayer, AI chat — UI over the framework's durable + reactive backend.
|
|
124
|
+
- **[Client utilities](./client-utilities/use-can)** — `useCan`, `useDerived`,
|
|
125
|
+
`useProvenance`, `useUndo`, `usePreview`, async validation, `<RecordView>`,
|
|
126
|
+
the typed analytics catalog, the offline outbox, windowed subscriptions.
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
<!-- source: en/ui/forms-and-tables.md -->
|
|
133
|
+
## Forms & tables
|
|
134
|
+
|
|
135
|
+
_<AutoForm> binds to a mutation, <DataTable> to a query — fields and columns from the Schema, validation and live updates for free._
|
|
136
|
+
|
|
137
|
+
Import components from `@voltro/web`, headless hooks from `@voltro/client`.
|
|
138
|
+
|
|
139
|
+
## `<AutoForm>` — bind to a MUTATION
|
|
140
|
+
|
|
141
|
+
A form binds to **one mutation's** input `Schema`. This cleanly resolves
|
|
142
|
+
create-vs-update: they're *different mutations with different input schemas*, so
|
|
143
|
+
they're naturally different forms — no "CRUD mode" switch.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { AutoForm } from '@voltro/web'
|
|
147
|
+
|
|
148
|
+
// fields from the mutation's input schema; client+server share the schema;
|
|
149
|
+
// submits via the mutation with op-correct optimistic (insert prepends, etc.)
|
|
150
|
+
<AutoForm api="app" mutation="todos.create" onSuccess={(t) => navigate(`/todos/${t.id}`)} />
|
|
151
|
+
|
|
152
|
+
// update: bind the update mutation + pass the existing row
|
|
153
|
+
<AutoForm api="app" mutation="todos.update" defaults={row} />
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Here it is **live** — a real `<AutoForm>` bound to a throwaway demo API (your
|
|
157
|
+
own sandbox; data resets on refresh). Add a todo and watch it appear in the
|
|
158
|
+
table below, pushed from the server:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<AutoForm api="docs" mutation="todos.create" submitLabel="Add todo" />
|
|
162
|
+
<DataTable api="docs" query="todos.list" />
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
A combined create-or-edit screen is a three-line wrapper:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
{row
|
|
169
|
+
? <AutoForm api="app" mutation="todos.update" defaults={row} />
|
|
170
|
+
: <AutoForm api="app" mutation="todos.create" />}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### The customization ladder
|
|
174
|
+
|
|
175
|
+
- **Rung 0 — zero config.** Fields render from the schema: a `Schema.Literal`
|
|
176
|
+
union → `select`; string → text; boolean → checkbox; `Date` → date; nested
|
|
177
|
+
object → a `custom` placeholder asking for a render-prop.
|
|
178
|
+
- **Rung 1 — one custom widget** via a `<Field>` render-prop:
|
|
179
|
+
```tsx
|
|
180
|
+
import { AutoForm, Field, AsyncSelect } from '@voltro/web'
|
|
181
|
+
|
|
182
|
+
<AutoForm api="app" mutation="todos.create">
|
|
183
|
+
{() => (
|
|
184
|
+
<>
|
|
185
|
+
<Field name="title" /> {/* auto */}
|
|
186
|
+
<Field name="assigneeId">
|
|
187
|
+
{(w) => <AsyncSelect {...w} api="app" source="users.search" />} {/* live picker */}
|
|
188
|
+
</Field>
|
|
189
|
+
</>
|
|
190
|
+
)}
|
|
191
|
+
</AutoForm>
|
|
192
|
+
```
|
|
193
|
+
- **Rung 2 — swap a widget kind app-wide** via the registry:
|
|
194
|
+
```tsx
|
|
195
|
+
import { WidgetRegistryProvider } from '@voltro/web'
|
|
196
|
+
import { shadcnWidgets } from '@voltro/ui-shadcn'
|
|
197
|
+
<WidgetRegistryProvider widgets={shadcnWidgets}>{/* AutoForms render styled */}</WidgetRegistryProvider>
|
|
198
|
+
```
|
|
199
|
+
- **Rung 3 — own the layout.** Pass children + arrange `<Field>`s (columns,
|
|
200
|
+
sections, tabs); each still auto-renders.
|
|
201
|
+
- **Eject (headless).** `useFormBinding('app', 'todos.create')` returns
|
|
202
|
+
`{ fields, values, errors, isValid, pending, setValue, submit, reset }` for
|
|
203
|
+
100% custom JSX — the binding stays.
|
|
204
|
+
|
|
205
|
+
## `<DataTable>` — bind to a QUERY
|
|
206
|
+
|
|
207
|
+
Columns come from the query's output `Schema`; rows are a LIVE subscription
|
|
208
|
+
(update on any write, no refetch); headers sort client-side.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { DataTable } from '@voltro/web'
|
|
212
|
+
|
|
213
|
+
<DataTable
|
|
214
|
+
api="app"
|
|
215
|
+
query="todos.list"
|
|
216
|
+
rowActions={(row) => <button onClick={() => del.mutate({ id: row.id })}>Delete</button>}
|
|
217
|
+
/>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Live** — the same demo todos, with per-row actions wired to the toggle +
|
|
221
|
+
delete mutations. Open this page in a second tab and toggle or delete a row:
|
|
222
|
+
the other tab updates instantly, no refetch — that's the reactive query, not a
|
|
223
|
+
poll.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<DataTable
|
|
227
|
+
api="docs"
|
|
228
|
+
query="todos.list"
|
|
229
|
+
rowActions={(row) => (
|
|
230
|
+
<>
|
|
231
|
+
<button onClick={() => toggle.mutate({ id: row.id, done: !row.done })}>Done</button>
|
|
232
|
+
<button onClick={() => del.mutate({ id: row.id })}>Delete</button>
|
|
233
|
+
</>
|
|
234
|
+
)}
|
|
235
|
+
/>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Headless eject: `useDataTable('app', 'todos.list', { initialSort, pageSize })`
|
|
239
|
+
→ `{ columns, rows, loading, error, sort, toggleSort, loadMore, hasMore }`.
|
|
240
|
+
`pageSize` opts into LIVE grow-the-window pagination (a "Load more" button; the
|
|
241
|
+
query applies `.limit(input.limit)`) — rows stay reactive as the window grows.
|
|
242
|
+
`renderCell` / `rowKey` / `emptyText` / `loadMoreText` cover the common overrides.
|
|
243
|
+
|
|
244
|
+
## Query-bound pickers
|
|
245
|
+
|
|
246
|
+
A picker binds to a query the way a form binds to a mutation. `<AsyncSelect>`
|
|
247
|
+
(or the headless `useQueryField`) drives a debounced, LIVE typeahead off a
|
|
248
|
+
source query — the option list updates reactively when the rows change:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
const picker = useQueryField('app', 'users.search', { labelField: 'name', valueField: 'id' })
|
|
252
|
+
// → { options, loading, term, search(term) }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Three cases need zero config because the schema says enough: an `enum` →
|
|
256
|
+
`select` (the options ARE the union); a `reference()` column → defaults to
|
|
257
|
+
`async-select` on the conventional `users.search`; a create-on-the-fly combobox
|
|
258
|
+
= a picker bound to a query (search) AND a mutation (`tags.create`).
|
|
259
|
+
|
|
260
|
+
## Skeletons — shape-matched loading, no CLS
|
|
261
|
+
|
|
262
|
+
While a bound query/mutation loads, render a placeholder shaped like the real
|
|
263
|
+
thing — derived from the same descriptor, so the swap causes zero layout shift.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { FormSkeleton, TableSkeleton } from '@voltro/web'
|
|
267
|
+
<FormSkeleton api="app" mutation="todos.create" /> {/* right field count */}
|
|
268
|
+
<TableSkeleton api="app" query="todos.list" rows={8} /> {/* right columns */}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Filters — the read-side AutoForm
|
|
272
|
+
|
|
273
|
+
A query's INPUT Schema IS the filter spec. `<QueryFilters>` generates a control
|
|
274
|
+
per filterable input field; values map to the query input; the result + count
|
|
275
|
+
update LIVE (the query is a subscription).
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
import { QueryFilters } from '@voltro/web'
|
|
279
|
+
|
|
280
|
+
<QueryFilters api="app" query="todos.list">
|
|
281
|
+
{(f) => <DataTable api="app" query="todos.list" /* … */ />}
|
|
282
|
+
</QueryFilters>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Headless core: `useQueryFilters('app', 'todos.list')` →
|
|
286
|
+
`{ filters, values, setFilter, clear, rows, count, loading }`.
|
|
287
|
+
|
|
288
|
+
## The schema rule (load-bearing)
|
|
289
|
+
|
|
290
|
+
A descriptor's `input` MUST be `effect/Schema` — that's what the form, the wire,
|
|
291
|
+
and the capability map introspect. **Never put Zod (or another validator) in a
|
|
292
|
+
`*.mutation.ts` / `*.query.ts` descriptor.** Client-side EXTRA validation
|
|
293
|
+
(beyond the descriptor) may use any Standard-Schema validator.
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
<!-- source: en/ui/reactive-components.md -->
|
|
300
|
+
## Reactive components
|
|
301
|
+
|
|
302
|
+
_Drop-in UI for durable workflows, presence/multiplayer, and AI chat — bound to backend primitives the framework already has, never polling._
|
|
303
|
+
|
|
304
|
+
UI over the framework's durable + reactive backend. Each has a headless hook
|
|
305
|
+
(the escape hatch) + a styled component. Components from `@voltro/web`, hooks
|
|
306
|
+
from `@voltro/client`.
|
|
307
|
+
|
|
308
|
+
## Workflow-aware UI
|
|
309
|
+
|
|
310
|
+
No other framework has a UI vocabulary for durable execution. Bind to a workflow
|
|
311
|
+
run id; the timeline + wait-states push reactively (no status-poll endpoint to
|
|
312
|
+
wire). `useWorkflowRunState('app', runId)` is the headless aggregator over the
|
|
313
|
+
EXISTING run/steps/events subscriptions → `{ status, steps, currentStep,
|
|
314
|
+
waitingFor, error, traceId, cancel, resume, signal, update }`.
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
import { WorkflowProgress, WorkflowStatusBadge, ApprovalControls } from '@voltro/web'
|
|
318
|
+
import { useWorkflowRunState } from '@voltro/client'
|
|
319
|
+
|
|
320
|
+
const run = useWorkflowRunState('app', runId)
|
|
321
|
+
<WorkflowProgress api="app" runId={runId} /> {/* live step timeline */}
|
|
322
|
+
<WorkflowStatusBadge api="app" runId={runId} /> {/* status + failing step + trace hint */}
|
|
323
|
+
|
|
324
|
+
// Approve/Reject a parked awaitSignal — renders ONLY while the run waits on it:
|
|
325
|
+
<ApprovalControls
|
|
326
|
+
when={run.waitingFor?.name === 'approval'}
|
|
327
|
+
onApprove={() => run.signal('approval', { approved: true })}
|
|
328
|
+
onReject={() => run.signal('approval', { approved: false })}
|
|
329
|
+
/>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Signal/update payloads are schema-typed (declared on the workflow's `messages`
|
|
333
|
+
metadata) — don't pass stringly-typed blobs.
|
|
334
|
+
|
|
335
|
+
## Reactive multiplayer
|
|
336
|
+
|
|
337
|
+
`@voltro/plugin-presence` ships ephemeral "who's online" per channel. Combined
|
|
338
|
+
with the reactive engine, live-collaboration primitives become drop-ins —
|
|
339
|
+
prop-driven so the kit stays plugin-agnostic. Feed them the roster from
|
|
340
|
+
`usePresence` (`@voltro/plugin-presence/web`):
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import { PresenceAvatars, EditingIndicator } from '@voltro/web'
|
|
344
|
+
import { usePresence, useTyping } from '@voltro/plugin-presence/web'
|
|
345
|
+
|
|
346
|
+
const members = usePresence(`doc:${docId}`, { meta: { name, field: 'title' } })
|
|
347
|
+
<PresenceAvatars members={members} max={5} />
|
|
348
|
+
<EditingIndicator members={members} selfKey={myId} field="title" />
|
|
349
|
+
|
|
350
|
+
const typing = useTyping(`thread:${threadId}`, { selfKey: myId }) // { active, isTyping, start, stop }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Live cursors ride an ephemeral broadcast lane, NEVER the durable presence table
|
|
354
|
+
— don't write cursor positions at 60fps to a swept table.
|
|
355
|
+
|
|
356
|
+
## AI chat — `<AgentChat>`
|
|
357
|
+
|
|
358
|
+
A complete chat over an agent's synthesized `*.send` + `*.messages` — durable +
|
|
359
|
+
reactive, so reconnect/reload-surviving for free. Render from the persisted
|
|
360
|
+
`parts` (text / reasoning / tool / source / file) so reload === live.
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
import { AgentChat, AgentStream, AppAgent } from '@voltro/web'
|
|
364
|
+
import { useAgentChat } from '@voltro/client'
|
|
365
|
+
|
|
366
|
+
const threadId = useRef(`thread_${crypto.randomUUID().replace(/-/g, '')}`).current
|
|
367
|
+
<AgentChat api="app" agent="support" threadId={threadId} />
|
|
368
|
+
|
|
369
|
+
// Headless — render your own bubbles from chat.messages[i].parts:
|
|
370
|
+
<AgentStream api="app" agent="support" threadId={threadId}>
|
|
371
|
+
{(chat) => chat.messages.map((m) => /* … */)}
|
|
372
|
+
</AgentStream>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`<AppAgent>` is the end-user "do it for me" agent: `<AgentChat>` + a tools
|
|
376
|
+
disclosure (it acts AS the logged-in subject, so its ceiling is the subject's
|
|
377
|
+
permissions). Don't hand-wire `useResumableAgentStream` + a bubble list for a
|
|
378
|
+
standard chat — `<AgentChat>` is the supported path; reach for the resumable
|
|
379
|
+
hook directly only for a TRANSIENT (non-persisted) run over a `*.stream.ts`.
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
<!-- source: en/ui/client-utilities/use-can.md -->
|
|
386
|
+
## useCan
|
|
387
|
+
|
|
388
|
+
_Gate UI on the same scope strings the server enforces — reactive permissions._
|
|
389
|
+
|
|
390
|
+
Gate UI on the SAME scope strings the server's `requireScope` checks — one
|
|
391
|
+
source of truth, no separate frontend permission list. Feed the subject's scopes
|
|
392
|
+
(from your session query) to `<PermissionProvider>`; `useCan` reads them.
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
import { PermissionProvider, useCan } from '@voltro/web'
|
|
396
|
+
|
|
397
|
+
<PermissionProvider scopes={session.scopes}>…</PermissionProvider>
|
|
398
|
+
const canDelete = useCan('todos:delete') // ADMIN bypasses; deny-by-default
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
UX only — the server still enforces. Pairs with `<AutoForm>` / `<DataTable>` to
|
|
402
|
+
auto-hide actions the subject can't perform.
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
<!-- source: en/ui/client-utilities/use-derived.md -->
|
|
409
|
+
## useDerived
|
|
410
|
+
|
|
411
|
+
_A value computed from reactive sources, recomputed only when a source changes._
|
|
412
|
+
|
|
413
|
+
A value computed from reactive sources (subscription snapshots), recomputed only
|
|
414
|
+
when a source changes, same reference otherwise — the formula layer.
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
const total = useDerived({ cart: cart ?? [], tax }, ({ cart, tax }) =>
|
|
418
|
+
cart.reduce((s, i) => s + i.price, 0) * (1 + tax))
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
A derived value that needs the DB is a computed *query* (server), not this.
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
<!-- source: en/ui/client-utilities/use-preview.md -->
|
|
428
|
+
## usePreview
|
|
429
|
+
|
|
430
|
+
_Mutation dry-run — the {old,new} diff in a rolled-back transaction before commit._
|
|
431
|
+
|
|
432
|
+
"What will this change?" The app exposes a preview action that runs the real
|
|
433
|
+
handler in a transaction the framework **rolls back** (`previewInRollback` +
|
|
434
|
+
`buildPreview` from `@voltro/runtime`); `usePreview` returns the `{old,new}` diff
|
|
435
|
+
before commit. Actions (external I/O) aren't previewable — same boundary as undo.
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
const p = usePreview('app', 'todos.update.preview')
|
|
439
|
+
const diff = await p.preview({ id, title }) // { rows, summary } — nothing committed
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
<!-- source: en/ui/client-utilities/use-async-validation.md -->
|
|
447
|
+
## useAsyncValidation
|
|
448
|
+
|
|
449
|
+
_DB-backed field validation as the user types — debounced, typed, over a query._
|
|
450
|
+
|
|
451
|
+
Async checks that need the DB (uniqueness, cross-row rules) run reactively as the
|
|
452
|
+
user types, debounced + typed, over a query — the same query gates submit.
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
const email = useAsyncValidation('app', 'users.emailAvailable', value, {
|
|
456
|
+
input: (v) => ({ email: v }),
|
|
457
|
+
interpret: (r) => ({ valid: r.available, message: r.available ? undefined : 'Email taken' }),
|
|
458
|
+
}) // email.status: 'idle' | 'checking' | 'valid' | 'invalid'
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
<!-- source: en/ui/client-utilities/record-view.md -->
|
|
466
|
+
## RecordView
|
|
467
|
+
|
|
468
|
+
_Live read-only detail view for one record plus its eager-loaded relations._
|
|
469
|
+
|
|
470
|
+
The read counterpart to `<AutoForm>`: a live detail view for one record + its
|
|
471
|
+
eager-loaded relations (scalars → a definition list; arrays → nested tables).
|
|
472
|
+
`useRecord('app', 'users.get', { id })` is the headless core.
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
import { RecordView } from '@voltro/web'
|
|
476
|
+
<RecordView api="app" query="users.get" input={{ id }} />
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
<!-- source: en/ui/client-utilities/use-provenance.md -->
|
|
484
|
+
## useProvenance
|
|
485
|
+
|
|
486
|
+
_Last-write attribution (actor + traceId) for a (table, id, column)._
|
|
487
|
+
|
|
488
|
+
"Why is this value here?" Last-write attribution (audit actor + traceId) for a
|
|
489
|
+
`(table, id, column)`, over `/_voltro/inspect/provenance`. Out-of-band /
|
|
490
|
+
un-audited rows report no attribution (never a fabricated actor); computed
|
|
491
|
+
values are set-level. Available in dev.
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
const prov = useProvenance('app', 'invoices', invoiceId, 'status')
|
|
495
|
+
// → { value, lastWrite: { actor, at, traceId }, attribution, found }
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
<!-- source: en/ui/client-utilities/use-undo.md -->
|
|
503
|
+
## useUndoLog
|
|
504
|
+
|
|
505
|
+
_Universal end-user undo + redo over mutations, server-persisted via synthesized inverses._
|
|
506
|
+
|
|
507
|
+
End-user Ctrl-Z over mutations, **server-backed**. When undo capture is on, the
|
|
508
|
+
framework records each mutation's change-set (the CDC `{old,new}` per row) into
|
|
509
|
+
`_voltro_undo_log` — inside the mutation's own transaction, so a rolled-back
|
|
510
|
+
write leaves no entry. Three built-in rpc procedures expose it, typed
|
|
511
|
+
end-to-end via codegen:
|
|
512
|
+
|
|
513
|
+
- `__voltro.undo.log` — a reactive, **subject-scoped** feed of your recent
|
|
514
|
+
undoable actions (newest first).
|
|
515
|
+
- `__voltro.undo.apply` / `__voltro.undo.redo` — undo / redo one invocation by id.
|
|
516
|
+
|
|
517
|
+
`useUndoLog(api)` is the client controller:
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
const undo = useUndoLog('app')
|
|
521
|
+
// → { entries, loading, canUndo, canRedo, undo, redo, undoLast, redoLast }
|
|
522
|
+
|
|
523
|
+
<button disabled={!undo.canUndo} onClick={() => undo.undoLast()}>Undo</button>
|
|
524
|
+
<button disabled={!undo.canRedo} onClick={() => undo.redoLast()}>Redo</button>
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Undo applies the synthesized inverse as a **normal store write** — it traverses
|
|
528
|
+
the same tenant/RBAC guards, is reactive (the affected rows reappear / vanish
|
|
529
|
+
live, even across tabs), and is audited. It is **per-actor** (you can't undo
|
|
530
|
+
another subject's action), refuses on a concurrent-change **conflict** or past
|
|
531
|
+
an **action** boundary (an external side effect), and is idempotent. Because the
|
|
532
|
+
stack lives on the server, it survives a page reload.
|
|
533
|
+
|
|
534
|
+
## Enabling — `VOLTRO_UNDO`
|
|
535
|
+
|
|
536
|
+
Capture has a per-mutation cost (a read-before-write on update/delete + a
|
|
537
|
+
log-row insert), so it's **on outside production, off in production** by default;
|
|
538
|
+
set `VOLTRO_UNDO=on|off` to override (the same environment-aware default the
|
|
539
|
+
durable trace persistence uses). When off, the table isn't created, nothing is
|
|
540
|
+
captured, and the built-ins aren't served or codegen'd.
|
|
541
|
+
|
|
542
|
+
## Boundaries
|
|
543
|
+
|
|
544
|
+
Captures writes made through `ctx.store` (the same scope CDC covers) — raw
|
|
545
|
+
`unsafe()` SQL writes and the bulk helpers (`updateMany` / `deleteMany` /
|
|
546
|
+
`upsert`) aren't captured. Undo of an action-crossing invocation is refused
|
|
547
|
+
(the engine's "can't undo past an external effect" wall).
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
<!-- source: en/ui/client-utilities/analytics-catalog.md -->
|
|
554
|
+
## Analytics catalog
|
|
555
|
+
|
|
556
|
+
_Typed analytics — defineEvent + createAnalytics; track is compile-checked._
|
|
557
|
+
|
|
558
|
+
Declared, Schema-typed events: `track` is typed against the catalog (wrong name
|
|
559
|
+
/ payload = a compile error) + validated at runtime. One discoverable taxonomy.
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
const analytics = createAnalytics([
|
|
563
|
+
defineEvent('checkout.started', Schema.Struct({ plan: Schema.String })),
|
|
564
|
+
], sink)
|
|
565
|
+
analytics.track('checkout.started', { plan: 'pro' }) // typed + validated
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Builds on `defineTracking` (the declarative per-component event map).
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
<!-- source: en/ui/client-utilities/use-outbox.md -->
|
|
575
|
+
## useOutbox
|
|
576
|
+
|
|
577
|
+
_Queue mutations offline, replay them in order on reconnect._
|
|
578
|
+
|
|
579
|
+
Queue mutations while offline, replay IN ORDER on reconnect — over the existing
|
|
580
|
+
optimistic layer. Replay stops at the first conflict to preserve causality. NOT
|
|
581
|
+
multi-device CRDT (single-user/device, honest about its limits).
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
const outbox = useOutbox({ send })
|
|
585
|
+
// → { queue, enqueue, replay, online, pending, conflicts }
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
<!-- source: en/ui/client-utilities/use-windowed-subscription.md -->
|
|
593
|
+
## useWindowedSubscription
|
|
594
|
+
|
|
595
|
+
_Subscribe to only the visible viewport of a huge table — live and bounded._
|
|
596
|
+
|
|
597
|
+
For a huge table, subscribe to only the visible viewport (offset+limit from
|
|
598
|
+
scroll); the server pushes deltas only for in-window rows — live AND bounded.
|
|
599
|
+
|
|
600
|
+
```tsx
|
|
601
|
+
const w = useWindowedSubscription('app', 'rows.list', { rowHeight: 32, viewportHeight: 480, total })
|
|
602
|
+
<div style={{ height: 480, overflow: 'auto' }} onScroll={w.onScroll}>
|
|
603
|
+
<div style={{ height: w.topSpacer }} />
|
|
604
|
+
{w.rows.map(/* … */)}
|
|
605
|
+
<div style={{ height: w.bottomSpacer }} />
|
|
606
|
+
</div>
|
|
607
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
> How Voltro handles security — reporting a vulnerability, the supply-chain gates (dependency audit, inbound-license allowlist, SBOM), supported versions, and why self-hosting keeps your data yours.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!-- source: en/security/overview.md -->
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
_How Voltro handles security — reporting a vulnerability, the supply-chain gates (dependency audit, inbound-license allowlist, SBOM), supported versions, and why self-hosting keeps your data yours._
|
|
13
|
+
|
|
14
|
+
Security is a first-class concern, not an afterthought. This page is the public summary of how we handle it; the machine-readable policy also ships as a `SECURITY.md` inside every `@voltro/*` package.
|
|
15
|
+
|
|
16
|
+
## Reporting a vulnerability
|
|
17
|
+
|
|
18
|
+
Please report suspected vulnerabilities **privately** — never in public issues or channels.
|
|
19
|
+
|
|
20
|
+
- **Email [support@voltro.dev](mailto:support@voltro.dev)** with the subject `SECURITY`.
|
|
21
|
+
- Include the affected package(s) and version(s), the impact, and a minimal reproduction if you have one.
|
|
22
|
+
|
|
23
|
+
You'll get an **acknowledgement within 3 business days**, an assessment once we've reproduced the issue, and a **coordinated disclosure** timeline agreed with you — with credit in the release notes if you'd like it. We don't run a paid bug-bounty program yet, but we genuinely value responsible disclosure.
|
|
24
|
+
|
|
25
|
+
## Supply-chain assurance
|
|
26
|
+
|
|
27
|
+
Every release passes automated supply-chain gates in CI before a single package is published:
|
|
28
|
+
|
|
29
|
+
- **Dependency audit.** The production dependency graph is audited on every build (`pnpm audit --prod`); a HIGH or CRITICAL advisory **fails the release**. Known-but-unfixed-upstream transitives are pinned deliberately, not waved through.
|
|
30
|
+
- **Inbound-license allowlist.** Every third-party runtime dependency's license is checked against an allowlist — a disallowed license **fails the release**. The aggregated attributions ship as `THIRD-PARTY-NOTICES.md` in each package.
|
|
31
|
+
- **SBOM.** A CycloneDX Software Bill of Materials of the third-party runtime graph is generated for, and attached to, each release. If your procurement or security team needs it, request it at [support@voltro.dev](mailto:support@voltro.dev).
|
|
32
|
+
- **Release integrity.** Publishing is gated behind organization 2FA and a narrowly-scoped, short-lived automation token, and is all-or-nothing (two-phase) so consumers never see a half-published release.
|
|
33
|
+
|
|
34
|
+
## Supported versions
|
|
35
|
+
|
|
36
|
+
Voltro is pre-1.0 and ships in lockstep — all `@voltro/*` packages share one version. **Only the latest published release receives security fixes.** Pin exact versions in production and upgrade promptly.
|
|
37
|
+
|
|
38
|
+
## Your data stays yours
|
|
39
|
+
|
|
40
|
+
Voltro is **self-hosted**: it runs next to *your* database, with *your* connection string, *your* backups, and *your* access controls. **When you self-host**, the framework never proxies your data through Voltro infrastructure — there is no phone-home and no hosted control plane in the request path; the runtime is a library that lives in your own deployment.
|
|
41
|
+
|
|
42
|
+
Managed **Voltro Cloud** is coming as a separate, opt-in deploy target. Choosing it means running your workload on Voltro-operated infrastructure under its own data-processing terms — a deliberate choice, not the default. Self-hosting remains the default and keeps the guarantees above.
|