@symbiote-native/network 0.2.0 → 0.3.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Port of [`expo-network`](https://docs.expo.dev/versions/latest/sdk/network/) for
4
4
  [SymbioteNative](../../README.md) — network connection state (type/connected/internet-reachable)
5
5
  with a change listener, device IP address, and airplane-mode detection, reachable from every
6
- adapter (React, Vue, Angular), not just React.
6
+ adapter (React, Vue, Svelte, Solid, Angular), not just React.
7
7
 
8
8
  Built the same way as [`@symbiote-native/battery`](../battery) and
9
9
  [`@symbiote-native/local-auth`](../local-auth), an `expo-modules-core`-based wrapper (see the
@@ -31,12 +31,12 @@ into the native host app **once**, covering this package and every other `expo-m
31
31
  package (`@symbiote-native/battery`, `@symbiote-native/sensors`, `@symbiote-native/local-auth`)
32
32
  with zero further changes:
33
33
 
34
- | Platform | Touches |
35
- |---|---|
36
- | iOS | `ios/Podfile` — add `use_expo_modules!` |
37
- | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
38
- | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
39
- | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
34
+ | Platform | Touches |
35
+ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
36
+ | iOS | `ios/Podfile` — add `use_expo_modules!` |
37
+ | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
38
+ | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
39
+ | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
40
40
 
41
41
  Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
42
42
  peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
@@ -56,37 +56,50 @@ src/core/ network.ts — get*Async functions + addNetworkStateList
56
56
  NetworkStateEvent, hand-ported from Network.types.ts.
57
57
  src/react/hooks/ @symbiote-native/network/react — useNetworkState
58
58
  src/vue/composables/ @symbiote-native/network/vue — useNetworkState (same name)
59
+ src/svelte/runes/ @symbiote-native/network/svelte — useNetworkState (same name)
60
+ src/solid/primitives/ @symbiote-native/network/solid — createNetworkState (returns an
61
+ Accessor; Solid reserves `use*` for consuming existing state)
59
62
  src/angular/services/ @symbiote-native/network/angular — NetworkStateService (`.connect()`
60
63
  returns a Signal)
61
64
  ```
62
65
 
63
- Each adapter's hook/composable/service is a thin lifecycle wrapper (seed from the one-shot
64
- `getNetworkStateAsync()` call, subscribe to `addNetworkStateListener`, unsubscribe on unmount) over
65
- the same `core` functions — the subscription logic is written once and shared by all three.
66
+ Each adapter's hook/composable/rune/primitive/service is a thin lifecycle wrapper (seed from the
67
+ one-shot `getNetworkStateAsync()` call, subscribe to `addNetworkStateListener`, unsubscribe on
68
+ unmount) over the same `core` functions — the subscription logic is written once and shared by all
69
+ of them.
66
70
 
67
71
  ## Use it
68
72
 
