@voltro/cli 0.28.0 → 0.30.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 +655 -0
- package/THIRD-PARTY-NOTICES.md +90 -4
- package/dist/{apiBuild-eUM32r1u.js → apiBuild-BceikpQf.js} +3 -3
- package/dist/apiBuild-CxvR1Dwt.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-BMr-X48w.js +2427 -0
- package/dist/checkCommand-BOk9y264.js +2 -0
- package/dist/cliRuntime-Oh517vCV.js +96 -0
- package/dist/{commands-CSkrUI1h.js → commands-D4Y1-Nlu.js} +8575 -9366
- package/dist/{dbCommand-CpYgmSw4.js → dbCommand-C7hbl5pk.js} +345 -266
- package/dist/dbCommand-D8nhyppR.js +2 -0
- package/dist/{dev-DlBWWnJQ.js → dev-Cr-teYsz.js} +1 -1
- package/dist/{dev-CEZwJhmb.js → dev-DmnkH-wr.js} +2939 -1832
- package/dist/fileConventions-Cof68_BL.js +33 -0
- package/dist/{frameworkTableAssembly-BwHU9Euq.js → frameworkTableAssembly-BzAajuUv.js} +115 -99
- package/dist/frameworkTableAssembly-DU0aOxjL.js +2 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +1 -1
- package/dist/{inspect-CUCCzw2I.js → inspect-BUUjt773.js} +349 -192
- package/dist/inspect-mmBuRXmy.js +2 -0
- package/dist/{inspectMetrics-BU90mvJN.js → inspectMetrics-BeJdP_3b.js} +1068 -978
- package/dist/{manifestBuild-BnzAxp2O.js → manifestBuild-BLrVuSlM.js} +1 -1
- package/dist/manifestBuild-Dj8Jjoto.js +2 -0
- package/dist/sdkgen-CqpOq_fy.js +582 -0
- package/dist/serveCommand-Ciq_wIY8.js +1577 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{start-BGXIf6zT.js → start-ocqB1seX.js} +283 -270
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.core.md +50 -0
- package/templates/AGENTS.md +54 -2
- package/templates/agent-docs/_index.md +4 -2
- package/templates/agent-docs/_manifest.json +26 -5
- package/templates/agent-docs/ai.md +191 -0
- package/templates/agent-docs/cli.md +143 -6
- package/templates/agent-docs/configuration.md +32 -0
- package/templates/agent-docs/data.md +282 -0
- package/templates/agent-docs/database/migrations.md +47 -0
- package/templates/agent-docs/database/misc.md +57 -0
- package/templates/agent-docs/database/schema.md +3 -1
- package/templates/agent-docs/internationalization.md +169 -8
- package/templates/agent-docs/local-first-mobile.md +426 -0
- package/templates/agent-docs/routing.md +94 -3
- package/templates/agent-docs/schema-driven-ui.md +12 -0
- package/templates/agent-docs/security.md +17 -0
- package/templates/agent-docs/templates/apibackends.md +210 -1
- package/templates/agent-docs/templates/appshells.md +144 -1
- package/templates/agent-docs/templates/mobile.md +60 -0
- package/templates/agent-docs/templates/overview.md +17 -7
- package/templates/agent-docs/whats-new.md +238 -158
- package/templates/agent-docs/workflows.md +376 -0
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/.env.example +19 -0
- package/templates/apps/api-backend-sqlite/README.md +38 -0
- package/templates/apps/api-backend-sqlite/app.config.ts +35 -0
- package/templates/apps/api-backend-sqlite/database/schema.ts +54 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-sqlite/package.json +30 -0
- package/templates/apps/api-backend-sqlite/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-sqlite/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-sqlite/template.json +6 -0
- package/templates/apps/api-backend-sqlite/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-sqlite/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/README.md +104 -0
- package/templates/apps/api-cms/actions/content.get.action.server.ts +27 -0
- package/templates/apps/api-cms/actions/content.get.action.ts +19 -0
- package/templates/apps/api-cms/actions/content.types.action.server.ts +26 -0
- package/templates/apps/api-cms/actions/content.types.action.ts +40 -0
- package/templates/apps/api-cms/actions/me.action.server.ts +18 -0
- package/templates/apps/api-cms/actions/me.action.ts +16 -0
- package/templates/apps/api-cms/app.config.ts +61 -0
- package/templates/apps/api-cms/content/blogPost.contentType.ts +39 -0
- package/templates/apps/api-cms/content/index.ts +18 -0
- package/templates/apps/api-cms/content/page.contentType.ts +24 -0
- package/templates/apps/api-cms/database/schema.ts +64 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.server.ts +19 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.ts +15 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.server.ts +36 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +32 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.server.ts +19 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +11 -0
- package/templates/apps/api-cms/package.json +32 -0
- package/templates/apps/api-cms/queries/content.list.query.server.ts +0 -0
- package/templates/apps/api-cms/queries/content.list.query.ts +27 -0
- package/templates/apps/api-cms/template.json +6 -0
- package/templates/apps/api-cms/tests/content.descriptors.test.ts +64 -0
- package/templates/apps/api-cms/tests/content.write.test.ts +85 -0
- package/templates/apps/api-cms/tsconfig.json +5 -0
- package/templates/apps/api-collab/README.md +70 -0
- package/templates/apps/api-collab/app.config.ts +28 -0
- package/templates/apps/api-collab/database/schema.ts +63 -0
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +19 -0
- package/templates/apps/api-collab/mutations/documents.create.mutation.ts +36 -0
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +20 -0
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +30 -0
- package/templates/apps/api-collab/package.json +30 -0
- package/templates/apps/api-collab/queries/documents.query.server.ts +16 -0
- package/templates/apps/api-collab/queries/documents.query.ts +23 -0
- package/templates/apps/api-collab/template.json +6 -0
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +89 -0
- package/templates/apps/api-collab/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/README.md +103 -0
- package/templates/apps/api-saas-starter/actions/me.action.server.ts +18 -0
- package/templates/apps/api-saas-starter/actions/me.action.ts +20 -0
- package/templates/apps/api-saas-starter/app.config.ts +87 -0
- package/templates/apps/api-saas-starter/database/schema.ts +57 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.server.ts +26 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +18 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.server.ts +29 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas-starter/package.json +32 -0
- package/templates/apps/api-saas-starter/queries/invites.list.query.server.ts +14 -0
- package/templates/apps/api-saas-starter/queries/invites.list.query.ts +17 -0
- package/templates/apps/api-saas-starter/queries/projects.list.query.server.ts +14 -0
- package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas-starter/template.json +6 -0
- package/templates/apps/api-saas-starter/tests/projects.create.test.ts +62 -0
- package/templates/apps/api-saas-starter/tests/session.test.ts +33 -0
- package/templates/apps/api-saas-starter/tsconfig.json +5 -0
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/README.md +88 -0
- package/templates/apps/api-status/app.config.ts +36 -0
- package/templates/apps/api-status/authz.ts +33 -0
- package/templates/apps/api-status/database/schema.ts +70 -0
- package/templates/apps/api-status/mutations/components.create.mutation.server.ts +19 -0
- package/templates/apps/api-status/mutations/components.create.mutation.ts +16 -0
- package/templates/apps/api-status/mutations/incidents.create.mutation.server.ts +25 -0
- package/templates/apps/api-status/mutations/incidents.create.mutation.ts +22 -0
- package/templates/apps/api-status/mutations/incidents.resolve.mutation.server.ts +24 -0
- package/templates/apps/api-status/mutations/incidents.resolve.mutation.ts +19 -0
- package/templates/apps/api-status/mutations/incidents.update.mutation.server.ts +26 -0
- package/templates/apps/api-status/mutations/incidents.update.mutation.ts +21 -0
- package/templates/apps/api-status/package.json +30 -0
- package/templates/apps/api-status/queries/components.list.query.server.ts +14 -0
- package/templates/apps/api-status/queries/components.list.query.ts +13 -0
- package/templates/apps/api-status/queries/incidents.live.query.server.ts +14 -0
- package/templates/apps/api-status/queries/incidents.live.query.ts +20 -0
- package/templates/apps/api-status/queries/updates.list.query.server.ts +14 -0
- package/templates/apps/api-status/queries/updates.list.query.ts +17 -0
- package/templates/apps/api-status/template.json +6 -0
- package/templates/apps/api-status/tests/status.test.ts +70 -0
- package/templates/apps/api-status/tsconfig.json +5 -0
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/app.config.ts +1 -1
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-admin/src/locales/index.ts +1 -1
- package/templates/apps/frontend-app/app.config.ts +1 -1
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-app/src/locales/index.ts +1 -1
- package/templates/apps/frontend-app/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-app/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-auth/README.md +78 -0
- package/templates/apps/frontend-auth/app.config.ts +34 -0
- package/templates/apps/frontend-auth/package.json +32 -0
- package/templates/apps/frontend-auth/src/components/AuthShell.tsx +35 -0
- package/templates/apps/frontend-auth/src/components/PasswordStrength.tsx +33 -0
- package/templates/apps/frontend-auth/src/config.ts +11 -0
- package/templates/apps/frontend-auth/src/globals.css +105 -0
- package/templates/apps/frontend-auth/src/globals.d.ts +6 -0
- package/templates/apps/frontend-auth/src/lib/auth.ts +34 -0
- package/templates/apps/frontend-auth/src/lib/redirect.test.ts +24 -0
- package/templates/apps/frontend-auth/src/lib/redirect.ts +29 -0
- package/templates/apps/frontend-auth/src/locales/de.ts +66 -0
- package/templates/apps/frontend-auth/src/locales/en.ts +76 -0
- package/templates/apps/frontend-auth/src/locales/index.ts +14 -0
- package/templates/apps/frontend-auth/src/pages/forgot/page.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-auth/src/pages/login/page.test.tsx +53 -0
- package/templates/apps/frontend-auth/src/pages/login/page.tsx +67 -0
- package/templates/apps/frontend-auth/src/pages/logout/page.tsx +28 -0
- package/templates/apps/frontend-auth/src/pages/magic/page.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/page.tsx +26 -0
- package/templates/apps/frontend-auth/src/pages/reset/page.test.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/reset/page.tsx +63 -0
- package/templates/apps/frontend-auth/src/pages/signup/page.tsx +60 -0
- package/templates/apps/frontend-auth/src/pages/verify/page.tsx +50 -0
- package/templates/apps/frontend-auth/template.json +6 -0
- package/templates/apps/frontend-auth/tsconfig.json +5 -0
- package/templates/apps/frontend-blank/app.config.ts +1 -1
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-blank/src/locales/index.ts +1 -1
- package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-blank/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-cms/README.md +47 -0
- package/templates/apps/frontend-cms/app.config.ts +37 -0
- package/templates/apps/frontend-cms/package.json +33 -0
- package/templates/apps/frontend-cms/src/config.ts +8 -0
- package/templates/apps/frontend-cms/src/globals.css +105 -0
- package/templates/apps/frontend-cms/src/globals.d.ts +6 -0
- package/templates/apps/frontend-cms/src/lib/api.ts +58 -0
- package/templates/apps/frontend-cms/src/locales/de.ts +50 -0
- package/templates/apps/frontend-cms/src/locales/en.ts +55 -0
- package/templates/apps/frontend-cms/src/locales/index.ts +14 -0
- package/templates/apps/frontend-cms/src/pages/(app)/error.tsx +18 -0
- package/templates/apps/frontend-cms/src/pages/(app)/layout.test.tsx +31 -0
- package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +62 -0
- package/templates/apps/frontend-cms/src/pages/(app)/not-found.tsx +14 -0
- package/templates/apps/frontend-cms/src/pages/(app)/page.test.tsx +110 -0
- package/templates/apps/frontend-cms/src/pages/(app)/page.tsx +159 -0
- package/templates/apps/frontend-cms/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-cms/src/pages/login/page.test.tsx +58 -0
- package/templates/apps/frontend-cms/src/pages/login/page.tsx +93 -0
- package/templates/apps/frontend-cms/template.json +6 -0
- package/templates/apps/frontend-cms/tsconfig.json +5 -0
- package/templates/apps/frontend-collab/README.md +80 -0
- package/templates/apps/frontend-collab/app.config.ts +38 -0
- package/templates/apps/frontend-collab/package.json +33 -0
- package/templates/apps/frontend-collab/src/locales/de.ts +29 -0
- package/templates/apps/frontend-collab/src/locales/en.ts +31 -0
- package/templates/apps/frontend-collab/src/locales/index.ts +15 -0
- package/templates/apps/frontend-collab/src/pages/layout.tsx +39 -0
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +150 -0
- package/templates/apps/frontend-collab/src/pages/page.tsx +186 -0
- package/templates/apps/frontend-collab/template.json +6 -0
- package/templates/apps/frontend-collab/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/app.config.ts +1 -1
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-dashboard/src/locales/index.ts +1 -1
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/README.md +71 -0
- package/templates/apps/frontend-portal/app.config.ts +37 -0
- package/templates/apps/frontend-portal/package.json +32 -0
- package/templates/apps/frontend-portal/src/config.ts +8 -0
- package/templates/apps/frontend-portal/src/globals.css +93 -0
- package/templates/apps/frontend-portal/src/globals.d.ts +6 -0
- package/templates/apps/frontend-portal/src/lib/api.ts +62 -0
- package/templates/apps/frontend-portal/src/locales/de.ts +86 -0
- package/templates/apps/frontend-portal/src/locales/en.ts +93 -0
- package/templates/apps/frontend-portal/src/locales/index.ts +14 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/api-keys/page.test.tsx +71 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/api-keys/page.tsx +124 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/billing/page.test.tsx +86 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/billing/page.tsx +97 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/error.tsx +19 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/invoices/page.tsx +45 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.test.tsx +31 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +63 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/not-found.tsx +15 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/page.tsx +39 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/profile/page.tsx +79 -0
- package/templates/apps/frontend-portal/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-portal/src/pages/login/page.test.tsx +58 -0
- package/templates/apps/frontend-portal/src/pages/login/page.tsx +96 -0
- package/templates/apps/frontend-portal/template.json +6 -0
- package/templates/apps/frontend-portal/tsconfig.json +5 -0
- package/templates/apps/frontend-saas/README.md +73 -0
- package/templates/apps/frontend-saas/app.config.ts +46 -0
- package/templates/apps/frontend-saas/package.json +32 -0
- package/templates/apps/frontend-saas/src/config.ts +8 -0
- package/templates/apps/frontend-saas/src/globals.css +85 -0
- package/templates/apps/frontend-saas/src/globals.d.ts +6 -0
- package/templates/apps/frontend-saas/src/lib/api.ts +47 -0
- package/templates/apps/frontend-saas/src/locales/de.ts +72 -0
- package/templates/apps/frontend-saas/src/locales/en.ts +81 -0
- package/templates/apps/frontend-saas/src/locales/index.ts +14 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +30 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/login/page.test.tsx +58 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/login/page.tsx +96 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/page.tsx +27 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +72 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/error.tsx +20 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.test.tsx +32 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/not-found.tsx +17 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +113 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +76 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +73 -0
- package/templates/apps/frontend-saas/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-saas/template.json +6 -0
- package/templates/apps/frontend-saas/tsconfig.json +5 -0
- package/templates/apps/frontend-spa/app.config.ts +1 -1
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-spa/src/locales/index.ts +1 -1
- package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-spa/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-ssr/app.config.ts +1 -1
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr/src/locales/index.ts +1 -1
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-ssr/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-ssr-api/app.config.ts +1 -1
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +1 -1
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/README.md +51 -0
- package/templates/apps/frontend-status/app.config.ts +39 -0
- package/templates/apps/frontend-status/package.json +32 -0
- package/templates/apps/frontend-status/src/config.ts +7 -0
- package/templates/apps/frontend-status/src/globals.css +70 -0
- package/templates/apps/frontend-status/src/globals.d.ts +6 -0
- package/templates/apps/frontend-status/src/lib/status.ts +81 -0
- package/templates/apps/frontend-status/src/locales/de.ts +41 -0
- package/templates/apps/frontend-status/src/locales/en.ts +45 -0
- package/templates/apps/frontend-status/src/locales/index.ts +13 -0
- package/templates/apps/frontend-status/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-status/src/pages/page.test.tsx +123 -0
- package/templates/apps/frontend-status/src/pages/page.tsx +136 -0
- package/templates/apps/frontend-status/template.json +6 -0
- package/templates/apps/frontend-status/tsconfig.json +11 -0
- package/templates/apps/mobile-app/README.md +58 -0
- package/templates/apps/mobile-app/app.config.ts +33 -0
- package/templates/apps/mobile-app/babel.config.js +5 -0
- package/templates/apps/mobile-app/metro.config.js +20 -0
- package/templates/apps/mobile-app/package.json +33 -0
- package/templates/apps/mobile-app/src/app/_layout.tsx +61 -0
- package/templates/apps/mobile-app/src/app/index.tsx +62 -0
- package/templates/apps/mobile-app/src/app/orders/[id].tsx +19 -0
- package/templates/apps/mobile-app/src/app/settings.tsx +51 -0
- package/templates/apps/mobile-app/src/client.ts +74 -0
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +39 -0
- package/templates/apps/mobile-app/src/lib/notifications.ts +31 -0
- package/templates/apps/mobile-app/src/lib/sync.ts +29 -0
- package/templates/apps/mobile-app/src/persistence.ts +38 -0
- package/templates/apps/mobile-app/template.json +6 -0
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +32 -0
- package/templates/apps/mobile-app/tests/notifications.test.ts +26 -0
- package/templates/apps/mobile-app/tests/sync.test.ts +34 -0
- package/templates/apps/mobile-app/tsconfig.json +10 -0
- package/templates/apps/mobile-app/voltro.mobile.ts +11 -0
- package/templates/baselines/compose/docker-compose.prod.yml +15 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +15 -0
- package/templates/patches/@effect__cluster@0.60.0.patch +6 -6
- package/dist/apiBuild-DgBS9ayv.js +0 -2
- package/dist/dbCommand-DvguqlzF.js +0 -2
- package/dist/fileConventions-3bffWssN.js +0 -30
- package/dist/frameworkTableAssembly-lrjZtk0G.js +0 -2
- package/dist/inspect-gt8bq-Tz.js +0 -2
- package/dist/manifestBuild-ifczArzr.js +0 -2
- package/dist/serveCommand-ZTn-dPFa.js +0 -1425
|
@@ -296,6 +296,63 @@ who may receive the plaintext — a private note you decrypt *for its owner* is
|
|
|
296
296
|
valid case, so treating "encrypted" as "never to a client" would be wrong. State
|
|
297
297
|
the exposure policy explicitly.
|
|
298
298
|
|
|
299
|
+
## `.readableBy(...scopes)` — visible only to scoped subjects
|
|
300
|
+
|
|
301
|
+
The **graded middle of the same wire-exposure axis** as `.serverOnly()` — not a
|
|
302
|
+
fourth axis. A plain column is visible to everyone who can read the row;
|
|
303
|
+
`.serverOnly()` hides it from every client; `.readableBy(...)` sits between them —
|
|
304
|
+
the column reaches a subject only if it holds the scope:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { id, integer, text, table } from '@voltro/database'
|
|
308
|
+
|
|
309
|
+
export const invoices = table('invoices', {
|
|
310
|
+
id: id(),
|
|
311
|
+
number: text(), // visible to everyone
|
|
312
|
+
amountCents: integer().readableBy('billing:read'), // only billing-scoped subjects
|
|
313
|
+
taxNote: text().readableBy('billing:read', 'admin:pii'), // ANY of the two scopes
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**What it does:** a column marked `.readableBy(...scopes)` is stripped from the
|
|
318
|
+
wire OUTPUT for any subject holding NONE of the listed scopes, and present for
|
|
319
|
+
one holding **at least one** of them. Like `.serverOnly()` it changes nothing at
|
|
320
|
+
rest and nothing in the DDL — it is a wire concern only, so a server-internal
|
|
321
|
+
read (`ctx.store.query`) still sees the value; the strip applies only on the way
|
|
322
|
+
out to a client.
|
|
323
|
+
|
|
324
|
+
**Scope semantics:**
|
|
325
|
+
|
|
326
|
+
- **≥ 1 scope, OR-matched.** A subject sees the column iff it holds at least one
|
|
327
|
+
of the listed scopes — `.readableBy('a', 'b')` means "a OR b", not both.
|
|
328
|
+
- **Effective scopes.** The check is the framework's `hasEffectiveScope` — the
|
|
329
|
+
subject's RAW scopes **∪** its rbac role-derived scopes — so a role that grants
|
|
330
|
+
`billing:read` unlocks the column even when the scope is not listed directly on
|
|
331
|
+
the subject.
|
|
332
|
+
- **`admin:full` bypasses it.** A subject holding `admin:full` sees every
|
|
333
|
+
`.readableBy(...)` column, exactly as it satisfies every guard.
|
|
334
|
+
- **At least one scope is required.** `.readableBy()` with no scope would mean
|
|
335
|
+
"readable by nobody" — that is `.serverOnly()` — so both the empty call and a
|
|
336
|
+
blank scope string are rejected at declaration.
|
|
337
|
+
|
|
338
|
+
**`.serverOnly()` wins when both are present.** `.serverOnly()` is the all-hidden
|
|
339
|
+
end of the axis, so a column carrying both is hidden from EVERYONE — `admin:full`
|
|
340
|
+
included. `.readableBy()` only ever *narrows* an otherwise-visible column; it can
|
|
341
|
+
never re-expose a `.serverOnly()` one:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
amountCents: integer().readableBy('billing:read'), // scoped subjects see it; admin:full does too
|
|
345
|
+
secretKey: text().serverOnly().readableBy('billing:read'), // serverOnly wins → hidden from EVERYONE
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Enforcement shares the SAME chokepoint as `.serverOnly()` — the Dispatcher's read
|
|
349
|
+
boundary — but resolves **per subject**: it applies to a query's initial snapshot
|
|
350
|
+
AND every reactive subscription delta, and to the one-shot
|
|
351
|
+
[`publicApi`](/docs/data/rest-routes) REST GET, in both boot paths (`voltro dev`,
|
|
352
|
+
`voltro serve`). A table that declares no `.readableBy(...)` column pays nothing —
|
|
353
|
+
the strip collapses to the subject-independent `.serverOnly()` set and the
|
|
354
|
+
dispatcher's read/diff memo is still shared across all subscribers of a change.
|
|
355
|
+
|
|
299
356
|
## Worked example
|
|
300
357
|
|
|
301
358
|
```ts
|
|
@@ -280,7 +280,9 @@ Two layers enforce row invariants, and they're complementary:
|
|
|
280
280
|
|
|
281
281
|
- **`table().validate(Schema)` (below)** — app-level `effect/Schema` validation, run before the INSERT/UPDATE. Use it for the rich stuff a portable `CHECK` can't express: regex / email / structured strings, length ranges, numeric bounds, cross-field rules with custom typed errors. It's the single validation surface — don't reach for a pile of `.min()` / `.max()` / `.pattern()` column modifiers (there aren't any, by design).
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
- **`table().rule(name, predicate)` (cross-table)** — neither of the above can read ANOTHER table. A [business rule](/docs/data/mutations#cross-table-business-rules-rule) is a predicate the runtime evaluates inside the mutation transaction, so it can compare the written row against rows in other tables (an invoice total vs. its line items) and rolls the mutation back with a typed `BusinessRuleViolation` on failure.
|
|
284
|
+
|
|
285
|
+
Rule of thumb: **`.check()` when the DATABASE must guarantee a single-row constraint; `table().rule()` for a cross-table invariant; `validate(Schema)` for everything else.** There is no `.comment()` / `.index()` column modifier — indexes are table-level.
|
|
284
286
|
|
|
285
287
|
## Default with callback
|
|
286
288
|
|
|
@@ -45,13 +45,17 @@ The framework's generated `.framework/app.tsx` wraps the Router in `<I18nProvide
|
|
|
45
45
|
|
|
46
46
|
Server-side, the active locale is determined by, in priority order:
|
|
47
47
|
|
|
48
|
-
1. **`voltro:
|
|
48
|
+
1. **`voltro:locale` cookie** — the user's explicit choice (written by `@voltro/ui-shadcn`'s ProfileMenu).
|
|
49
49
|
2. **`Accept-Language` header** — the browser/OS preference, q-weighted and sorted per RFC 4647.
|
|
50
50
|
3. **`defaultLocale`** — last-resort fallback.
|
|
51
51
|
|
|
52
52
|
The resolved locale is **guaranteed** to be one of the codes in `locales`. Any unsupported value (a cookie pointing at a code you no longer ship, a browser asking for `xx-YY`) falls through to the next signal. RFC 4647 lookup strips subtags one segment at a time — `de-CH-1996` → `de-CH` → `de` — so a `de` catalog serves a `de-CH` browser.
|
|
53
53
|
|
|
54
|
-
The client
|
|
54
|
+
The client **adopts what the server resolved**, reading it from the `<html lang>` attribute the server render sets, then falling back to the cookie and the default. `Accept-Language` is never read in the browser: `navigator.languages` can diverge from what the server saw.
|
|
55
|
+
|
|
56
|
+
> Earlier versions said the client "mirrors cookie and default for hydration safety". That was the opposite of what happened. Dropping the `Accept-Language` signal is not the same as agreeing with the server about it — on a first visit, with no cookie yet, the server negotiated `Accept-Language` while the client fell through to `defaultLocale`. An English browser on a German-default app therefore hydrated `de` over an `en` tree and React discarded the entire server render, which is exactly what SSR was enabled to avoid. It stopped as soon as anything wrote the cookie, so one language switch made it un-reproducible for that developer.
|
|
57
|
+
|
|
58
|
+
`<html lang>` carries the same resolved locale — the value the `<I18nProvider>` renders with, on the same request. That matters on its own: it is what a screen reader pronounces in, what Chrome offers to translate *from*, and what hyphenation uses.
|
|
55
59
|
|
|
56
60
|
See [Catalogs](/docs/i18n/catalogs) for the type-safe catalog convention and the component hooks, [Plurals & formatting](/docs/i18n/formatting) for CLDR plural selection and the `Intl`-backed date / number / relative-time hooks, and [URL strategies](/docs/i18n/url-strategies) for cookie-only vs URL-prefix routing.
|
|
57
61
|
|
|
@@ -236,7 +240,7 @@ export const getCatalog = (locale?: string) => pickCatalog({ en, de }, locale, '
|
|
|
236
240
|
```
|
|
237
241
|
|
|
238
242
|
```tsx
|
|
239
|
-
// any page — meta gets the active locale (from the URL prefix, or the voltro:
|
|
243
|
+
// any page — meta gets the active locale (from the URL prefix, or the voltro:locale cookie)
|
|
240
244
|
import { getCatalog } from '../locales'
|
|
241
245
|
|
|
242
246
|
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
@@ -326,14 +330,14 @@ The wrap deliberately doesn't re-export everything, to keep the framework's bles
|
|
|
326
330
|
<!-- source: en/i18n/url-strategies.md -->
|
|
327
331
|
## URL strategies
|
|
328
332
|
|
|
329
|
-
_Cookie-only vs URL-prefix routing (Strategy A / B), the URL-prefix integration sketch, resolveLocale on the /server subpath, the voltro:
|
|
333
|
+
_Cookie-only vs URL-prefix routing (Strategy A / B), the URL-prefix integration sketch, resolveLocale on the /server subpath, the voltro:locale / voltro:theme cookie convention._
|
|
330
334
|
|
|
331
335
|
There are two ways an i18n app can encode the active locale. Pick **one**, based on what the app's URLs are *for*.
|
|
332
336
|
|
|
333
337
|
## Strategy A — cookie-only (default for product apps)
|
|
334
338
|
|
|
335
339
|
- All locales are served from the **same** URL (`/dashboard`, `/settings`).
|
|
336
|
-
- The `voltro:
|
|
340
|
+
- The `voltro:locale` cookie + `Accept-Language` determine which catalog renders.
|
|
337
341
|
- The ProfileMenu's language switch — or the standalone `<LocaleSwitcher>` from `@voltro/ui-shadcn` — writes the cookie and reloads.
|
|
338
342
|
- Use when URLs are **functional** (`/dashboard/projects/42/deployments`) and don't need to encode the language. This is most apps.
|
|
339
343
|
|
|
@@ -507,15 +511,16 @@ The `/server` subpath is convention. Both that path and the main entry are pure
|
|
|
507
511
|
|
|
508
512
|
## Cookie convention
|
|
509
513
|
|
|
510
|
-
The framework reads + writes **`voltro:
|
|
514
|
+
The framework reads + writes **`voltro:locale`** for the active language choice (lowercase IETF tag: `en`, `de`, `fr-CA`). `@voltro/ui-shadcn`'s ProfileMenu and the framework's auto-wired resolver agree on this name — **don't pick a different one in app code.**
|
|
511
515
|
|
|
512
|
-
For theme the parallel cookie is **`voltro:theme`** (values `'system' | 'light' | 'dark'`). Both are managed by the kit's ProfileMenu out of the box; the helpers live in `@voltro/ui-shadcn` (`THEME_COOKIE`, `
|
|
516
|
+
For theme the parallel cookie is **`voltro:theme`** (values `'system' | 'light' | 'dark'`). Both are managed by the kit's ProfileMenu out of the box; the helpers live in `@voltro/ui-shadcn` (`THEME_COOKIE`, `LOCALE_COOKIE`, `getCookie`, `setCookie`, `deleteCookie`, `parsePreferenceCookies`, `applyTheme`).
|
|
513
517
|
|
|
514
518
|
## Anti-patterns
|
|
515
519
|
|
|
516
520
|
- **Don't mix strategies within one app.** Pick cookie-only *or* URL-prefix per app; mixing them produces ambiguous canonical URLs and broken language switching.
|
|
517
521
|
- **Don't read `Accept-Language` on the client.** It's server-only — `navigator.languages` can diverge from what the server saw and cause a hydration mismatch.
|
|
518
|
-
- **Don't invent your own cookie name.** The kit and the resolver only agree on `voltro:
|
|
522
|
+
- **Don't invent your own cookie name.** The kit and the resolver only agree on `voltro:locale` / `voltro:theme`.
|
|
523
|
+
- **Don't hardcode the name either — import the constant.** `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/ui-shadcn`. A cookie name the framework READS and your app WRITES is a public API, and it is the only kind where both sides can disagree with nothing failing: the resolver finds nothing, falls back to `Accept-Language`, and a user's language choice quietly stops working — but only for the users whose browser language differs from their choice, which is the least likely case anyone tests. With the constant, a rename in the framework is a compile error in your app. `voltro doctor` flags a written literal for exactly this reason.
|
|
519
524
|
|
|
520
525
|
|
|
521
526
|
|
|
@@ -697,3 +702,159 @@ For dates, numbers and relative time the hooks are the blessed path; reach for `
|
|
|
697
702
|
- **Don't pin a global `timeZone` / locale for the whole app.** The formatters resolve the active locale from the provider; a pin makes every viewer read the app in one user's settings.
|
|
698
703
|
- **Don't pass a locale-formatted string to a machine consumer.** Formatted output is presentation — send ISO strings and raw numbers to APIs, `dateTime` attributes and sort keys.
|
|
699
704
|
- **Don't format inside a `.map()` by constructing `Intl` objects yourself.** The hooks memoize per locale; a fresh `new Intl.NumberFormat(...)` per row is the slow path.
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
<!-- source: en/i18n/datetime.md -->
|
|
711
|
+
## Dates & timezones
|
|
712
|
+
|
|
713
|
+
_"@voltro/datetime — UTC-instant storage, timezone-aware arithmetic/formatting on TC39 Temporal, and the request-scoped timezone-context seam."_
|
|
714
|
+
|
|
715
|
+
`@voltro/datetime` is the framework's opinion on time: **store UTC instants,
|
|
716
|
+
render in the viewer's timezone, never do zone math by hand.** It is built on the
|
|
717
|
+
[TC39 Temporal](https://tc39.es/proposal-temporal/docs/) standard (via a
|
|
718
|
+
polyfill, so the API is identical on the server and in the browser) and ships
|
|
719
|
+
pure, browser-safe helpers plus a request-scoped timezone seam.
|
|
720
|
+
|
|
721
|
+
The `.` entry is browser-safe — no `effect`, no `node:*` — so a route or
|
|
722
|
+
component can import it directly. The Effect seam lives in a separate subpath,
|
|
723
|
+
`@voltro/datetime/context`, so the pure surface carries no `effect` dependency.
|
|
724
|
+
|
|
725
|
+
> **Phase 1.** This is the storage + timezone + formatting layer. Schema-DSL
|
|
726
|
+
> temporal column types, `interval()`, and `rrule()` are later phases; use
|
|
727
|
+
> `timestamp()` columns and these helpers today.
|
|
728
|
+
|
|
729
|
+
## The storage contract: a `Date` is a UTC instant
|
|
730
|
+
|
|
731
|
+
`timestamp()` stores `TIMESTAMPTZ`, and the driver hands your app code a plain JS
|
|
732
|
+
`Date`. A `Date` is a bare epoch-millisecond count with no zone of its own, so
|
|
733
|
+
the ONLY correct reading of it is "the UTC instant it points at". These helpers
|
|
734
|
+
make that reading explicit and lossless in both directions:
|
|
735
|
+
|
|
736
|
+
```ts
|
|
737
|
+
import { toInstant, toUTCString, isValidTimeZone } from '@voltro/datetime'
|
|
738
|
+
|
|
739
|
+
// A `Date` from a query IS a UTC instant — read it as one, losslessly.
|
|
740
|
+
const when = toInstant(row.createdAt) // Temporal.Instant
|
|
741
|
+
toUTCString(row.createdAt) // "2026-08-06T12:00:00Z" — the wire/storage form
|
|
742
|
+
|
|
743
|
+
isValidTimeZone('Europe/Berlin') // true
|
|
744
|
+
isValidTimeZone('Mars/Phobos') // false
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
`toInstant` accepts a `Temporal.Instant`, a JS `Date`, or an ISO-8601 string —
|
|
748
|
+
but a string MUST carry an explicit offset or `Z`. A naive `2026-01-01T12:00`
|
|
749
|
+
denotes no instant and throws, by design: the whole point is that there is no
|
|
750
|
+
silent zone-guessing. Bridge back with `toDate(instant)` for storage or interop.
|
|
751
|
+
|
|
752
|
+
## Timezone resolution — the framework convention
|
|
753
|
+
|
|
754
|
+
Which zone should a given request render in? Resolve it in priority order, each
|
|
755
|
+
candidate validated as an IANA name, falling back to `UTC`:
|
|
756
|
+
|
|
757
|
+
```ts
|
|
758
|
+
import { resolveTimezone } from '@voltro/datetime'
|
|
759
|
+
|
|
760
|
+
const tz = resolveTimezone({
|
|
761
|
+
userTimeZone: user.timezone, // 1. the viewing user's profile
|
|
762
|
+
tenantTimeZone: tenant.timezone, // 2. the tenant / org default
|
|
763
|
+
browserTimeZone: 'Europe/Berlin', // 3. the browser-reported zone
|
|
764
|
+
}) // → a validated IANA name, else 'UTC'
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
This mirrors `@voltro/i18n`'s `resolveLocale` in spirit — pure, framework-agnostic,
|
|
768
|
+
no Effect or RPC — so it runs in any request pipeline. An invalid or absent
|
|
769
|
+
candidate falls through to the next signal; the result is GUARANTEED valid.
|
|
770
|
+
|
|
771
|
+
### Carrying it through a request — the context seam
|
|
772
|
+
|
|
773
|
+
The resolved zone travels through a request via an Effect context tag in
|
|
774
|
+
`@voltro/datetime/context`, so any route, mutation, workflow, or agent can read it:
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
import { currentTimezone, withTimezone } from '@voltro/datetime/context'
|
|
778
|
+
import { Effect } from 'effect'
|
|
779
|
+
|
|
780
|
+
const program = Effect.gen(function* () {
|
|
781
|
+
const tz = yield* currentTimezone // the request's zone, or 'UTC' when none set
|
|
782
|
+
return tz
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
// Provide an explicit zone for a sub-computation:
|
|
786
|
+
program.pipe(withTimezone('Europe/Berlin'))
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
`currentTimezone` never fails — an absent context is the documented `UTC`
|
|
790
|
+
default, so call sites don't handle a missing-service error. The runtime provides
|
|
791
|
+
the tag per request (via `resolvedTimezoneLayer`); the web layer's `useTimezone()`
|
|
792
|
+
hook is a thin projection over it.
|
|
793
|
+
|
|
794
|
+
## Arithmetic — the DST split is in the names
|
|
795
|
+
|
|
796
|
+
Every operation that is ambiguous without a zone REQUIRES an IANA `timeZone`
|
|
797
|
+
argument. There is no implicit "system zone", so a call site cannot silently do
|
|
798
|
+
the wrong thing on a differently-configured box. The DST distinction is
|
|
799
|
+
deliberate and encoded in the method names:
|
|
800
|
+
|
|
801
|
+
```ts
|
|
802
|
+
import { addDays, addHours, startOfDay, endOfDay } from '@voltro/datetime'
|
|
803
|
+
|
|
804
|
+
// WALL-CLOCK: same local time, one calendar day later in Berlin.
|
|
805
|
+
// Across a DST boundary the elapsed real time is 23h or 25h. Needs a zone.
|
|
806
|
+
addDays(when, 1, 'Europe/Berlin')
|
|
807
|
+
|
|
808
|
+
// EXACT elapsed time: 24 × 3600 seconds, DST-oblivious. No zone.
|
|
809
|
+
addHours(when, 24)
|
|
810
|
+
|
|
811
|
+
// Zone-relative day boundaries.
|
|
812
|
+
startOfDay(when, 'Europe/Berlin')
|
|
813
|
+
endOfDay(when, 'Europe/Berlin')
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
`addDays` / `addMonths` are wall-clock ("same local time, N days on"); `addHours`
|
|
817
|
+
is exact elapsed time. Comparisons that measure absolute instants (`isAfter`,
|
|
818
|
+
`isBefore`) take no zone; `isSameDay` does, because "same day" is a wall-clock
|
|
819
|
+
question. For a calendar date with no time and no zone — a birthday, a holiday, a
|
|
820
|
+
due date — use `plainDate(year, month, day)` / `parseDate('2026-08-06')`.
|
|
821
|
+
|
|
822
|
+
## Formatting — locale- and timezone-aware
|
|
823
|
+
|
|
824
|
+
Formatting is where the viewer's timezone and locale are APPLIED. Both are
|
|
825
|
+
explicit arguments — this layer carries no ambient locale (the web layer resolves
|
|
826
|
+
`useLocale()` and passes it in):
|
|
827
|
+
|
|
828
|
+
```ts
|
|
829
|
+
import { formatDate, formatDateTime, formatRelativeTime } from '@voltro/datetime'
|
|
830
|
+
|
|
831
|
+
formatDate(row.createdAt, 'Europe/Berlin', { locale: 'de' }) // "6. Aug. 2026"
|
|
832
|
+
formatDateTime(row.createdAt, 'America/New_York', { locale: 'en' })
|
|
833
|
+
formatRelativeTime(row.createdAt, { locale: 'en' }) // "3 hours ago"
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
`FormatOptions` is `{ locale? }` plus any `Intl.DateTimeFormatOptions` override,
|
|
837
|
+
so `formatDate(when, tz, { locale, dateStyle: 'full' })` works. `formatRelativeTime`
|
|
838
|
+
picks the largest unit that fits the signed distance from `now` (default: the
|
|
839
|
+
current instant) and is zone-independent — it measures elapsed real time, not
|
|
840
|
+
wall-clock days.
|
|
841
|
+
|
|
842
|
+
> These helpers are the STORAGE/TIMEZONE layer. `@voltro/i18n`'s
|
|
843
|
+
> [`useFormatDate` / `useRelativeTime`](/docs/i18n/formatting) hooks are the
|
|
844
|
+
> React binding that read the active locale from the provider; reach for those in
|
|
845
|
+
> components, and for `@voltro/datetime` in server code, loaders, and tests.
|
|
846
|
+
|
|
847
|
+
## Temporal directly
|
|
848
|
+
|
|
849
|
+
The exact standard `Temporal` types are re-exported, so you can drop to the full
|
|
850
|
+
API when a helper doesn't cover your case:
|
|
851
|
+
|
|
852
|
+
```ts
|
|
853
|
+
import { Temporal } from '@voltro/datetime'
|
|
854
|
+
|
|
855
|
+
const noon = Temporal.PlainTime.from('12:00')
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
Import `Temporal` from `@voltro/datetime`, never from the polyfill directly — that
|
|
859
|
+
keeps the eventual switch to the native global (once it is universal in browsers)
|
|
860
|
+
a one-line change for the whole codebase.
|