@voltro/cli 0.38.0 → 0.39.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 +224 -0
- package/THIRD-PARTY-NOTICES.md +80 -45
- package/dist/{addCommand-BNeoeSxe.js → addCommand-C05L9tZh.js} +2 -2
- package/dist/addCommand-C3MdJG-a.js +2 -0
- package/dist/{agentsMd-mhQMF1bx.js → agentsMd-7zI2h5l9.js} +3 -3
- package/dist/agentsMd-BFCXh2gl.js +2 -0
- package/dist/apiBuild-2GvK8CUB.js +2 -0
- package/dist/{apiBuild-CvacV4zA.js → apiBuild-DGalUk9v.js} +3 -3
- package/dist/baselineCommand-CXv680Dc.js +2 -0
- package/dist/{baselineCommand-C6NMt-oa.js → baselineCommand-Ck2Xm8M7.js} +18 -18
- package/dist/bin.js +1 -1
- package/dist/{build-_LoTC1s0.js → build-CfF6t0UM.js} +107 -100
- package/dist/{capabilitiesCommand-nq_pz5xd.js → capabilitiesCommand-D5gucwYa.js} +8 -8
- package/dist/{checkCommand-wibtQx-N.js → checkCommand-BV56Pcc1.js} +1 -1
- package/dist/{checkCommand-Pjk5sBl2.js → checkCommand-CBtynBKW.js} +34 -34
- package/dist/{cloudCmd-NQSwe_Qk.js → cloudCmd-C42gaO8s.js} +16 -16
- package/dist/{clusterCmd-D5wsCmA_.js → clusterCmd-DrVFCzSj.js} +8 -4
- package/dist/{codegen-GYYcdpCg.js → codegen-CbpGCWLG.js} +1 -1
- package/dist/codegen-i8QGcHsi.js +2 -0
- package/dist/codegenCommand-W7SDdiAQ.js +129 -0
- package/dist/{codemodRunner-CXgQ-ecJ.js → codemodRunner-D-jTyvWo.js} +622 -551
- package/dist/{commands-DaZVi9wi.js → commands-BD9eBRY3.js} +48 -48
- package/dist/{connectionConfig-UFlIEiys.js → connectionConfig-Bk9IC7D0.js} +1 -1
- package/dist/{dashboardCommand-Dxy_kmoW.js → dashboardCommand-7qGylm0F.js} +3 -3
- package/dist/{dataCommand-D6-Ha2lf.js → dataCommand-C_F2DYxe.js} +14 -11
- package/dist/{dbCommand-C4UW8cVN.js → dbCommand-DSwGv9wS.js} +50 -49
- package/dist/dbCommand-DcqEyxju.js +2 -0
- package/dist/dev-alhkKoEX.js +3 -0
- package/dist/{dev-E7Eo5GHc.js → dev-gqpnzVhI.js} +2260 -2184
- package/dist/{dialectDriver-CgXnDfec.js → dialectDriver-czCHYpeH.js} +2 -1
- package/dist/{doctorCommand-gmDyALFL.js → doctorCommand-Bvs-BQrM.js} +242 -238
- package/dist/doctorCommand-CVXfRrng.js +2 -0
- package/dist/{dormancyCommand-CE6wG8lj.js → dormancyCommand-B-PPHc9Q.js} +1 -1
- package/dist/{e2eCmd-BRabZww-.js → e2eCmd-uFHig1hV.js} +8 -2
- package/dist/{embeddingsCommand-C3uAuf6N.js → embeddingsCommand-OKY6XjUf.js} +6 -6
- package/dist/{envCommand-DzuWh8ya.js → envCommand-B1-Zm85H.js} +19 -19
- package/dist/{evolveCommand-DPLqcYb2.js → evolveCommand-C4-NlFbd.js} +6 -8
- package/dist/{generateCommand-DbgcUpGw.js → generateCommand-CcyvH2ve.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{infoCommand-CmczGj_l.js → infoCommand-CTo8Jnhq.js} +7 -7
- package/dist/inspect-B7U7Cl_Z.js +1192 -0
- package/dist/inspect-DUze25t0.js +2 -0
- package/dist/{inspectCmd-EHFZ9yYu.js → inspectCmd-niF97fAq.js} +5 -5
- package/dist/inspectGateHint-BjnFubmH.js +7 -0
- package/dist/{logFileSink-C_D2wRN1.js → logFileSink-B4uP8pvf.js} +1 -1
- package/dist/{logsCmd-D36xK7Zu.js → logsCmd-B6oNsfaZ.js} +10 -7
- package/dist/{manifestBuild-hpPLaGxV.js → manifestBuild-BK42hu0k.js} +1 -1
- package/dist/manifestBuild-j0n109tt.js +2 -0
- package/dist/{metaCommands-CIoQNlaQ.js → metaCommands-DUYR--Ts.js} +1 -1
- package/dist/{migrate-RwgUWXfc.js → migrate-BnPw2zC8.js} +7 -4
- package/dist/{packageCommand-Cug_3Ogl.js → packageCommand-9SVyvsSo.js} +3 -2
- package/dist/{probeCommand-BPEpwT32.js → probeCommand-C9gazU0H.js} +35 -24
- package/dist/{projectScaffold-CxgtJvlb.js → projectScaffold-BEhhHjPr.js} +1 -1
- package/dist/{projectScaffold-DDgWbNLe.js → projectScaffold-BvhLOrLq.js} +17 -15
- package/dist/{runtimeTrace-6jfznSzr.js → runtimeTrace-DzZOCd94.js} +1 -1
- package/dist/{scheduleManifestCmd-D2x0CTTY.js → scheduleManifestCmd-kmWzrO_w.js} +6 -9
- package/dist/{sdkgen-CEJZZQUA.js → sdkgen-C4roLErM.js} +4 -4
- package/dist/{seedRunner-ZmLSqNe2.js → seedRunner-IdHEprqf.js} +1 -4
- package/dist/serveCommand-B6TATyCj.js +1770 -0
- package/dist/serveCommand-CNR0gI9V.js +2 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{serverlessCommand-CfJZy6dS.js → serverlessCommand-DbJ7Plg9.js} +3 -3
- package/dist/{start-CidwCcjl.js → start-BNuTWdRd.js} +1 -1
- package/dist/{start-RCh6qKFe.js → start-ft_KzFTd.js} +257 -254
- package/dist/startEntry.js +1 -1
- package/dist/{test-DIQ0jlkQ.js → test-CLYXrZ2F.js} +1 -1
- package/dist/{tracesCmd-DStmCJPi.js → tracesCmd-DgtgOUdi.js} +7 -4
- package/dist/{typecheckCommand-BlsWiCNq.js → typecheckCommand-BuCtT92o.js} +7 -7
- package/dist/updateCommand-6FMU2klq.js +2 -0
- package/dist/{updateCommand-CmksX7m_.js → updateCommand-CHBmCB17.js} +5 -3
- package/dist/{webDev-BKdD7c_a.js → webDev-CTpSY-e_.js} +813 -775
- package/dist/webDev-id3I5PvG.js +2 -0
- package/dist/{webhookDiscovery-D7VaeMlz.js → webhookDiscovery-CphsQe59.js} +4 -6
- package/dist/{webhookDiscovery-CrGAfhIG.js → webhookDiscovery-il9ti-HE.js} +1 -1
- package/dist/{webhooksCommand-BHeaF1ZJ.js → webhooksCommand-BVvOtZ1B.js} +1 -1
- package/package.json +27 -21
- package/templates/AGENTS.md +2 -2
- package/templates/agent-docs/_index.md +2 -2
- package/templates/agent-docs/_manifest.json +2 -2
- package/templates/agent-docs/authentication.md +30 -0
- package/templates/agent-docs/cli.md +17 -13
- package/templates/agent-docs/data.md +44 -1
- package/templates/agent-docs/database/migrations.md +2 -2
- package/templates/agent-docs/database/seedsdialects.md +2 -2
- package/templates/agent-docs/deployment.md +21 -7
- package/templates/agent-docs/internationalization.md +10 -3
- package/templates/agent-docs/local-first-mobile.md +141 -20
- package/templates/agent-docs/plugins/mail.md +1 -1
- package/templates/agent-docs/plugins/ratelimit.md +1 -1
- package/templates/agent-docs/plugins.md +3 -1
- package/templates/agent-docs/releases.md +130 -1
- package/templates/agent-docs/routing.md +34 -2
- package/templates/agent-docs/templates/apibackends.md +1 -1
- package/templates/agent-docs/templates/mobile.md +1 -1
- package/templates/agent-docs/whats-new.md +181 -49
- 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/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- 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/authz.ts +16 -1
- 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/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- 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/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +10 -10
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- 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/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/README.md +41 -17
- package/templates/apps/mobile-app/app.config.ts +1 -1
- package/templates/apps/mobile-app/package.json +15 -11
- package/templates/apps/mobile-app/src/app/_layout.tsx +44 -13
- package/templates/apps/mobile-app/src/app/index.tsx +1 -1
- package/templates/apps/mobile-app/src/app/settings.tsx +4 -1
- package/templates/apps/mobile-app/src/client.ts +74 -57
- package/templates/apps/mobile-app/src/lib/api.ts +4 -4
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +6 -6
- package/templates/apps/mobile-app/src/persistence.ts +24 -3
- package/templates/apps/mobile-app/tsconfig.json +17 -3
- package/dist/addCommand-aXSQveak.js +0 -2
- package/dist/agentsMd-BTchIZku.js +0 -2
- package/dist/apiBuild-ttSDDOGp.js +0 -2
- package/dist/baselineCommand-Cfw2Afwm.js +0 -2
- package/dist/codegen-uYrrDzQv.js +0 -2
- package/dist/codegenCommand-C60m_LCp.js +0 -30
- package/dist/dbCommand-CC-qNzpz.js +0 -2
- package/dist/dev-CAOoXOkc.js +0 -3
- package/dist/doctorCommand-Cjl8Yii8.js +0 -2
- package/dist/inspect-CjTYzAs_.js +0 -1190
- package/dist/inspect-P4pxoMaV.js +0 -2
- package/dist/inspectGateHint-BF6608UT.js +0 -4
- package/dist/manifestBuild-COkJoyAr.js +0 -2
- package/dist/serveCommand-DNRF2nDI.js +0 -2
- package/dist/serveCommand-DnY4VS3D.js +0 -1767
- package/dist/updateCommand-B0J_T9Vs.js +0 -2
- package/dist/webDev-CcJfdSjl2.js +0 -2
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
// Root layout — wires the Voltro client
|
|
2
|
-
//
|
|
1
|
+
// Root layout — wires the Voltro client, deep-link routing, and a connection
|
|
2
|
+
// banner around the whole app.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// The boot ORDER here is load-bearing, and it is the one thing to preserve if
|
|
5
|
+
// you rewrite this file:
|
|
6
|
+
//
|
|
7
|
+
// 1. hydrate persisted stores, THEN install them as the backing store;
|
|
8
|
+
// 2. only then render the app.
|
|
9
|
+
//
|
|
10
|
+
// A store reads during render and a render cannot await, so rendering first
|
|
11
|
+
// shows empty state and flickers into the saved state a frame later. The splash
|
|
12
|
+
// screen stays up until step 1 finishes — that is what it is for.
|
|
13
|
+
//
|
|
14
|
+
// Connecting is deliberately NOT part of that gate. The app must open offline.
|
|
7
15
|
import { useEffect, useState } from 'react'
|
|
8
16
|
import { Stack, useRouter } from 'expo-router'
|
|
9
17
|
import * as Linking from 'expo-linking'
|
|
18
|
+
import * as SplashScreen from 'expo-splash-screen'
|
|
10
19
|
import { StatusBar } from 'expo-status-bar'
|
|
11
|
-
import { FrameworkRuntimesProvider, type ApiHandle } from '@voltro/client'
|
|
20
|
+
import { FrameworkRuntimesProvider, type ApiHandle, type ResolvedClient } from '@voltro/client'
|
|
12
21
|
import { useMobileConnectionStatus } from '@voltro/react-native'
|
|
13
22
|
import { Text, View } from 'react-native'
|
|
14
|
-
import {
|
|
23
|
+
import { connectApis, handlesFor, onlineSource, preparePersistence } from '../client'
|
|
15
24
|
import { handleDeepLink, makeDeepLinks } from '../lib/deeplinks'
|
|
16
25
|
|
|
26
|
+
void SplashScreen.preventAutoHideAsync()
|
|
27
|
+
|
|
17
28
|
function ConnectionBanner() {
|
|
18
|
-
|
|
29
|
+
// Reachability comes from NetInfo. The default source reads `navigator.onLine`,
|
|
30
|
+
// which React Native does not have — so on a device it would answer "online"
|
|
31
|
+
// forever, including in airplane mode.
|
|
32
|
+
const { status } = useMobileConnectionStatus({ onlineSource })
|
|
19
33
|
if (status === 'connected') return null
|
|
20
34
|
return (
|
|
21
35
|
<View style={{ backgroundColor: status === 'offline' ? '#b91c1c' : '#b45309', padding: 8 }}>
|
|
@@ -27,16 +41,31 @@ function ConnectionBanner() {
|
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
export default function RootLayout() {
|
|
30
|
-
|
|
31
|
-
// @voltro/client's buildApiRuntime). Empty until it resolves; the hooks render
|
|
32
|
-
// their loading state meanwhile.
|
|
44
|
+
const [ready, setReady] = useState(false)
|
|
33
45
|
const [apis, setApis] = useState<ReadonlyMap<string, ApiHandle>>(() => new Map())
|
|
46
|
+
const router = useRouter()
|
|
47
|
+
|
|
48
|
+
// 1. Persisted state, before anything renders.
|
|
34
49
|
useEffect(() => {
|
|
35
50
|
let live = true
|
|
36
|
-
void
|
|
51
|
+
void preparePersistence()
|
|
52
|
+
.catch(() => { /* a device with no readable storage still gets an app */ })
|
|
53
|
+
.finally(() => {
|
|
54
|
+
if (!live) return
|
|
55
|
+
setReady(true)
|
|
56
|
+
void SplashScreen.hideAsync()
|
|
57
|
+
})
|
|
37
58
|
return () => { live = false }
|
|
38
59
|
}, [])
|
|
39
|
-
|
|
60
|
+
|
|
61
|
+
// 2. Connect, and keep connected. The supervisor re-dials with backoff; a
|
|
62
|
+
// dropped socket needs no handling here.
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const supervisor = connectApis((clients: ReadonlyMap<string, ResolvedClient>) => {
|
|
65
|
+
setApis(handlesFor(clients))
|
|
66
|
+
})
|
|
67
|
+
return () => supervisor.dispose()
|
|
68
|
+
}, [])
|
|
40
69
|
|
|
41
70
|
// Route inbound URLs (universal links, custom scheme, push taps) through the
|
|
42
71
|
// ONE typed link table — the same `matchFirstDeepLink` path push will use.
|
|
@@ -51,6 +80,8 @@ export default function RootLayout() {
|
|
|
51
80
|
return () => sub.remove()
|
|
52
81
|
}, [router])
|
|
53
82
|
|
|
83
|
+
if (!ready) return null
|
|
84
|
+
|
|
54
85
|
return (
|
|
55
86
|
<FrameworkRuntimesProvider apis={apis}>
|
|
56
87
|
<StatusBar style="auto" />
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The SAME hooks as the web frontend: `useSubscription('notes.list')` streams
|
|
4
4
|
// the server-side query; `useMutation('notes.create')` writes it. When the
|
|
5
5
|
// mutation lands, the server pushes a delta and this list re-renders — no
|
|
6
|
-
// refetch, no polling.
|
|
6
|
+
// refetch, no polling.
|
|
7
7
|
//
|
|
8
8
|
// Both come from `../lib/api`, which binds them ONCE to the api's generated
|
|
9
9
|
// procedure map — so the tag autocompletes, a typo is a compile error, and the
|
|
@@ -3,9 +3,12 @@ import { useState } from 'react'
|
|
|
3
3
|
import { Pressable, Text, View } from 'react-native'
|
|
4
4
|
import { useMobileConnectionStatus } from '@voltro/react-native'
|
|
5
5
|
import { buildDeviceRegistration } from '../lib/notifications'
|
|
6
|
+
import { onlineSource } from '../client'
|
|
6
7
|
|
|
7
8
|
export default function Settings() {
|
|
8
|
-
|
|
9
|
+
// Same injected source as the banner in _layout: the default reads
|
|
10
|
+
// `navigator.onLine`, which React Native does not have.
|
|
11
|
+
const { status, reportSuccess, reportFailure } = useMobileConnectionStatus({ onlineSource })
|
|
9
12
|
const [registered, setRegistered] = useState<string | null>(null)
|
|
10
13
|
|
|
11
14
|
return (
|
|
@@ -1,74 +1,91 @@
|
|
|
1
1
|
// The Voltro client for React Native.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// the WebSocket constructor as an INJECTED dependency — so React Native passes
|
|
6
|
-
// its own `globalThis.WebSocket` (which RN provides) and gets the SAME client
|
|
7
|
-
// stack the web app uses, without pulling in `@voltro/web`. See
|
|
8
|
-
// plans/open/mobile/00-overview.md (M0).
|
|
3
|
+
// Three pieces, all of them SHARED with the web client rather than
|
|
4
|
+
// re-implemented for mobile:
|
|
9
5
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
6
|
+
// · `mobileApis` — the generated binding (`.framework/mobileApis.generated.ts`,
|
|
7
|
+
// written by `voltro codegen` from `voltro.mobile.ts`). It carries the api's
|
|
8
|
+
// rpc group and descriptors, so the hooks are typed end to end from the
|
|
9
|
+
// api's own schema.
|
|
10
|
+
// · `startMobileApis` — connect + reconnect, over the SAME supervisor
|
|
11
|
+
// `@voltro/web` uses: exponential backoff, generation tracking, and the
|
|
12
|
+
// stale-seed gate that must not carry one subject's rows into the next.
|
|
13
|
+
// · `createAsyncStoragePersistence` — persisted stores over AsyncStorage,
|
|
14
|
+
// hydrated BEFORE the first render (a render cannot await).
|
|
15
|
+
//
|
|
16
|
+
// The ws URL is resolved at runtime, never baked in. `localhost` on a phone is
|
|
17
|
+
// the PHONE — the single most common reason a mobile dev loop silently never
|
|
18
|
+
// connects — so `resolveDevWsUrl` takes the LAN host Expo already knows.
|
|
17
19
|
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
+
import Constants from 'expo-constants'
|
|
21
|
+
import NetInfo from '@react-native-community/netinfo'
|
|
22
|
+
import { setStoreStorage, type ResolvedClient } from '@voltro/client'
|
|
23
|
+
import {
|
|
24
|
+
createAsyncStoragePersistence,
|
|
25
|
+
netInfoOnlineSource,
|
|
26
|
+
offlineFirstDefaults,
|
|
27
|
+
resolveDevWsUrl,
|
|
28
|
+
startMobileApis,
|
|
29
|
+
toApiHandles,
|
|
30
|
+
} from '@voltro/react-native'
|
|
31
|
+
import { mobileApis } from '../.framework/mobileApis.generated'
|
|
32
|
+
import { storeStorage } from './persistence'
|
|
20
33
|
|
|
21
|
-
/** The api's
|
|
22
|
-
|
|
23
|
-
type ApiRpcGroup = BuildApiRuntimeOptions['group']
|
|
34
|
+
/** The api's port, as `voltro dev` allocates it. Override per environment. */
|
|
35
|
+
const API_PORT = Number(process.env.EXPO_PUBLIC_API_PORT ?? 4000)
|
|
24
36
|
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The api's WebSocket URL.
|
|
39
|
+
*
|
|
40
|
+
* In development this is derived from the host running Metro, so a real device
|
|
41
|
+
* on the same wifi reaches your machine. In production set
|
|
42
|
+
* `EXPO_PUBLIC_API_WS_URL` to the deployed api.
|
|
43
|
+
*/
|
|
44
|
+
export const apiWsUrl = (): string =>
|
|
45
|
+
process.env.EXPO_PUBLIC_API_WS_URL
|
|
46
|
+
?? resolveDevWsUrl(Constants.expoConfig?.hostUri, API_PORT)
|
|
28
47
|
|
|
29
|
-
/** The offline-first posture
|
|
48
|
+
/** The offline-first posture (local-first on, optimistic writes, sync status
|
|
49
|
+
* surfaced). Spread into a client config, or read for UI decisions. */
|
|
30
50
|
export const clientPosture = offlineFirstDefaults
|
|
31
51
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
/** Reachability, from NetInfo. Module scope on purpose: an inline
|
|
53
|
+
* `netInfoOnlineSource(NetInfo)` is a new object every render, and the hook
|
|
54
|
+
* would resubscribe on each one. */
|
|
55
|
+
export const onlineSource = netInfoOnlineSource(NetInfo)
|
|
35
56
|
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
const
|
|
39
|
-
new globalThis.WebSocket(url, protocols as string | string[] | undefined)
|
|
57
|
+
/** Persisted stores, backed by AsyncStorage — NOT the Keychain. `persistence.ts`
|
|
58
|
+
* says why, and where a secret goes instead. */
|
|
59
|
+
export const persistence = createAsyncStoragePersistence({ storage: storeStorage })
|
|
40
60
|
|
|
41
61
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
62
|
+
* Load persisted state and install it as the backing store for every
|
|
63
|
+
* `defineStore({ persist })`.
|
|
64
|
+
*
|
|
65
|
+
* MUST be awaited before the first render. A store reads during render and a
|
|
66
|
+
* render cannot await, so an app that renders first shows empty state and then
|
|
67
|
+
* flickers into the saved one. The root layout holds the splash screen until
|
|
68
|
+
* this resolves.
|
|
46
69
|
*/
|
|
47
|
-
export const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
descriptors: ApiHandle['descriptors'],
|
|
51
|
-
): Promise<ApiHandle> => {
|
|
52
|
-
const wsUrl = toWsUrl(API_URL)
|
|
53
|
-
const built = await buildApiRuntime({ name, wsUrl, group, webSocketConstructor: rnWebSocket })
|
|
54
|
-
return {
|
|
55
|
-
runtime: built.runtime,
|
|
56
|
-
cache: built.cache,
|
|
57
|
-
client: built.client as ApiHandle['client'],
|
|
58
|
-
descriptors,
|
|
59
|
-
inspectBaseUrl: API_URL.replace(/\/+$/, ''),
|
|
60
|
-
errorBus: built.errorBus,
|
|
61
|
-
}
|
|
70
|
+
export const preparePersistence = async (): Promise<void> => {
|
|
71
|
+
await persistence.hydrate()
|
|
72
|
+
setStoreStorage(persistence.provider)
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
/** Every api this app talks to, bound to the resolved ws URL. */
|
|
76
|
+
export const apis = mobileApis(() => apiWsUrl())
|
|
77
|
+
|
|
64
78
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
79
|
+
* Connect the apis and keep them connected.
|
|
80
|
+
*
|
|
81
|
+
* `onChange` fires on the first connect, on every reconnect swap, and on
|
|
82
|
+
* teardown. Dispose the returned handle when the tree unmounts; call
|
|
83
|
+
* `reconnect()` after a sign-in so the connection re-resolves who it is.
|
|
68
84
|
*/
|
|
69
|
-
export const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
export const connectApis = (
|
|
86
|
+
onChange: (clients: ReadonlyMap<string, ResolvedClient>, initialized: boolean) => void,
|
|
87
|
+
) => startMobileApis({ apis, onChange })
|
|
88
|
+
|
|
89
|
+
/** Map the live clients into the `apis` prop the client provider takes. */
|
|
90
|
+
export const handlesFor = (clients: ReadonlyMap<string, ResolvedClient>) =>
|
|
91
|
+
toApiHandles(apis, clients)
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
// that reason; scaffold the pair (`create-project --api=api-backend --mobile`),
|
|
22
22
|
// which is what this template's README already assumes.
|
|
23
23
|
//
|
|
24
|
-
// RUNTIME
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
24
|
+
// RUNTIME: live. `voltro codegen` writes this app's api binding into
|
|
25
|
+
// `.framework/mobileApis.generated.ts` (from `voltro.mobile.ts`), and
|
|
26
|
+
// `../client.ts` connects it through the same supervisor the web client uses.
|
|
27
|
+
// The start scripts run codegen, so `pnpm ios` needs no separate step.
|
|
28
28
|
import { createHooks } from '@voltro/client'
|
|
29
29
|
import type { AppProcedures } from '@{{projectName}}/api/rpcGroup'
|
|
30
30
|
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
// come from `@voltro/react-native`; this module is browser/RN-safe (no Metro, no
|
|
7
7
|
// screens), which is exactly why it is unit-testable without a simulator.
|
|
8
8
|
//
|
|
9
|
-
// Declaration order wins in `
|
|
9
|
+
// Declaration order wins in `dispatchDeepLink`, so list more-specific patterns
|
|
10
10
|
// FIRST. The `navigate` callback is injected (the screen layer passes
|
|
11
11
|
// expo-router's `router.push`), keeping this table pure + testable.
|
|
12
12
|
import {
|
|
13
13
|
defineDeepLink,
|
|
14
|
-
|
|
14
|
+
dispatchDeepLink,
|
|
15
15
|
type DeepLinkDescriptor,
|
|
16
16
|
} from '@voltro/react-native'
|
|
17
17
|
|
|
@@ -32,8 +32,8 @@ export const handleDeepLink = (
|
|
|
32
32
|
links: ReadonlyArray<DeepLinkDescriptor>,
|
|
33
33
|
url: string,
|
|
34
34
|
): boolean => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
35
|
+
// `dispatchDeepLink` matches AND runs. Doing it in two steps here does not
|
|
36
|
+
// typecheck: the descriptors are a heterogeneous array, so the handler that
|
|
37
|
+
// comes back declares params the match that produced it cannot satisfy.
|
|
38
|
+
return dispatchDeepLink(links, url) !== null
|
|
39
39
|
}
|
|
@@ -8,9 +8,15 @@
|
|
|
8
8
|
// Putting a session token in AsyncStorage is a security bug; secure-store is its
|
|
9
9
|
// home.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// Two exports, and the split between them is the point:
|
|
12
|
+
// - `kv` is the APP's own store for things it manages itself, tokens included.
|
|
13
|
+
// - `storeStorage` backs `defineStore({ persist })` and is AsyncStorage ONLY.
|
|
14
|
+
//
|
|
15
|
+
// A persisted store is app STATE — a draft, a filter, a wizard step — and none of
|
|
16
|
+
// that belongs in the Keychain, which cannot be enumerated and which the OS may
|
|
17
|
+
// gate behind a biometric prompt. A secret does not belong in a persisted store
|
|
18
|
+
// either; put it through `kv` with `secretKey()`. Routing store keys by prefix
|
|
19
|
+
// would blur a rule worth keeping sharp.
|
|
14
20
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
15
21
|
import * as SecureStore from 'expo-secure-store'
|
|
16
22
|
|
|
@@ -36,3 +42,18 @@ export const kv = {
|
|
|
36
42
|
|
|
37
43
|
/** Prefix a key so it lands in secure storage. */
|
|
38
44
|
export const secretKey = (name: string): string => `${SECRET_PREFIX}${name}`
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The backing store for `defineStore({ persist })`, handed to
|
|
48
|
+
* `createAsyncStoragePersistence()` in `src/client.ts`.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately AsyncStorage alone — see the header. `getAllKeys` is what lets
|
|
51
|
+
* the adapter hydrate without being told which keys exist; SecureStore has no
|
|
52
|
+
* equivalent, which is the second reason secrets do not live here.
|
|
53
|
+
*/
|
|
54
|
+
export const storeStorage = {
|
|
55
|
+
getItem: (key: string): Promise<string | null> => AsyncStorage.getItem(key),
|
|
56
|
+
setItem: (key: string, value: string): Promise<void> => AsyncStorage.setItem(key, value),
|
|
57
|
+
removeItem: (key: string): Promise<void> => AsyncStorage.removeItem(key),
|
|
58
|
+
getAllKeys: async (): Promise<ReadonlyArray<string>> => AsyncStorage.getAllKeys(),
|
|
59
|
+
}
|
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"strict": true,
|
|
5
5
|
"paths": {
|
|
6
|
-
"@/*": [
|
|
7
|
-
|
|
6
|
+
"@/*": [
|
|
7
|
+
"./src/*"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"types": [
|
|
13
|
+
"node",
|
|
14
|
+
"react"
|
|
15
|
+
]
|
|
8
16
|
},
|
|
9
|
-
"include": [
|
|
17
|
+
"include": [
|
|
18
|
+
"src",
|
|
19
|
+
"app.config.ts",
|
|
20
|
+
"tests",
|
|
21
|
+
".expo/types/**/*.ts",
|
|
22
|
+
"expo-env.d.ts"
|
|
23
|
+
]
|
|
10
24
|
}
|
package/dist/codegen-uYrrDzQv.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { l as e, z as t } from "./dev-E7Eo5GHc.js";
|
|
2
|
-
import { r as n } from "./codegen-GYYcdpCg.js";
|
|
3
|
-
import { basename as r, relative as i, resolve as a } from "node:path";
|
|
4
|
-
//#region src/codegenCommand.ts
|
|
5
|
-
var o = async (i) => {
|
|
6
|
-
let { config: a, present: o, error: s } = await e(i);
|
|
7
|
-
if (o && a === null) return {
|
|
8
|
-
kind: "refused",
|
|
9
|
-
reason: "app.config.ts exists but could not be loaded" + (s === void 0 ? "" : ` — ${s instanceof Error ? s.message : String(s)}`)
|
|
10
|
-
};
|
|
11
|
-
let c = a?.plugins ?? [], { pluginContributions: l, pluginErrorImports: u, pluginRpcImports: d } = await t(c, a?.name ?? r(i), i), f = await n({
|
|
12
|
-
root: i,
|
|
13
|
-
...l.length > 0 ? { pluginContributions: l } : {},
|
|
14
|
-
...u.length > 0 ? { pluginErrorImports: u } : {},
|
|
15
|
-
...d.length > 0 ? { pluginRpcImports: d } : {},
|
|
16
|
-
...a?.name === void 0 ? {} : { apiName: a.name }
|
|
17
|
-
});
|
|
18
|
-
return f ? {
|
|
19
|
-
kind: "wrote",
|
|
20
|
-
file: f,
|
|
21
|
-
plugins: c.length,
|
|
22
|
-
errorSchemas: u.length,
|
|
23
|
-
routes: d.length
|
|
24
|
-
} : { kind: "nothing-to-write" };
|
|
25
|
-
}, s = async (e) => {
|
|
26
|
-
let t = a(e.find((e) => !e.startsWith("-")) ?? process.cwd()), n = await o(t);
|
|
27
|
-
return n.kind === "refused" ? (console.error(`voltro codegen: ${n.reason} — refusing to write.`), console.error(" The generated file would omit every plugin's error union and client routes, and would typecheck, so the omission would only surface as a missing error tag at runtime."), 1) : n.kind === "nothing-to-write" ? (console.log("voltro codegen: no rpc primitives found — nothing to write."), 0) : (console.log(`voltro codegen: wrote ${i(t, n.file)} (plugins ${n.plugins}, error schemas ${n.errorSchemas}, plugin routes ${n.routes})`), n.plugins > 0 && n.errorSchemas === 0 && console.log(" note: no plugin contributed a cross-cutting error schema, so the wire error union carries only each procedure's own `error:`. Expected if none of your plugins declare `errorSchemas` — but `guards:` needs @voltro/plugin-rbac for its ScopeError to decode typed."), 0);
|
|
28
|
-
};
|
|
29
|
-
//#endregion
|
|
30
|
-
export { o as regenerateRpcGroup, s as runCodegenCommand };
|
package/dist/dev-CAOoXOkc.js
DELETED