69
73
  ```tsx
70
74
  // React — examples/expo-react/screens/NetworkScreen.tsx
71
75
  import { useEffect, useState } from 'react';
72
- import { getIpAddressAsync, isAirplaneModeEnabledAsync } from '@symbiote-native/network';
76
+ import {
77
+ getIpAddressAsync,
78
+ isAirplaneModeEnabledAsync,
79
+ } from '@symbiote-native/network';
73
80
  import { useNetworkState } from '@symbiote-native/network/react';
74
81
 
75
82
  function NetworkScreen() {
76
- const networkState = useNetworkState(); // { type, isConnected, isInternetReachable }
83
+ const networkState = useNetworkState(); // { type, isConnected, isInternetReachable }
77
84
  const [ipAddress, setIpAddress] = useState<string | null>(null);
78
85
  const [isAirplaneMode, setIsAirplaneMode] = useState<boolean | null>(null);
79
86
 
80
87
  useEffect(() => {
81
- Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(([ip, airplaneMode]) => {
82
- setIpAddress(ip);
83
- setIsAirplaneMode(airplaneMode);
84
- });
88
+ Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
89
+ ([ip, airplaneMode]) => {
90
+ setIpAddress(ip);
91
+ setIsAirplaneMode(airplaneMode);
92
+ },
93
+ );
85
94
  }, [networkState]);
86
95
 
87
96
  return (
88
97
  <>
89
- <Text>{networkState.isConnected ? `Connected via ${networkState.type}` : 'Offline'}</Text>
98
+ <Text>
99
+ {networkState.isConnected
100
+ ? `Connected via ${networkState.type}`
101
+ : 'Offline'}
102
+ </Text>
90
103
  <Text>{ipAddress ?? 'checking…'}</Text>
91
104
  <Text>{isAirplaneMode ? 'Airplane mode: On' : 'Airplane mode: Off'}</Text>
92
105
  </>
@@ -99,26 +112,33 @@ function NetworkScreen() {
99
112
  <script setup lang="ts">
100
113
  import { ref, watch } from 'vue';
101
114
  import { Text } from '@symbiote-native/vue';
102
- import { getIpAddressAsync, isAirplaneModeEnabledAsync } from '@symbiote-native/network';
115
+ import {
116
+ getIpAddressAsync,
117
+ isAirplaneModeEnabledAsync,
118
+ } from '@symbiote-native/network';
103
119
  import { useNetworkState } from '@symbiote-native/network/vue';
104
120
 
105
- const networkState = useNetworkState(); // Ref<NetworkState>
121
+ const networkState = useNetworkState(); // Ref<NetworkState>
106
122
  const ipAddress = ref<string | null>(null);
107
123
  const isAirplaneMode = ref<boolean | null>(null);
108
124
 
109
125
  watch(
110
126
  networkState,
111
127
  () => {
112
- void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(([ip, airplaneMode]) => {
113
- ipAddress.value = ip;
114
- isAirplaneMode.value = airplaneMode;
115
- });
128
+ void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
129
+ ([ip, airplaneMode]) => {
130
+ ipAddress.value = ip;
131
+ isAirplaneMode.value = airplaneMode;
132
+ },
133
+ );
116
134
  },
117
135
  { immediate: true },
118
136
  );
119
137
  </script>
120
138
  <template>
121
- <Text>{{ networkState.isConnected ? `Connected via ${networkState.type}` : 'Offline' }}</Text>
139
+ <Text>{{
140
+ networkState.isConnected ? `Connected via ${networkState.type}` : 'Offline'
141
+ }}</Text>
122
142
  <Text>{{ ipAddress ?? 'checking…' }}</Text>
123
143
  </template>
124
144
  ```
@@ -145,15 +165,59 @@ export class NetworkScreen {
145
165
  constructor() {
146
166
  effect(() => {
147
167
  this.networkState();
148
- Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(([ip]) => {
149
- this.ipAddress.set(ip);
150
- });
168
+ Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
169
+ ([ip]) => {
170
+ this.ipAddress.set(ip);
171
+ },
172
+ );
151
173
  });
152
174
  }
153
175
  }
154
176
  ```
155
177
 
156
- Each of the three demo screens above also re-fetches the IP address and airplane-mode card
178
+ ```tsx
179
+ // Solid — the accessor is CALLED; a Solid component body runs once, so a snapshot would freeze.
180
+ import { createSignal, onMount } from 'solid-js';
181
+ import { Text } from '@symbiote-native/solid';
182
+ import {
183
+ getIpAddressAsync,
184
+ isAirplaneModeEnabledAsync,
185
+ } from '@symbiote-native/network';
186
+ import { createNetworkState } from '@symbiote-native/network/solid';
187
+
188
+ function NetworkScreen() {
189
+ const networkState = createNetworkState(); // Accessor<NetworkState>
190
+ const [ipAddress, setIpAddress] = createSignal<string | null>(null);
191
+ const [isAirplaneMode, setIsAirplaneMode] = createSignal<boolean | null>(
192
+ null,
193
+ );
194
+
195
+ onMount(() => {
196
+ void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
197
+ ([ip, airplaneMode]) => {
198
+ setIpAddress(ip);
199
+ setIsAirplaneMode(airplaneMode);
200
+ },
201
+ );
202
+ });
203
+
204
+ return (
205
+ <>
206
+ <Text>
207
+ {networkState().isConnected
208
+ ? `Connected via ${networkState().type}`
209
+ : 'Offline'}
210
+ </Text>
211
+ <Text>{ipAddress() ?? 'checking…'}</Text>
212
+ <Text>
213
+ {isAirplaneMode() ? 'Airplane mode: On' : 'Airplane mode: Off'}
214
+ </Text>
215
+ </>
216
+ );
217
+ }
218
+ ```
219
+
220
+ Each of the demo screens above also re-fetches the IP address and airplane-mode card
157
221
  whenever the live network state changes (toggling Wi-Fi/airplane mode on the device updates both
158
222
  cards together) — see the linked files for the full version, including the connection-type label
159
223
  switch and the layout around it.
@@ -174,18 +238,25 @@ Plus `NetworkStateType` (enum: `NONE`/`UNKNOWN`/`CELLULAR`/`WIFI`/`BLUETOOTH`/`E
174
238
  `VPN`/`OTHER`), `NetworkState`, `NetworkStateEvent` — ported from upstream's `Network.types.ts`.
