@voltro/cli 0.33.0 → 0.35.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 +1968 -0
- package/bin/nodeEnvironment.d.mts +30 -0
- package/bin/nodeEnvironment.mjs +158 -0
- package/bin/voltro.mjs +69 -5
- package/dist/addCommand-BNeoeSxe.js +124 -0
- package/dist/addCommand-aXSQveak.js +2 -0
- package/dist/agentsMd-BTchIZku.js +2 -0
- package/dist/agentsMd-mhQMF1bx.js +254 -0
- package/dist/apiBuild-B8aoJvuw.js +2 -0
- package/dist/{apiBuild-h9VHtnlw.js → apiBuild-DYD_ONLD.js} +46 -46
- package/dist/{appGraph-gQ_6GkQQ.js → appGraph-KGDPTuTy.js} +1 -1
- package/dist/appGraph-zuMGKVYX.js +2 -0
- package/dist/appPort-B_HpJ_ck.js +48 -0
- package/dist/baselineCommand-C2ClWZN3.js +2 -0
- package/dist/baselineCommand-DIttzO8A.js +227 -0
- package/dist/bin.js +71 -28
- package/dist/build-CD8K4XOr.js +711 -0
- package/dist/cacheCommand-DA4OH9xt.js +42 -0
- package/dist/capabilitiesCommand-nq_pz5xd.js +123 -0
- package/dist/checkCommand-Ct9xkTrS.js +232 -0
- package/dist/checkCommand-DKpDLlqu.js +2 -0
- package/dist/{cliArgs-qdZSElM3.js → cliArgs-D4p8n7EE.js} +12 -1
- package/dist/cliError-BmdYnghb.js +10 -0
- package/dist/cliOutput-D1tSBoRM.js +15 -0
- package/dist/{cliRuntime-Oh517vCV.js → cliRuntime-Dh7UDinH.js} +20 -20
- package/dist/cloudClient-DWL-Hw_T.js +67 -0
- package/dist/cloudCmd-Cvv5HGaZ.js +364 -0
- package/dist/clusterCmd-D5wsCmA_.js +54 -0
- package/dist/codegen-CYM3Zqrf.js +605 -0
- package/dist/codegen-ChBi_hVa.js +2 -0
- package/dist/codegenCommand-C4YoQIc2.js +30 -0
- package/dist/codemodRunner-BnFq3Fgu.js +5384 -0
- package/dist/commandRunner-BLAEFLjp.js +47 -0
- package/dist/commands-BE8E7zF3.js +816 -0
- package/dist/connectionConfig-UFlIEiys.js +66 -0
- package/dist/dashboardCommand-D7SgZGaN.js +25 -0
- package/dist/dataCommand-BhYwDgg-.js +537 -0
- package/dist/dataProfile-dW-PsfLB.js +15 -0
- package/dist/dbCommand-DS4b97Is.js +2 -0
- package/dist/{dbCommand-DTLKAfbA.js → dbCommand-O8HA63s2.js} +552 -402
- package/dist/{dev-C_P8FLSx.js → dev-C7sFZq3m.js} +3670 -3169
- package/dist/dev-D2BikO7a.js +3 -0
- package/dist/devActivity-Dx_3nnGv.js +100 -0
- package/dist/devActivity.js +1 -1
- package/dist/dialectDriver-CgXnDfec.js +39 -0
- package/dist/discover-C9XKJDco.js +25 -0
- package/dist/doctorCommand-CM4Ch9C7.js +2 -0
- package/dist/{checkCommand-xGhRFFg2.js → doctorCommand-DnimF5IM.js} +523 -1278
- package/dist/dormancyCommand-QewYug_s.js +69 -0
- package/dist/e2eCmd-BRabZww-.js +147 -0
- package/dist/embeddingsCommand-BfiLS_QI.js +73 -0
- package/dist/envCommand-CCGPRQY1.js +60 -0
- package/dist/evalCommand-6RUfPen4.js +118 -0
- package/dist/evolveCommand-CHsLCtDf.js +281 -0
- package/dist/fileTaxonomy-CJfgOllU.js +457 -0
- package/dist/frameworkTableAssembly-YyVe32Cb.js +2 -0
- package/dist/frameworkTableAssembly-oMPBKqlE.js +511 -0
- package/dist/generateCommand-DbgcUpGw.js +147 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +4 -3
- package/dist/infoCommand-DwOgK1t6.js +60 -0
- package/dist/{inspect-BUUjt773.js → inspect-CjTYzAs_.js} +113 -41
- package/dist/inspect-P4pxoMaV.js +2 -0
- package/dist/inspectCmd-EHFZ9yYu.js +224 -0
- package/dist/inspectFetch-EMuhTG_9.js +151 -0
- package/dist/inspectMetrics-CfdKLh6t.js +72 -0
- package/dist/loadEnv-D9nEOClM.js +44 -0
- package/dist/logFileSink-C_D2wRN1.js +105 -0
- package/dist/logsCmd-D36xK7Zu.js +260 -0
- package/dist/manifestBuild-COkJoyAr.js +2 -0
- package/dist/{manifestBuild-BLrVuSlM.js → manifestBuild-hpPLaGxV.js} +1 -1
- package/dist/metaCommands-7MJfZ5cf.js +196 -0
- package/dist/migrate-BV7I-ZHZ.js +83 -0
- package/dist/mssqlClusterPatch-_4cE_nun.js +44 -0
- package/dist/newCommand-COWOJ1_E.js +156 -0
- package/dist/nodeEnvironment-cGFAj1J8.js +28 -0
- package/dist/packageCommand-Cug_3Ogl.js +271 -0
- package/dist/pageConvention-cEiRxdab.js +5 -0
- package/dist/privacyCommand-C-Df56U_.js +146 -0
- package/dist/probeCommand-CZfaaUOZ.js +122 -0
- package/dist/projectScaffold-DmzEKHib.js +2 -0
- package/dist/projectScaffold-LMMtaavR.js +814 -0
- package/dist/renderModeScan-D7J1B7Kw.js +105 -0
- package/dist/renderProfile-1OWWAAtx.js +81 -0
- package/dist/runtimeRegistry-DMeKfTHP.js +81 -0
- package/dist/runtimeTrace-CH3eUiMw.js +91 -0
- package/dist/scheduleCmd-DQRu6BZC.js +149 -0
- package/dist/scheduleManifestCmd-D2x0CTTY.js +249 -0
- package/dist/schemaIr-UJybUUZW.js +103 -0
- package/dist/{sdkgen-C81QIkiL.js → sdkgen-BLkvGRfX.js} +111 -209
- package/dist/seedRunner-ZmLSqNe2.js +333 -0
- package/dist/serveCommand-CbDHU6l-.js +2 -0
- package/dist/serveCommand-iwlUBNS1.js +1766 -0
- package/dist/serveEntry.js +5 -5
- package/dist/serverlessCommand-CfJZy6dS.js +482 -0
- package/dist/start-BgN62boB.js +3 -0
- package/dist/start-T4VesWiM.js +1087 -0
- package/dist/startEntry.js +2 -2
- package/dist/staticCommand-Dr2M6tpU.js +304 -0
- package/dist/storageCommand-Co6NfLqN.js +42 -0
- package/dist/templates-De8IR5-c.js +102 -0
- package/dist/test-CI6iDsYc.js +115 -0
- package/dist/tracesCmd-DStmCJPi.js +232 -0
- package/dist/tsconfigPaths-BWXBWgcl.js +107 -0
- package/dist/tsxLoader-EuXmSJ1K.js +51 -0
- package/dist/typecheckCommand-BlsWiCNq.js +61 -0
- package/dist/updateCommand-BlMZhWgO.js +2 -0
- package/dist/updateCommand-x0pI_x-B.js +585 -0
- package/dist/webDev-BcykISYQ2.js +2 -0
- package/dist/{inspectMetrics-1xzTKAFx.js → webDev-Dybxew86.js} +988 -1560
- package/dist/webhookDiscovery-CrGAfhIG.js +2 -0
- package/dist/webhookDiscovery-D7VaeMlz.js +51 -0
- package/dist/webhooksCommand-DlAgS2Iw.js +267 -0
- package/dist/workflowsCmd-BGF-mRZ5.js +608 -0
- package/package.json +209 -17
- package/templates/AGENTS.core.md +58 -3
- package/templates/AGENTS.md +64 -7
- package/templates/agent-docs/_index.md +6 -4
- package/templates/agent-docs/_manifest.json +23 -6
- package/templates/agent-docs/ai.md +370 -0
- package/templates/agent-docs/authentication.md +313 -31
- package/templates/agent-docs/caching.md +6 -0
- package/templates/agent-docs/cli.md +853 -50
- package/templates/agent-docs/data.md +608 -12
- package/templates/agent-docs/database/migrations.md +223 -25
- package/templates/agent-docs/database/misc.md +156 -40
- package/templates/agent-docs/database/querying.md +19 -1
- package/templates/agent-docs/database/scaling.md +60 -0
- package/templates/agent-docs/database/schema.md +1 -0
- package/templates/agent-docs/database/seedsdialects.md +208 -19
- package/templates/agent-docs/database/transactions.md +68 -0
- package/templates/agent-docs/deployment.md +284 -25
- package/templates/agent-docs/introduction.md +88 -17
- package/templates/agent-docs/local-first-mobile.md +79 -4
- package/templates/agent-docs/multi-tenancy.md +188 -42
- package/templates/agent-docs/observability.md +58 -3
- package/templates/agent-docs/plugins/ai-flows.md +247 -2
- package/templates/agent-docs/plugins/analytics-postgres.md +1 -1
- package/templates/agent-docs/plugins/audit.md +37 -1
- package/templates/agent-docs/plugins/auth-social.md +143 -0
- package/templates/agent-docs/plugins/auth-workos.md +4 -2
- package/templates/agent-docs/plugins/auth.md +131 -6
- package/templates/agent-docs/plugins/billing.md +132 -15
- package/templates/agent-docs/plugins/cdc-out.md +46 -7
- package/templates/agent-docs/plugins/clickhouse.md +32 -1
- package/templates/agent-docs/plugins/duckdb.md +1 -1
- package/templates/agent-docs/plugins/flags.md +132 -0
- package/templates/agent-docs/plugins/governance.md +105 -7
- package/templates/agent-docs/plugins/multitenancy.md +9 -4
- package/templates/agent-docs/plugins/presence.md +13 -2
- package/templates/agent-docs/plugins/ratelimit.md +9 -0
- package/templates/agent-docs/plugins/search.md +162 -8
- package/templates/agent-docs/plugins/sso-saml.md +47 -8
- package/templates/agent-docs/plugins/storage.md +11 -0
- package/templates/agent-docs/plugins/webhooks.md +105 -0
- package/templates/agent-docs/plugins.md +152 -18
- package/templates/agent-docs/reference.md +60 -3
- package/templates/agent-docs/releases.md +1117 -0
- package/templates/agent-docs/routing.md +43 -25
- package/templates/agent-docs/scheduling.md +27 -0
- package/templates/agent-docs/schema-driven-ui.md +92 -12
- package/templates/agent-docs/security.md +426 -0
- package/templates/agent-docs/templates/apibackends.md +87 -18
- package/templates/agent-docs/templates/appshells.md +32 -14
- package/templates/agent-docs/templates/overview.md +13 -8
- package/templates/agent-docs/testing.md +211 -14
- package/templates/agent-docs/whats-new.md +98 -136
- package/templates/agent-docs/workflows.md +231 -14
- package/templates/apps/api-ai/actions/summarize.action.ts +11 -0
- package/templates/apps/api-ai/package.json +8 -7
- package/templates/apps/api-auth/actions/me.action.ts +13 -0
- package/templates/apps/api-auth/package.json +9 -8
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend/package.json +12 -8
- package/templates/apps/api-backend/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +14 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +8 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +10 -0
- package/templates/apps/api-backend-deactivation/package.json +8 -7
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +17 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-mail/package.json +9 -8
- package/templates/apps/api-backend-mail/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-mariadb/.env.example +14 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-mariadb/package.json +10 -9
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-sqlite/.env.example +14 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-sqlite/package.json +9 -8
- package/templates/apps/api-backend-sqlite/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +13 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +12 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-storage/package.json +9 -8
- package/templates/apps/api-backend-storage/queries/notes.query.ts +30 -8
- package/templates/apps/api-cms/actions/content.get.action.ts +7 -0
- package/templates/apps/api-cms/actions/content.types.action.ts +6 -0
- package/templates/apps/api-cms/actions/me.action.ts +13 -0
- package/templates/apps/api-cms/app.config.ts +19 -0
- package/templates/apps/api-cms/authz.ts +63 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.ts +10 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +10 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +5 -0
- package/templates/apps/api-cms/package.json +11 -10
- package/templates/apps/api-cms/queries/content.list.query.ts +23 -7
- package/templates/apps/api-cms/tests/accessDecisions.test.ts +121 -0
- package/templates/apps/api-cms/tests/content.descriptors.test.ts +8 -4
- package/templates/apps/api-collab/mutations/documents.create.mutation.ts +8 -0
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +13 -0
- package/templates/apps/api-collab/package.json +9 -8
- package/templates/apps/api-collab/queries/documents.query.ts +19 -7
- package/templates/apps/api-data-advanced/package.json +9 -8
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +12 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +9 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +17 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +9 -0
- package/templates/apps/api-durable/package.json +9 -8
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +16 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +12 -0
- package/templates/apps/api-feature-flags/package.json +10 -9
- package/templates/apps/api-governance/README.md +30 -8
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -1
- package/templates/apps/api-governance/actions/profiles.get.action.ts +29 -4
- package/templates/apps/api-governance/database/schema.ts +16 -4
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +12 -0
- package/templates/apps/api-governance/package.json +9 -8
- package/templates/apps/api-kv/actions/sync.pull.action.ts +15 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +13 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +7 -0
- package/templates/apps/api-kv/package.json +9 -8
- package/templates/apps/api-kv/queries/events.list.query.ts +19 -8
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +11 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +13 -0
- package/templates/apps/api-moderation/package.json +9 -8
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +8 -0
- package/templates/apps/api-observability/package.json +9 -8
- package/templates/apps/api-observability/queries/notes.list.query.ts +13 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +14 -0
- package/templates/apps/api-ratelimit/package.json +9 -8
- package/templates/apps/api-rbac/package.json +9 -8
- package/templates/apps/api-rest/package.json +8 -7
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +13 -0
- package/templates/apps/api-saas/package.json +12 -11
- package/templates/apps/api-saas/queries/projects.list.query.ts +11 -0
- package/templates/apps/api-saas-starter/actions/me.action.ts +13 -0
- package/templates/apps/api-saas-starter/app.config.ts +19 -0
- package/templates/apps/api-saas-starter/authz.ts +75 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +10 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +9 -0
- package/templates/apps/api-saas-starter/package.json +15 -11
- package/templates/apps/api-saas-starter/queries/invites.list.query.ts +19 -8
- package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -7
- package/templates/apps/api-saas-starter/tests/accessDecisions.test.ts +135 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +14 -0
- package/templates/apps/api-search/package.json +9 -8
- package/templates/apps/api-search/queries/articles.list.query.ts +21 -8
- package/templates/apps/api-status/README.md +10 -3
- package/templates/apps/api-status/app.config.ts +8 -3
- package/templates/apps/api-status/authz.ts +5 -3
- package/templates/apps/api-status/package.json +9 -8
- package/templates/apps/api-status/queries/components.list.query.ts +14 -6
- package/templates/apps/api-status/queries/incidents.live.query.ts +23 -10
- package/templates/apps/api-status/queries/updates.list.query.ts +16 -9
- package/templates/apps/api-status/tests/status.test.ts +9 -1
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +12 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +13 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +12 -0
- package/templates/apps/api-versioning/package.json +9 -8
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +16 -0
- package/templates/apps/api-webhooks/package.json +10 -9
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +10 -0
- package/templates/apps/changelog/package.json +8 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +10 -8
- package/templates/apps/frontend-admin/src/lib/admin.ts +20 -9
- package/templates/apps/frontend-admin/src/locales/de.ts +11 -1
- package/templates/apps/frontend-admin/src/locales/en.ts +13 -1
- package/templates/apps/frontend-admin/src/pages/admin/[entity]/page.tsx +65 -22
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +130 -23
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +3 -2
- package/templates/apps/frontend-admin/src/pages/admin/page.test.tsx +19 -2
- package/templates/apps/frontend-admin/src/pages/admin/page.tsx +9 -4
- package/templates/apps/frontend-app/app.config.ts +4 -3
- package/templates/apps/frontend-app/package.json +11 -8
- package/templates/apps/frontend-app/src/lib/api.ts +25 -0
- package/templates/apps/frontend-app/src/pages/page.test.tsx +130 -82
- package/templates/apps/frontend-app/src/pages/page.tsx +14 -18
- package/templates/apps/frontend-auth/package.json +10 -8
- package/templates/apps/frontend-blank/package.json +9 -7
- package/templates/apps/frontend-cms/package.json +11 -9
- package/templates/apps/frontend-collab/package.json +12 -9
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +122 -78
- package/templates/apps/frontend-contact/package.json +9 -7
- package/templates/apps/frontend-dashboard/package.json +9 -7
- package/templates/apps/frontend-docs/package.json +9 -7
- package/templates/apps/frontend-i18n/package.json +8 -6
- package/templates/apps/frontend-landing/package.json +9 -7
- package/templates/apps/frontend-portal/package.json +10 -8
- package/templates/apps/frontend-saas/app.config.ts +10 -6
- package/templates/apps/frontend-saas/package.json +10 -8
- package/templates/apps/frontend-saas/src/lib/api.ts +27 -32
- package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +7 -8
- package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +27 -3
- package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +4 -4
- package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +3 -4
- package/templates/apps/frontend-spa/package.json +9 -7
- package/templates/apps/frontend-ssr/package.json +9 -7
- package/templates/apps/frontend-ssr-api/package.json +10 -8
- package/templates/apps/frontend-static-blog/package.json +8 -6
- package/templates/apps/frontend-status/package.json +10 -8
- package/templates/apps/mobile-app/README.md +1 -0
- package/templates/apps/mobile-app/package.json +4 -2
- package/templates/apps/mobile-app/src/app/index.tsx +22 -12
- package/templates/apps/mobile-app/src/app/orders/[id].tsx +1 -1
- package/templates/apps/mobile-app/src/lib/api.ts +34 -0
- package/templates/apps/mobile-app/voltro.mobile.ts +4 -2
- package/templates/baselines/bare/.env.example +14 -0
- package/templates/baselines/bare/baseline.json +4 -4
- package/templates/baselines/compose/.env.example +14 -0
- package/templates/baselines/compose/README.md +1 -1
- package/templates/baselines/compose/baseline.json +5 -5
- package/templates/baselines/compose-mariadb/.env.example +14 -0
- package/templates/baselines/compose-mariadb/README.md +1 -1
- package/templates/baselines/compose-mariadb/baseline.json +5 -5
- package/templates/baselines/helm/.env.example +14 -0
- package/templates/baselines/helm/baseline.json +4 -4
- package/dist/apiBuild-C-x9YacA.js +0 -2
- package/dist/appGraph-CvQCte0z.js +0 -2
- package/dist/checkCommand-DRovTKza.js +0 -2
- package/dist/commands-CJfepbm4.js +0 -11541
- package/dist/dbCommand-b1gum4td.js +0 -2
- package/dist/dev-iiMtlkfs.js +0 -3
- package/dist/devActivity-BhIu6ncs.js +0 -159
- package/dist/frameworkTableAssembly-BwIrO5nv.js +0 -638
- package/dist/frameworkTableAssembly-D-EebUQX.js +0 -2
- package/dist/inspect-mmBuRXmy.js +0 -2
- package/dist/manifestBuild-Dj8Jjoto.js +0 -2
- package/dist/seedRunner-Bqxgp7HZ.js +0 -230
- package/dist/serveCommand-DdaM4Hup.js +0 -1608
- package/dist/start-C0koT0UO.js +0 -1084
- /package/templates/apps/api-ai/actions/{summarize.action.server.tsx → summarize.action.server.ts} +0 -0
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
// `renderMode: 'ssr'` so the gate runs per request.
|
|
12
12
|
//
|
|
13
13
|
// The pages read the api live with `useSubscription` and write with
|
|
14
|
-
// `useMutation` / `useAction
|
|
14
|
+
// `useMutation` / `useAction` — imported from `src/lib/api.ts`, which binds them
|
|
15
|
+
// ONCE to this api's generated procedure map so every tag and every input/output
|
|
16
|
+
// type is checked by the compiler. The auth forms POST to the api's `/auth/*`.
|
|
15
17
|
import { defineEnv, envVar } from '@voltro/env'
|
|
16
18
|
|
|
17
19
|
// Public, build-time env (baked into the bundle; read via `publicEnv`).
|
|
@@ -35,11 +37,13 @@ export default {
|
|
|
35
37
|
// grow the app to more languages.
|
|
36
38
|
locales: ['en', 'de'] as const,
|
|
37
39
|
defaultLocale: 'en' as const,
|
|
38
|
-
// The api this dashboard consumes. The NAME ('app') is the lookup key
|
|
39
|
-
//
|
|
40
|
-
// `
|
|
41
|
-
//
|
|
42
|
-
//
|
|
40
|
+
// The api this dashboard consumes. The NAME ('app') is the lookup key — spelled
|
|
41
|
+
// ONCE, in `src/lib/api.ts`'s `createHooks<AppProcedures>('app')`, and then
|
|
42
|
+
// never again: pages call `useSubscription('projects.list')` /
|
|
43
|
+
// `useMutation('projects.create')`. (A loader still names the tag itself:
|
|
44
|
+
// `query('session.me', {})`.) The api's port is auto-discovered from its own
|
|
45
|
+
// app.config.ts, and codegen pulls its typed rpc surface (including the
|
|
46
|
+
// plugins' `billing.*` / `/auth`) into that same map.
|
|
43
47
|
apis: {
|
|
44
48
|
app: { package: '@{{projectName}}/api' },
|
|
45
49
|
},
|
|
@@ -12,21 +12,23 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.35.0",
|
|
16
|
+
"@voltro/cli": "0.35.0",
|
|
17
|
+
"@voltro/client": "0.35.0",
|
|
18
|
+
"@voltro/env": "0.35.0",
|
|
19
|
+
"@voltro/i18n": "0.35.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.35.0",
|
|
21
|
+
"@voltro/web": "0.35.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.35.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
|
+
"jsdom": "^30.0.0",
|
|
29
30
|
"typescript": "^6.0.3",
|
|
31
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
30
32
|
"vitest": "^4.1.10"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
// The
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// `
|
|
5
|
-
//
|
|
1
|
+
// The TYPED hook surface for the `app` api — bound ONCE, here, and imported by
|
|
2
|
+
// every page instead of `@voltro/client`.
|
|
3
|
+
//
|
|
4
|
+
// `AppProcedures` is emitted by codegen into the api's `rpcGroup.generated.ts`:
|
|
5
|
+
// a type-level map of every rpc tag (the app's own AND the plugins', e.g.
|
|
6
|
+
// `billing.*`) to the descriptor behind it. Feeding it to `createHooks` makes
|
|
7
|
+
// the tag a literal union and the input/output types INFER:
|
|
8
|
+
//
|
|
9
|
+
// const { data } = useSubscription('projects.list') // rows, typed
|
|
10
|
+
// const create = useMutation('projects.create') // input + output typed
|
|
11
|
+
//
|
|
12
|
+
// So the row shapes this file used to declare by hand are gone. They were a
|
|
13
|
+
// MIRROR of the api's descriptor outputs, kept in sync by nobody: the old
|
|
14
|
+
// `useSubscription<ReadonlyArray<Project>>('app', 'projects.list')` asserted a
|
|
15
|
+
// type nothing checked against the server, and a typo'd tag only showed up at
|
|
16
|
+
// runtime. Both are compile errors now.
|
|
17
|
+
//
|
|
18
|
+
// `'app'` is the key this web app gave the api in `app.config.ts` → `apis`.
|
|
19
|
+
// `import type` is erased, so the binding costs the browser bundle nothing.
|
|
20
|
+
import { createHooks } from '@voltro/client'
|
|
21
|
+
import type { AppProcedures } from '@{{projectName}}/api/rpcGroup'
|
|
6
22
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
readonly createdAt: string | Date
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Invite {
|
|
15
|
-
readonly id: string
|
|
16
|
-
readonly email: string
|
|
17
|
-
readonly status: string
|
|
18
|
-
readonly tenantId: string
|
|
19
|
-
readonly createdAt: string | Date
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// `billing.subscription` streams `{ subscription: … | null }` — null before the
|
|
23
|
-
// tenant has any subscription row (they're on the implicit free plan).
|
|
24
|
-
export interface SubscriptionState {
|
|
25
|
-
readonly subscription: {
|
|
26
|
-
readonly plan: string
|
|
27
|
-
readonly status: string
|
|
28
|
-
readonly quantity: number
|
|
29
|
-
readonly currentPeriodEnd: string | null
|
|
30
|
-
readonly cancelAt: string | null
|
|
31
|
-
} | null
|
|
32
|
-
}
|
|
23
|
+
// Destructured on purpose — `react-hooks/rules-of-hooks` only treats a member
|
|
24
|
+
// call as a hook when the object is PascalCase, so top-level `useX` identifiers
|
|
25
|
+
// are what keep the React lint rules working at every call site.
|
|
26
|
+
export const { useSubscription, useMutation, useAction } = createHooks<AppProcedures>('app')
|
|
33
27
|
|
|
34
|
-
// The caller's resolved identity, from `session.me`. The SSR gate reads it
|
|
28
|
+
// The caller's resolved identity, from `session.me`. The SSR gate reads it, and
|
|
29
|
+
// a LOADER is not a hook — it calls `ctx.query` — so this shape stays hand-written.
|
|
35
30
|
export interface Identity {
|
|
36
31
|
readonly type: string
|
|
37
32
|
readonly id: string | null
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
// row yet means the tenant is on the implicit free plan.
|
|
7
7
|
import type { ReactNode } from 'react'
|
|
8
8
|
import type { PageMeta } from '@voltro/web'
|
|
9
|
-
import { useSubscription, useAction } from '@voltro/client'
|
|
10
9
|
import { T, useT } from '@voltro/i18n'
|
|
11
10
|
import { getCatalog } from '../../../locales/index'
|
|
12
|
-
import
|
|
11
|
+
import { useAction, useSubscription } from '../../../lib/api'
|
|
13
12
|
|
|
14
13
|
export const renderMode = 'ssr' as const
|
|
15
14
|
|
|
@@ -17,13 +16,13 @@ export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
|
17
16
|
title: getCatalog(locale)['meta.billing.title'],
|
|
18
17
|
})
|
|
19
18
|
|
|
20
|
-
interface CheckoutInput { plan: string; successUrl: string; cancelUrl: string }
|
|
21
|
-
interface SeatsInput { quantity: number }
|
|
22
|
-
|
|
23
19
|
export default function Billing(): ReactNode {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
// `billing.*` is contributed by @voltro/plugin-billing and lands in the same
|
|
21
|
+
// generated map as the app's own procedures, so a plugin route is typed here
|
|
22
|
+
// exactly like a hand-written one — no local Checkout/Seats input mirrors.
|
|
23
|
+
const { data } = useSubscription('billing.subscription')
|
|
24
|
+
const checkout = useAction('billing.startCheckout')
|
|
25
|
+
const seats = useAction('billing.changeSeats')
|
|
27
26
|
|
|
28
27
|
const sub = data?.subscription ?? null
|
|
29
28
|
const planLabel = useT('billing.plan.free')
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
//
|
|
3
|
-
// Projects page — the live list + the paywall branch. We mock
|
|
4
|
-
// (useSubscription streams the rows; useMutation
|
|
3
|
+
// Projects page — the live list + the paywall branch. We mock the app's TYPED
|
|
4
|
+
// hook binding `src/lib/api.ts` (useSubscription streams the rows; useMutation
|
|
5
|
+
// is the create) — mocking `@voltro/client` underneath it would leave the real
|
|
6
|
+
// `createHooks` running over a stub, i.e. the wrong seam. We drive the
|
|
5
7
|
// create form, asserting: the SSR'd rows render, the empty state shows, and a
|
|
6
8
|
// `projects.create` that rejects with a typed `EntitlementExceeded` flips the
|
|
7
9
|
// page into the upgrade CTA (NOT a raw error).
|
|
@@ -14,7 +16,15 @@ import enCatalog from '../../locales/en'
|
|
|
14
16
|
|
|
15
17
|
const subData = vi.fn<() => { data: unknown }>()
|
|
16
18
|
const mutate = vi.fn<(input: { name: string }) => Promise<unknown>>()
|
|
17
|
-
|
|
19
|
+
// Only the HOOKS are faked. `isEntitlementExceeded` comes through from the real
|
|
20
|
+
// module — an earlier version of this file re-implemented it inside the mock,
|
|
21
|
+
// which meant the paywall test asserted that a copy of the predicate agreed
|
|
22
|
+
// with the literal the same test had just written. That passes in every world,
|
|
23
|
+
// including the one where the framework hands `catch` a value with no `_tag`
|
|
24
|
+
// at all. `import type` is erased, so importing the original pulls in nothing
|
|
25
|
+
// but `@voltro/client`.
|
|
26
|
+
vi.mock('../../lib/api', async (importOriginal) => ({
|
|
27
|
+
...(await importOriginal<typeof import('../../lib/api')>()),
|
|
18
28
|
useSubscription: () => subData(),
|
|
19
29
|
useMutation: () => ({ mutate, pending: false, error: undefined, data: undefined }),
|
|
20
30
|
}))
|
|
@@ -104,10 +114,24 @@ describe('projects — paywall', () => {
|
|
|
104
114
|
})
|
|
105
115
|
|
|
106
116
|
test('a typed EntitlementExceeded flips the page into the upgrade CTA', async () => {
|
|
117
|
+
// The rejection value is the TYPED ERROR ITSELF — that is the framework's
|
|
118
|
+
// guarantee for `mutate` / `run` / `signal` / `upload`, and the whole reason
|
|
119
|
+
// this page can branch on `_tag` instead of string-matching a message.
|
|
107
120
|
mutate.mockRejectedValue({ _tag: 'EntitlementExceeded', entitlement: 'projects', limit: 3 })
|
|
108
121
|
render(createElement(Projects))
|
|
109
122
|
await submit()
|
|
110
123
|
expect(container.querySelector('.paywall')).not.toBeNull()
|
|
111
124
|
expect(container.textContent).toContain('hit your plan limit')
|
|
112
125
|
})
|
|
126
|
+
|
|
127
|
+
test('an UNTAGGED failure does not paywall — that branch is for a declared outcome', async () => {
|
|
128
|
+
// The negative control, and the exact state the app was in while a rejected
|
|
129
|
+
// write handed back a wrapper with no `_tag`: the create fails, and the user
|
|
130
|
+
// is shown nothing about why. A paywall that appears for every failure is
|
|
131
|
+
// as wrong as one that never appears.
|
|
132
|
+
mutate.mockRejectedValue(new Error('socket hang up'))
|
|
133
|
+
render(createElement(Projects))
|
|
134
|
+
await submit()
|
|
135
|
+
expect(container.querySelector('.paywall')).toBeNull()
|
|
136
|
+
})
|
|
113
137
|
})
|
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
import type { ReactNode } from 'react'
|
|
8
8
|
import { useState, type FormEvent } from 'react'
|
|
9
9
|
import type { PageMeta } from '@voltro/web'
|
|
10
|
-
import { useSubscription, useMutation } from '@voltro/client'
|
|
11
10
|
import { T, useT } from '@voltro/i18n'
|
|
12
11
|
import { getCatalog } from '../../locales'
|
|
13
|
-
import { isEntitlementExceeded,
|
|
12
|
+
import { isEntitlementExceeded, useMutation, useSubscription } from '../../lib/api'
|
|
14
13
|
|
|
15
14
|
export const renderMode = 'ssr' as const
|
|
16
15
|
|
|
@@ -19,8 +18,9 @@ export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
|
19
18
|
})
|
|
20
19
|
|
|
21
20
|
export default function Projects(): ReactNode {
|
|
22
|
-
|
|
23
|
-
const {
|
|
21
|
+
// Tag + row type both come from the generated procedure map — no annotation.
|
|
22
|
+
const { data: live } = useSubscription('projects.list')
|
|
23
|
+
const { mutate, pending } = useMutation('projects.create')
|
|
24
24
|
const projects = live ?? []
|
|
25
25
|
const [name, setName] = useState('')
|
|
26
26
|
const [paywalled, setPaywalled] = useState(false)
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
import type { ReactNode } from 'react'
|
|
7
7
|
import { useState, type FormEvent } from 'react'
|
|
8
8
|
import type { PageMeta } from '@voltro/web'
|
|
9
|
-
import { useSubscription, useMutation } from '@voltro/client'
|
|
10
9
|
import { T, useT } from '@voltro/i18n'
|
|
11
10
|
import { getCatalog } from '../../../locales/index'
|
|
12
|
-
import { isEntitlementExceeded,
|
|
11
|
+
import { isEntitlementExceeded, useMutation, useSubscription } from '../../../lib/api'
|
|
13
12
|
|
|
14
13
|
export const renderMode = 'ssr' as const
|
|
15
14
|
|
|
@@ -24,8 +23,8 @@ const STATUS_KEY: Record<string, 'team.status.pending' | 'team.status.accepted'
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export default function Team(): ReactNode {
|
|
27
|
-
const { data: live } = useSubscription
|
|
28
|
-
const { mutate, pending } = useMutation
|
|
26
|
+
const { data: live } = useSubscription('invites.list')
|
|
27
|
+
const { mutate, pending } = useMutation('invites.create')
|
|
29
28
|
const invites = live ?? []
|
|
30
29
|
const [email, setEmail] = useState('')
|
|
31
30
|
const [outOfSeats, setOutOfSeats] = useState(false)
|
|
@@ -12,20 +12,22 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/ui-shadcn": "0.
|
|
20
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.35.0",
|
|
16
|
+
"@voltro/client": "0.35.0",
|
|
17
|
+
"@voltro/env": "0.35.0",
|
|
18
|
+
"@voltro/i18n": "0.35.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.35.0",
|
|
20
|
+
"@voltro/web": "0.35.0",
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.35.0",
|
|
26
26
|
"@types/react": "^19.0.0",
|
|
27
27
|
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"jsdom": "^30.0.0",
|
|
28
29
|
"typescript": "^6.0.3",
|
|
30
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
29
31
|
"vitest": "^4.1.10"
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -12,20 +12,22 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/ui-shadcn": "0.
|
|
20
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.35.0",
|
|
16
|
+
"@voltro/client": "0.35.0",
|
|
17
|
+
"@voltro/env": "0.35.0",
|
|
18
|
+
"@voltro/i18n": "0.35.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.35.0",
|
|
20
|
+
"@voltro/web": "0.35.0",
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.35.0",
|
|
26
26
|
"@types/react": "^19.0.0",
|
|
27
27
|
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"jsdom": "^30.0.0",
|
|
28
29
|
"typescript": "^6.0.3",
|
|
30
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
29
31
|
"vitest": "^4.1.10"
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -12,21 +12,23 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.35.0",
|
|
16
|
+
"@voltro/cli": "0.35.0",
|
|
17
|
+
"@voltro/client": "0.35.0",
|
|
18
|
+
"@voltro/env": "0.35.0",
|
|
19
|
+
"@voltro/i18n": "0.35.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.35.0",
|
|
21
|
+
"@voltro/web": "0.35.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.35.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
|
+
"jsdom": "^30.0.0",
|
|
29
30
|
"typescript": "^6.0.3",
|
|
31
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
30
32
|
"vitest": "^4.1.10"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -12,19 +12,21 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.35.0",
|
|
16
|
+
"@voltro/client": "0.35.0",
|
|
17
|
+
"@voltro/env": "0.35.0",
|
|
18
|
+
"@voltro/i18n": "0.35.0",
|
|
19
|
+
"@voltro/web": "0.35.0",
|
|
20
20
|
"react": "^19.0.0",
|
|
21
21
|
"react-dom": "^19.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/devtools": "0.
|
|
24
|
+
"@voltro/devtools": "0.35.0",
|
|
25
25
|
"@types/react": "^19.0.0",
|
|
26
26
|
"@types/react-dom": "^19.0.0",
|
|
27
|
+
"jsdom": "^30.0.0",
|
|
27
28
|
"typescript": "^6.0.3",
|
|
29
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
28
30
|
"vitest": "^4.1.10"
|
|
29
31
|
}
|
|
30
32
|
}
|
|
@@ -12,21 +12,23 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.35.0",
|
|
16
|
+
"@voltro/cli": "0.35.0",
|
|
17
|
+
"@voltro/client": "0.35.0",
|
|
18
|
+
"@voltro/env": "0.35.0",
|
|
19
|
+
"@voltro/i18n": "0.35.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.35.0",
|
|
21
|
+
"@voltro/web": "0.35.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.35.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
|
+
"jsdom": "^30.0.0",
|
|
29
30
|
"typescript": "^6.0.3",
|
|
31
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
30
32
|
"vitest": "^4.1.10"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -27,6 +27,7 @@ pnpm ios # or: pnpm android · pnpm start (Expo Go)
|
|
|
27
27
|
|
|
28
28
|
| Path | What |
|
|
29
29
|
|---|---|
|
|
30
|
+
| `src/lib/api.ts` | the **typed hook binding** — `createHooks<AppProcedures>('app')`, imported by every screen |
|
|
30
31
|
| `src/lib/deeplinks.ts` | the typed deep-link table + pure resolver (**tested**) |
|
|
31
32
|
| `src/lib/sync.ts` | the background-sync decision layer (**tested**) |
|
|
32
33
|
| `src/lib/notifications.ts` | build a `_voltro_devices` row from a push token (**tested**) |
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/client": "0.
|
|
16
|
-
"@voltro/react-native": "0.
|
|
15
|
+
"@voltro/client": "0.35.0",
|
|
16
|
+
"@voltro/react-native": "0.35.0",
|
|
17
17
|
"expo": "^52.0.0",
|
|
18
18
|
"expo-constants": "~17.0.0",
|
|
19
19
|
"expo-linking": "~7.0.0",
|
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
"react-native": "0.76.5"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"@{{projectName}}/api": "0.35.0",
|
|
29
30
|
"@types/react": "^19.0.0",
|
|
30
31
|
"typescript": "^6.0.3",
|
|
32
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
31
33
|
"vitest": "^4.1.10"
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
// Home screen — the reactive end-to-end loop, on a phone.
|
|
2
2
|
//
|
|
3
|
-
// The SAME hooks as the web frontend: `useSubscription('
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The SAME hooks as the web frontend: `useSubscription('notes.list')` streams
|
|
4
|
+
// the server-side query; `useMutation('notes.create')` writes it. When the
|
|
5
|
+
// mutation lands, the server pushes a delta and this list re-renders — no
|
|
6
|
+
// refetch, no polling. (Requires the M0 client boot; see ../client.ts.)
|
|
7
|
+
//
|
|
8
|
+
// Both come from `../lib/api`, which binds them ONCE to the api's generated
|
|
9
|
+
// procedure map — so the tag autocompletes, a typo is a compile error, and the
|
|
10
|
+
// row type is inferred rather than declared here.
|
|
7
11
|
import { useState } from 'react'
|
|
8
12
|
import { Link } from 'expo-router'
|
|
9
13
|
import { FlatList, Pressable, Text, TextInput, View } from 'react-native'
|
|
10
|
-
import { useMutation, useSubscription } from '@voltro/client'
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
import { useMutation, useSubscription } from '../lib/api'
|
|
16
|
+
|
|
17
|
+
// The dev AuthMiddleware resolves the tenant from the `x-tenant` header,
|
|
18
|
+
// defaulting to 'acme'. `notes.create` guards that the submitted tenantId
|
|
19
|
+
// matches the caller's (a typed TenantMismatch otherwise).
|
|
20
|
+
const TENANT = 'acme'
|
|
16
21
|
|
|
17
22
|
export default function Home() {
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
// No row `interface` and no `<T>`: the shape comes from `notes.list`'s own
|
|
24
|
+
// output schema, including the client's `optimistic` marker.
|
|
25
|
+
const notes = useSubscription('notes.list')
|
|
26
|
+
const createNote = useMutation('notes.create')
|
|
20
27
|
const [title, setTitle] = useState('')
|
|
21
28
|
|
|
22
29
|
return (
|
|
@@ -32,7 +39,10 @@ export default function Home() {
|
|
|
32
39
|
onPress={() => {
|
|
33
40
|
if (title.trim().length === 0) return
|
|
34
41
|
// Auto-optimistic: the list updates instantly, reconciles on ack.
|
|
35
|
-
|
|
42
|
+
// `tenantId` and `body` are REQUIRED by `notes.create`'s input
|
|
43
|
+
// schema — the old hand-written `useMutation<{ title: string }>`
|
|
44
|
+
// annotation hid that, so this call decoded-failed at runtime.
|
|
45
|
+
createNote.mutate({ tenantId: TENANT, title: title.trim(), body: '' })
|
|
36
46
|
setTitle('')
|
|
37
47
|
}}
|
|
38
48
|
style={{ backgroundColor: '#2563eb', borderRadius: 8, paddingHorizontal: 16, justifyContent: 'center' }}
|
|
@@ -12,7 +12,7 @@ export default function OrderDetail() {
|
|
|
12
12
|
<Text style={{ fontSize: 20, fontWeight: '700' }}>Order {id}</Text>
|
|
13
13
|
<Text style={{ color: '#666' }}>
|
|
14
14
|
Reached via the typed deep-link table. Fetch this order with
|
|
15
|
-
{' '}useSubscription('
|
|
15
|
+
{' '}useSubscription('orders.get', {'{'} id {'}'}) from ../../lib/api.
|
|
16
16
|
</Text>
|
|
17
17
|
</View>
|
|
18
18
|
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The typed hook surface for the `app` api — bound ONCE, here, and imported by
|
|
2
|
+
// every screen instead of `@voltro/client`.
|
|
3
|
+
//
|
|
4
|
+
// `AppProcedures` is emitted by codegen into the sibling api's
|
|
5
|
+
// `rpcGroup.generated.ts`: a type-level map of every rpc tag to the descriptor
|
|
6
|
+
// behind it. Feeding it to `createHooks` makes the tag a literal union and the
|
|
7
|
+
// input/output types INFER:
|
|
8
|
+
//
|
|
9
|
+
// const notes = useSubscription('notes.list') // rows, typed
|
|
10
|
+
// const create = useMutation('notes.create') // input + output typed
|
|
11
|
+
//
|
|
12
|
+
// So no screen declares a row `interface` mirroring the api any more. The old
|
|
13
|
+
// `useSubscription<ReadonlyArray<Note>>('app', 'notes.list')` spelled the api
|
|
14
|
+
// name at every call site, left the tag an unchecked string (a typo was a
|
|
15
|
+
// runtime error), and the `<T>` was an ASSERTION nothing compared against the
|
|
16
|
+
// server. All three are compile-checked now.
|
|
17
|
+
//
|
|
18
|
+
// `'app'` is the key this project's api is registered under. The api is a
|
|
19
|
+
// type-only import — `import type` is erased, so Metro never resolves it and
|
|
20
|
+
// nothing server-side reaches the bundle. It is a devDependency for exactly
|
|
21
|
+
// that reason; scaffold the pair (`create-project --api=api-backend --mobile`),
|
|
22
|
+
// which is what this template's README already assumes.
|
|
23
|
+
//
|
|
24
|
+
// RUNTIME NOTE: the hooks are typed today and live once the mobile codegen
|
|
25
|
+
// target lands — `buildApiHandles()` in ../client.ts still returns an empty map
|
|
26
|
+
// (see the seam documented there), so screens render their loading state until
|
|
27
|
+
// then. The TYPES are real either way; they come from the api's own descriptors.
|
|
28
|
+
import { createHooks } from '@voltro/client'
|
|
29
|
+
import type { AppProcedures } from '@{{projectName}}/api/rpcGroup'
|
|
30
|
+
|
|
31
|
+
// Destructured on purpose — `react-hooks/rules-of-hooks` only treats a member
|
|
32
|
+
// call as a hook when the object is PascalCase, so top-level `useX` identifiers
|
|
33
|
+
// are what keep the React lint rules working at every call site.
|
|
34
|
+
export const { useSubscription, useMutation, useAction } = createHooks<AppProcedures>('app')
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// The api(s) this mobile app consumes — mirrors a web app.config.ts's `apis:`
|
|
2
|
-
// map. The NAME ('app') is the lookup key the hooks
|
|
3
|
-
// `
|
|
2
|
+
// map. The NAME ('app') is the lookup key the hooks bind to: `src/lib/api.ts`
|
|
3
|
+
// spells it ONCE via `createHooks<AppProcedures>('app')`, and every screen then
|
|
4
|
+
// calls `useSubscription('notes.list')` / `useMutation('notes.create')` with no
|
|
5
|
+
// api name and no hand-written result type.
|
|
4
6
|
// The `package` resolves the sibling api in this project so codegen can pull its
|
|
5
7
|
// typed rpc surface (end-to-end types on a phone). Scaffold the api with
|
|
6
8
|
// `--api=api-backend`.
|
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
# override .env values, so CI can set DB_URL via env without touching
|
|
6
6
|
# this file.
|
|
7
7
|
|
|
8
|
+
# ── Environment ────────────────────────────────────────────────────────
|
|
9
|
+
#
|
|
10
|
+
# DECLARE IT. Every `voltro db …` / `voltro migrate` invocation resolves an
|
|
11
|
+
# UNSET `NODE_ENV` to `production` — the same way `voltro serve` and `voltro
|
|
12
|
+
# start` do — so a bare `pnpm migrate` with no NODE_ENV refuses rather than
|
|
13
|
+
# applying an un-reviewed diff to what might be a production database. It also
|
|
14
|
+
# decides which `_voltro_*` bookkeeping tables the command declares, and a
|
|
15
|
+
# migration command that resolves it differently from the serving process
|
|
16
|
+
# declares a DIFFERENT schema.
|
|
17
|
+
#
|
|
18
|
+
# `voltro dev` declares `development` for itself and needs nothing from here;
|
|
19
|
+
# this line is what makes the explicit schema commands work locally.
|
|
20
|
+
NODE_ENV=development
|
|
21
|
+
|
|
8
22
|
# Postgres connection. Bring your own (host install, docker run,
|
|
9
23
|
# cloud provider URL).
|
|
10
24
|
DB_URL=postgres://app:app@localhost:5432/{{projectName}}
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"deploy/voltro.service.example"
|
|
24
24
|
],
|
|
25
25
|
"packageJsonScripts": {
|
|
26
|
-
"dev": "
|
|
27
|
-
"build": "
|
|
28
|
-
"test": "
|
|
29
|
-
"typecheck": "
|
|
26
|
+
"dev": "pnpm -r --parallel dev",
|
|
27
|
+
"build": "pnpm -r build",
|
|
28
|
+
"test": "pnpm -r test",
|
|
29
|
+
"typecheck": "pnpm -r typecheck"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
# override .env values, so CI can set DB_URL via env without touching
|
|
6
6
|
# this file.
|
|
7
7
|
|
|
8
|
+
# ── Environment ────────────────────────────────────────────────────────
|
|
9
|
+
#
|
|
10
|
+
# DECLARE IT. Every `voltro db …` / `voltro migrate` invocation resolves an
|
|
11
|
+
# UNSET `NODE_ENV` to `production` — the same way `voltro serve` and `voltro
|
|
12
|
+
# start` do — so a bare `pnpm migrate` with no NODE_ENV refuses rather than
|
|
13
|
+
# applying an un-reviewed diff to what might be a production database. It also
|
|
14
|
+
# decides which `_voltro_*` bookkeeping tables the command declares, and a
|
|
15
|
+
# migration command that resolves it differently from the serving process
|
|
16
|
+
# declares a DIFFERENT schema.
|
|
17
|
+
#
|
|
18
|
+
# `voltro dev` declares `development` for itself and needs nothing from here;
|
|
19
|
+
# this line is what makes the explicit schema commands work locally.
|
|
20
|
+
NODE_ENV=development
|
|
21
|
+
|
|
8
22
|
# Postgres connection — points at the postgres service in
|
|
9
23
|
# docker-compose.yml (port 5433 to avoid clashing with a host pg on 5432).
|
|
10
24
|
# When you run inside the docker network (dev:docker / prod:up), the
|