175
239
 
176
240
  ```ts
177
- import { getNetworkStateAsync, addNetworkStateListener } from '@symbiote-native/network';
241
+ import {
242
+ getNetworkStateAsync,
243
+ addNetworkStateListener,
244
+ } from '@symbiote-native/network';
178
245
 
179
246
  // framework-scoped entry points re-export the same free functions, plus a lifecycle
180
247
  // hook/composable/service:
181
248
  import { useNetworkState } from '@symbiote-native/network/react';
182
249
  import { useNetworkState } from '@symbiote-native/network/vue';
250
+ import { useNetworkState } from '@symbiote-native/network/svelte';
251
+ import { createNetworkState } from '@symbiote-native/network/solid';
183
252
  import { NetworkStateService } from '@symbiote-native/network/angular';
184
253
  ```
185
254
 
186
- `useNetworkState` seeds its initial value from a one-shot `getNetworkStateAsync()` call, then
187
- subscribes to `addNetworkStateListener` for updates, and unsubscribes on unmount — mirroring
188
- upstream's own `useNetworkState`.
255
+ `useNetworkState` (Solid: `createNetworkState`) seeds its initial value from a one-shot
256
+ `getNetworkStateAsync()` call, then subscribes to `addNetworkStateListener` for updates, and
257
+ unsubscribes on unmount — mirroring upstream's own `useNetworkState`. The Solid primitive returns
258
+ an `Accessor<NetworkState>` and subscribes from its body rather than an effect, so nothing can slip
259
+ between the seed and the subscription.
189
260
 
190
261
  ## Test it
191
262
 
@@ -32,8 +32,8 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { effect, inject, Injectable, Injector, signal } from '@angular/core';
36
- import { addNetworkStateListener, getNetworkStateAsync } from '../../../core/index.js';
35
+ import { effect, inject, Injectable, Injector, signal, } from '@angular/core';
36
+ import { addNetworkStateListener, getNetworkStateAsync, } from '../../../core/index.js';
37
37
  // Angular twin of React's `useNetworkState` hook and Vue's `useNetworkState` composable.
38
38
  //
39
39
  // readonly networkState = inject(NetworkStateService).connect();
@@ -1,3 +1,3 @@
1
1
  export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network';
2
- export { NetworkStateType, type NetworkState, type NetworkStateEvent } from './types';
2
+ export { NetworkStateType, type NetworkState, type NetworkStateEvent, } from './types';
3
3
  export type { EventSubscription } from 'expo-modules-core';
@@ -1,2 +1,2 @@
1
1
  export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network.js';
2
- export { NetworkStateType } from './types.js';
2
+ export { NetworkStateType, } from './types.js';
@@ -2,7 +2,7 @@
2
2
  // value with a one-shot getNetworkStateAsync() call before the first native event fires, matching
3
3
  // upstream's own useNetworkState.
4
4
  import { useEffect, useState } from 'react';
5
- import { addNetworkStateListener, getNetworkStateAsync } from '../../../core/index.js';
5
+ import { addNetworkStateListener, getNetworkStateAsync, } from '../../../core/index.js';
6
6
  export function useNetworkState() {
7
7
  const [networkState, setNetworkState] = useState({});
8
8
  useEffect(() => {
@@ -0,0 +1,2 @@
1
+ export { createNetworkState } from './primitives/create-network-state';
2
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/network/solid: the Solid entry over the framework-agnostic core — mirrors the
2
+ // lifecycle-bucket naming convention of adapters/solid/src/primitives (never `hooks`/`composables`/
3
+ // `runes`, those are React's, Vue's and Svelte's terms).
4
+ export { createNetworkState } from './primitives/create-network-state.js';
5
+ export * from '../core/index.js';
@@ -0,0 +1,3 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import { type NetworkState } from '../../core';
3
+ export declare function createNetworkState(): Accessor<NetworkState>;
@@ -0,0 +1,32 @@
1
+ // createNetworkState — the Solid twin of React's `useNetworkState` hook, Vue's composable and
2
+ // Svelte's rune, over the framework-agnostic core (core/network.ts). Seeds with a one-shot
3
+ // getNetworkStateAsync() call before the first native event fires, matching upstream's own
4
+ // useNetworkState.
5
+ //
6
+ // `primitives/` and `create*`, never `hooks/`+`use*`: Solid's ecosystem calls a composable
7
+ // reactive function a PRIMITIVE and reserves `use*` for consuming something that already exists.
8
+ // Full rationale in adapters/solid/src/primitives/create-color-scheme.ts.
9
+ //
10
+ // Returns an ACCESSOR, never a snapshot: a Solid component body runs ONCE, so a returned object
11
+ // would pin the caller to the empty seed forever.
12
+ //
13
+ // Outside a component or `createRoot` there is no owner for `onCleanup` — Solid warns and the
14
+ // native listener lives for the process. The accessor still tracks; only the teardown is lost.
15
+ import { createSignal, onCleanup } from 'solid-js';
16
+ import { addNetworkStateListener, getNetworkStateAsync, } from '../../core/index.js';
17
+ export function createNetworkState() {
18
+ const [networkState, setNetworkState] = createSignal({});
19
+ getNetworkStateAsync().then(state => {
20
+ setNetworkState(state);
21
+ });
22
+ // Subscribed from the primitive body, not from an effect: React/Vue/Svelte start listening a
23
+ // tick after their seed runs, so a change landing in that window is lost. Both statements here
24
+ // run in one synchronous tick and nothing can interleave.
25
+ const subscription = addNetworkStateListener(event => {
26
+ setNetworkState(event);
27
+ });
28
+ onCleanup(() => {
29
+ subscription.remove();
30
+ });
31
+ return networkState;
32
+ }
@@ -1,5 +1,5 @@
1
- import { effect, inject, Injectable, Injector, signal } from '@angular/core';
2
- import { addNetworkStateListener, getNetworkStateAsync } from '../../../core';
1
+ import { effect, inject, Injectable, Injector, signal, } from '@angular/core';
2
+ import { addNetworkStateListener, getNetworkStateAsync, } from '../../../core';
3
3
  import * as i0 from "@angular/core";
4
4
  // Angular twin of React's `useNetworkState` hook and Vue's `useNetworkState` composable.
5
5
  //
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/network-state.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAqB,MAAM,eAAe,CAAC;;AAEjG,yFAAyF;AACzF,EAAE;AACF,mEAAmE;AACnE,2CAA2C;AAE3C,MAAM,OAAO,mBAAmB;IACb,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,OAAO;QACL,MAAM,YAAY,GAAG,MAAM,CAAe,EAAE;yFAAC,CAAC;QAE9C,MAAM,CACJ,SAAS,CAAC,EAAE;YACV,oBAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9D,MAAM,YAAY,GAAG,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/E,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,CAAC,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAC5B,CAAC;QAEF,OAAO,YAAY,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;uGAhBU,mBAAmB;2GAAnB,mBAAmB,cADN,MAAM;;2FACnB,mBAAmB;kBAD/B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/network-state.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,GAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GAErB,MAAM,eAAe,CAAC;;AAEvB,yFAAyF;AACzF,EAAE;AACF,mEAAmE;AACnE,2CAA2C;AAE3C,MAAM,OAAO,mBAAmB;IACb,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,OAAO;QACL,MAAM,YAAY,GAAG,MAAM,CAAe,EAAE;yFAAC,CAAC;QAE9C,MAAM,CACJ,SAAS,CAAC,EAAE;YACV,oBAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9D,MAAM,YAAY,GAAG,uBAAuB,CAAC,KAAK,CAAC,EAAE,CACnD,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CACxB,CAAC;YACF,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,CAAC,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAC5B,CAAC;QAEF,OAAO,YAAY,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;uGAlBU,mBAAmB;2GAAnB,mBAAmB,cADN,MAAM;;2FACnB,mBAAmB;kBAD/B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
@@ -1,3 +1,3 @@
1
1
  export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network';
2
- export { NetworkStateType, type NetworkState, type NetworkStateEvent } from './types';
2
+ export { NetworkStateType, type NetworkState, type NetworkStateEvent, } from './types';
3
3
  export type { EventSubscription } from 'expo-modules-core';
@@ -1,3 +1,3 @@
1
1
  export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network';
2
- export { NetworkStateType } from './types';
2
+ export { NetworkStateType, } from './types';
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAA6C,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,GAGjB,MAAM,SAAS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAc/C,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAAuB,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAc/C,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAC5C,wBAAwB,CACzB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/core/network.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,0FAA0F;AAC1F,2DAA2D;AAC3D,OAAO,EAAE,mBAAmB,EAA0B,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;QACtC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,WAAW,CAAC,oBAAoB,EAAE,CAAC;AAC5C,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;QACnC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,WAAW,CAAC,iBAAiB,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC;QAC5C,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,4BAA4B,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,WAAW,CAAC,0BAA0B,EAAE,CAAC;AAClD,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,uBAAuB,CACrC,QAA4C;IAE5C,OAAO,WAAW,CAAC,WAAW,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/core/network.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,0FAA0F;AAC1F,2DAA2D;AAC3D,OAAO,EAAE,mBAAmB,EAA0B,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;QACtC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,WAAW,CAAC,oBAAoB,EAAE,CAAC;AAC5C,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;QACnC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,WAAW,CAAC,iBAAiB,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC;QAC5C,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,4BAA4B,CAC7B,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,0BAA0B,EAAE,CAAC;AAClD,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,uBAAuB,CACrC,QAA4C;IAE5C,OAAO,WAAW,CAAC,WAAW,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;AACpE,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@symbiote-native/network",
3
- "version": "0.2.0",
4
- "description": "expo-network wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Network state (type/connected/internet-reachable) with a change listener, device IP address, and airplane-mode detection.",
3
+ "version": "0.3.0",
4
+ "description": "expo-network wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Network state (type/connected/internet-reachable) with a change listener, device IP address, and airplane-mode detection.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -34,6 +34,10 @@
34
34
  "types": "./build/svelte/index.d.ts",
35
35
  "default": "./build/svelte/index.js"
36
36
  },
37
+ "./solid": {
38
+ "types": "./build/solid/index.d.ts",
39
+ "default": "./build/solid/index.js"
40
+ },
37
41
  "./angular": {
38
42
  "types": "./build-ngc/angular/index.d.ts",
39
43
  "react-native": "./build-ngc/angular/index.js",
@@ -60,12 +64,14 @@
60
64
  "@symbiote-native/angular": ">=0.6.2",
61
65
  "@symbiote-native/engine": ">=0.1.7",
62
66
  "@symbiote-native/react": ">=0.2.8",
67
+ "@symbiote-native/solid": ">=0.0.1",
63
68
  "@symbiote-native/svelte": ">=0.1.0",
64
69
  "@symbiote-native/vue": ">=0.3.8",
65
70
  "@angular/core": ">=20",
66
71
  "@vue/runtime-core": "^3.5.13",
67
72
  "react": ">=19.0.0",
68
73
  "react-native": ">=0.86",
74
+ "solid-js": ">=1.9.0",
69
75
  "svelte": ">=5.56.0",
70
76
  "vue": ">=3.5.0"
71
77
  },
@@ -76,6 +82,9 @@
76
82
  "@symbiote-native/react": {
77
83
  "optional": true
78
84
  },
85
+ "@symbiote-native/solid": {
86
+ "optional": true
87
+ },
79
88
  "@symbiote-native/svelte": {
80
89
  "optional": true
81
90
  },
@@ -91,6 +100,9 @@
91
100
  "react": {
92
101
  "optional": true
93
102
  },
103
+ "solid-js": {
104
+ "optional": true
105
+ },
94
106
  "svelte": {
95
107
  "optional": true
96
108
  },
@@ -106,14 +118,16 @@
106
118
  "@types/react": "^19.2.0",
107
119
  "@vue/runtime-core": "^3.5.13",
108
120
  "react": "19.2.3",
121
+ "solid-js": "^1.9.14",
109
122
  "svelte": "^5.56.0",
110
123
  "typescript": "~6.0.0",
111
- "@symbiote-native/angular": "0.7.0",
112
- "@symbiote-native/engine": "0.2.0",
113
- "@symbiote-native/react": "0.3.0",
114
- "@symbiote-native/svelte": "0.2.0",
115
- "@symbiote-native/test-utils": "0.1.6",
116
- "@symbiote-native/vue": "0.4.0"
124
+ "@symbiote-native/angular": "0.8.0",
125
+ "@symbiote-native/engine": "0.3.0",
126
+ "@symbiote-native/react": "0.4.0",
127
+ "@symbiote-native/solid": "0.1.0",
128
+ "@symbiote-native/svelte": "0.3.0",
129
+ "@symbiote-native/test-utils": "0.2.0",
130
+ "@symbiote-native/vue": "0.5.0"
117
131
  },
118
132
  "scripts": {
119
133
  "typecheck": "tsc --build",
@@ -1,5 +1,16 @@
1
- import { effect, inject, Injectable, Injector, signal, type Signal } from '@angular/core';
2
- import { addNetworkStateListener, getNetworkStateAsync, type NetworkState } from '../../../core';
1
+ import {
2
+ effect,
3
+ inject,
4
+ Injectable,
5
+ Injector,
6
+ signal,
7
+ type Signal,
8
+ } from '@angular/core';
9
+ import {
10
+ addNetworkStateListener,
11
+ getNetworkStateAsync,
12
+ type NetworkState,
13
+ } from '../../../core';
3
14
 
4
15
  // Angular twin of React's `useNetworkState` hook and Vue's `useNetworkState` composable.
5
16
  //
@@ -15,7 +26,9 @@ export class NetworkStateService {
15
26
  effect(
16
27
  onCleanup => {
17
28
  getNetworkStateAsync().then(state => networkState.set(state));
18
- const subscription = addNetworkStateListener(event => networkState.set(event));
29
+ const subscription = addNetworkStateListener(event =>
30
+ networkState.set(event),
31
+ );
19
32
  onCleanup(() => subscription.remove());
20
33
  },
21
34
  { injector: this.injector },
package/src/core/index.ts CHANGED
@@ -4,5 +4,9 @@ export {
4
4
  isAirplaneModeEnabledAsync,
5
5
  addNetworkStateListener,
6
6
  } from './network';
7
- export { NetworkStateType, type NetworkState, type NetworkStateEvent } from './types';
7
+ export {
8
+ NetworkStateType,
9
+ type NetworkState,
10
+ type NetworkStateEvent,
11
+ } from './types';
8
12
  export type { EventSubscription } from 'expo-modules-core';
@@ -16,4 +16,6 @@ export type INativeNetworkModule = {
16
16
  ): EventSubscription;
17
17
  };
18
18
 
19
- export const expoNetwork = requireNativeModule<INativeNetworkModule>(EXPO_NETWORK_MODULE_NAME);
19
+ export const expoNetwork = requireNativeModule<INativeNetworkModule>(
20
+ EXPO_NETWORK_MODULE_NAME,
21
+ );
@@ -29,7 +29,10 @@ export async function getIpAddressAsync(): Promise<string> {
29
29
  */
30
30
  export async function isAirplaneModeEnabledAsync(): Promise<boolean> {
31
31
  if (!expoNetwork.isAirplaneModeEnabledAsync) {
32
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'isAirplaneModeEnabledAsync');
32
+ throw new UnavailabilityError(
33
+ NATIVE_MODULE_NAME,
34
+ 'isAirplaneModeEnabledAsync',
35
+ );
33
36
  }
34
37
  return expoNetwork.isAirplaneModeEnabledAsync();
35
38
  }
@@ -2,14 +2,20 @@
2
2
  // value with a one-shot getNetworkStateAsync() call before the first native event fires, matching
3
3
  // upstream's own useNetworkState.
4
4
  import { useEffect, useState } from 'react';
5
- import { addNetworkStateListener, getNetworkStateAsync, type NetworkState } from '../../../core';
5
+ import {
6
+ addNetworkStateListener,
7
+ getNetworkStateAsync,
8
+ type NetworkState,
9
+ } from '../../../core';
6
10
 
7
11
  export function useNetworkState(): NetworkState {
8
12
  const [networkState, setNetworkState] = useState<NetworkState>({});
9
13
 
10
14
  useEffect(() => {
11
15
  getNetworkStateAsync().then(setNetworkState);
12
- const subscription = addNetworkStateListener(event => setNetworkState(event));
16
+ const subscription = addNetworkStateListener(event =>
17
+ setNetworkState(event),
18
+ );
13
19
  return () => subscription.remove();
14
20
  }, []);
15
21
 
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/network/solid: the Solid entry over the framework-agnostic core — mirrors the
2
+ // lifecycle-bucket naming convention of adapters/solid/src/primitives (never `hooks`/`composables`/
3
+ // `runes`, those are React's, Vue's and Svelte's terms).
4
+
5
+ export { createNetworkState } from './primitives/create-network-state';
6
+ export * from '../core';
@@ -0,0 +1,43 @@
1
+ // createNetworkState — the Solid twin of React's `useNetworkState` hook, Vue's composable and
2
+ // Svelte's rune, over the framework-agnostic core (core/network.ts). Seeds with a one-shot
3
+ // getNetworkStateAsync() call before the first native event fires, matching upstream's own
4
+ // useNetworkState.
5
+ //
6
+ // `primitives/` and `create*`, never `hooks/`+`use*`: Solid's ecosystem calls a composable
7
+ // reactive function a PRIMITIVE and reserves `use*` for consuming something that already exists.
8
+ // Full rationale in adapters/solid/src/primitives/create-color-scheme.ts.
9
+ //
10
+ // Returns an ACCESSOR, never a snapshot: a Solid component body runs ONCE, so a returned object
11
+ // would pin the caller to the empty seed forever.
12
+ //
13
+ // Outside a component or `createRoot` there is no owner for `onCleanup` — Solid warns and the
14
+ // native listener lives for the process. The accessor still tracks; only the teardown is lost.
15
+
16
+ import { createSignal, onCleanup, type Accessor } from 'solid-js';
17
+ import {
18
+ addNetworkStateListener,
19
+ getNetworkStateAsync,
20
+ type EventSubscription,
21
+ type NetworkState,
22
+ } from '../../core';
23
+
24
+ export function createNetworkState(): Accessor<NetworkState> {
25
+ const [networkState, setNetworkState] = createSignal<NetworkState>({});
26
+
27
+ getNetworkStateAsync().then(state => {
28
+ setNetworkState(state);
29
+ });
30
+
31
+ // Subscribed from the primitive body, not from an effect: React/Vue/Svelte start listening a
32
+ // tick after their seed runs, so a change landing in that window is lost. Both statements here
33
+ // run in one synchronous tick and nothing can interleave.
34
+ const subscription: EventSubscription = addNetworkStateListener(event => {
35
+ setNetworkState(event);
36
+ });
37
+
38
+ onCleanup(() => {
39
+ subscription.remove();
40
+ });
41
+
42
+ return networkState;
43
+ }