Schnellstart
TypeScript-basierten RxJS-HTTP-Client + (optional) Next.js/RSocket-Adapter.
Kernmerkmale
- Sicherstellung der Typstabilität der Routen (Anfragen) durch Einspeisung von OpenAPI-ähnlichen Paths-Typen (konventionell paths) aus Swagger/OpenAPI / AsyncAPI abgeleiteten Schemata / benutzerdefinierten Verträgen.
- Alle APIs geben RxJS Observable zurück.
- Core ist frameworkunabhängig (keine Abhängigkeit von Next.js).
- Next.js-spezifische Funktionen sind im @byeolnaerim/typed-rx-http/next-Einstiegspunkt getrennt → Next.js wird nur benötigt, wenn /next importiert wird.
- RSocket-spezifische Funktionen sind im @byeolnaerim/typed-rx-http/rsocket-Einstiegspunkt getrennt → RSocket-Paket wird nur benötigt, wenn /rsocket importiert wird.
Installation
npm
npm i @byeolnaerim/typed-rx-http rxjsEinstiegspunkt
Core (framework-unabhängig)
import {
createHttpClient,
createHeaderStore,
createCsrCache,
createSessionAuth,
HttpResponseError,
isHttpResponseError,
type ServiceArguments,
type CacheForService,
} from "@byeolnaerim/typed-rx-http";Next.js-Adapter (optional)
import {
callApiSsrCache,
redirectToUnauthorizedOnServer401,
} from "@byeolnaerim/typed-rx-http/next";In Projekten, die Next.js nicht verwenden, importieren Sie /next nicht.
RSocket-Adapter (optional)
Installieren Sie das Peer-Paket nur in Projekten, die RSocket verwenden.
npm i rsocket-core rsocket-types rsocket-websocket-clientimport { createRSocketApi } from "@byeolnaerim/typed-rx-http/rsocket";
const api = createRSocketApi("ws://localhost:7000/rsocket", jwt);
api.mono<User>("user.findById", { id: 1 }).subscribe((user) => {
console.log(user);
});
api.stream<Notification>("notification.stream").subscribe((notification) => {
console.log(notification);
});In Projekten, die RSocket nicht verwenden, importieren Sie /rsocket nicht.
Core Verwendungshinweise
1) Paths-Typ vorbereiten (normalerweise OpenAPI paths)
Der Paths in createHttpClient<Paths>() repräsentiert den Typ der "Anforderungsspezifikation (Route)". Im Dokument wird er konventionell als paths bezeichnet, muss jedoch nicht unbedingt OpenAPI/Swagger sein oder den Namen paths tragen.
Allerdings verwendet der Core intern die Einschränkung OpenApiPathsLike, sodass Paths eine Form haben muss, die der OpenAPI paths ähnlich ist.
- Oberster Schlüssel: URL-Pfad-String (z. B. "/users/{id}")
- Unterer Schlüssel: HTTP-Methode (get/post/put/delete/patch …)
- Innerhalb jeder Methode gibt es Felder wie parameters.query/path/header/cookie, requestBody, responses (oder nie)
Der Core bezieht sich hauptsächlich auf die oben genannten Felder, um den Typ von ServiceArguments zu konstruieren.
- url: keyof Paths
- method: keyof Paths[url]
- queryString: parameters.query
- pathVariable: parameters.path
- body: requestBody
Beispiel: Die Ausgabe von openapi-typescript hat normalerweise die folgende Struktur (teilweise abgekürzt).
export interface paths {
"/test/get-hello-world": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
200: {
content: {
"application/json": components["schemas"]["TestResponse"];
};
};
};
};
};
}// aliasing: OpenAPI `paths` -> `Paths`
import type { paths as Paths } from "./@types/ApiTypes";2) HeaderStore erstellen
HeaderStore ist ein einfacher In-Memory-Speicher zur Verwaltung der Standardheader in CSR.
import { createHeaderStore } from "@byeolnaerim/typed-rx-http";
export const headerStore = createHeaderStore({
"Content-Type": "application/json",
});3) HTTP-Client erstellen
- headerStore ist optional, aber es wird empfohlen, ihn zu verwenden, wenn Sie Standardheader/Sitzungsauthentifizierung in CSR verwenden möchten.
- headersProvider wird verwendet, wenn bei jeder Anfrage Header berechnet werden müssen, wie bei SSR/Multi-Tenant.
import { createHttpClient } from "@byeolnaerim/typed-rx-http";
import type { paths as Paths } from "./@types/ApiTypes";
const apiUrl = process.env.NEXT_PUBLIC_API_URL!;
export const client = createHttpClient<Paths>({
baseUrl: apiUrl,
headerStore,
});4) API-Aufruf (typensicher)
Der Anfragetyp (url/method/pathVariable/queryString/body) wird durch den Typ bestimmt, den der Benutzer in createHttpClient<Paths>() injiziert hat (üblicherweise OpenAPI-Pfade). Der Antworttyp wird von dem Aufrufer in callApi<R>() als generischer R ausgewählt (der Kern leitet dies nicht automatisch aus den Antworten ab).
export const getAdminList = () => {
return client.callApi<{ items: any[]; total: number }>({
url: "/api/account/search/admin/get-list",
method: "get",
queryString: { pageNumber: 1, pageSize: 20 },
});
};Antwortverpackung (ResponseWrapper) — optional
Diese Bibliothek zwingt nicht zur Antwortverpackung. Sie können den Antworttyp generisch pro API auswählen.
Verpackte Antwort
type ResponseWrapper<T> = { data: T; message?: string; resultType?: string };
export const bidCreate = (body: unknown) => {
return client.callApi<ResponseWrapper<{ requestId: string }>>({
url: "/api/bid/create/bid/request",
method: "post",
body,
});
};Unverpackte Antwort
export const bidCreate = (body: unknown) => {
return client.callApi<{ requestId: string }>({
url: "/api/bid/create/bid/request",
method: "post",
body,
});
};Streaming (NDJSON)
Wenn der Server NDJSON (ein JSON pro Zeile) zurückgibt, verwenden Sie callApiStream. Wenn kein Accept-Header vorhanden ist, wird application/x-ndjson als Standard festgelegt.
export const streamEvents = () => {
return client.callApiStream<{ eventName: string; content: unknown }>({
url: "/api/event/stream",
method: "get",
// headers: { Accept: "application/x-ndjson" }, // 선택
});
};CSR-Cache (Client-Cache)
Funktionen, die von createCsrCache<CacheName>() bereitgestellt werden:
callApiCsrCache(callApiFn, serviceArgs, cacheOptions)- removeCsrCache(cacheName) — unterstützt Typ-Cache-Namen + Strings
import { createCsrCache } from "@byeolnaerim/typed-rx-http";
import type { paths as Paths } from "./@types/ApiTypes";
type CacheNames = "adminList" | "profile";
const csrCache = createCsrCache<CacheNames>();
export const callApiClientCache = <R>(
args: ServiceArguments<Paths, any, any, R>,
cache: { cacheName: CacheNames; cacheTime: number; cacheSize?: number },
) => csrCache.callApiCsrCache(client.callApi, args as any, cache as any);
// 무효화
csrCache.removeCsrCache("adminList"); // 타입
csrCache.removeCsrCache("any-prefix"); // 문자열Sitzungsbasierter Authentifizierungs-Plugin (optional)
createSessionAuth trennt die Sitzungsauthentifizierungslogik vom Kern und ermöglicht es, sie optional hinzuzufügen oder zu entfernen.
Funktionsweise:
- Authorization im headerStore beibehalten
- Token-Synchronisierung mit ensureToken$() (/api/auth/token)
- Bei 401 einmalige Erneuerung versuchen (/api/auth/token/refresh) und ursprüngliche Anfrage erneut versuchen
- Bei Erneuerungsfehler logout (/api/auth/logout) und Fehler weitergeben
- Änderungen des Anmeldestatus werden extern über den onLoginChange-Callback verarbeitet
import { createSessionAuth } from "@byeolnaerim/typed-rx-http";
const auth = createSessionAuth({
headerStore,
onLoginChange: (loggedIn) => {
// 예: setLogin(loggedIn) 같은 외부 상태 업데이트(선택)
},
// tokenUrl / refreshUrl / logoutUrl 커스터마이징 가능
});
export const secureCall = () => {
return client
.callApi<{ ok: true }>({ url: "/api/secure", method: "get" })
.pipe(auth.withSessionAuth());
};Wenn nur eine Token-Synchronisierung ohne Erneuerung/Wiederholung erforderlich ist:
return client.callApi(...).pipe(auth.withEnsureToken());Fehlerbehandlung
Wenn es nicht 2xx ist, wird HttpResponseError geworfen (einschließlich status, response, args, data).
Legacy-Kompatibilität: Wenn der Fehlerkörper die Form { resultType: ... } hat, wird dieses Objekt direkt geworfen.
import { isHttpResponseError } from "@byeolnaerim/typed-rx-http";
client.callApi(...).subscribe({
error: (e) => {
if (isHttpResponseError(e)) {
console.log(e.status, e.data);
}
},
});Next.js-Adapter (/next)
redirectToUnauthorizedOnServer401
redirectToUnauthorizedOnServer401 ist die Standardimplementierung (Hilfsfunktion), die eine Umleitung durchführt, wenn ein 401-Fehler in der Next.js (App Router) SSR-Umgebung auftritt.
import { redirectToUnauthorizedOnServer401 } from "@byeolnaerim/typed-rx-http/next";Funktionsregeln (fest):
- Umleitungsziel: /unauthorized
- queryString: redirect_uri=<aktuelle Seite> + logout=true
- Die aktuelle Seite wird aus dem x-page-url-Header gelesen (wenn nicht vorhanden, dann /)
Verwenden Sie diese Pfad-/Abfrage-Regeln nur, wenn sie mit Ihrem Projekt übereinstimmen. Wenn der Pfad anders oder die Abfrage-Regeln unterschiedlich sind, implementieren Sie onServer401 direkt und injizieren Sie es.
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export const onServer401 = async () => {
const h = await headers();
const pageUrl = h.get("x-page-url") || "/";
redirect(`/login?next=${encodeURIComponent(pageUrl)}`);
};callApiSsrCache
Next's next/cache (unstable_cache) basierter SSR Cache-Helfer.
- GET + cacheTime > 0 → force-cache + revalidate
- Sonst → no-store
- Cookie / Authorization pro Anfrage über headersProvider injizieren
- Bei 401 wird onServer401 ausgeführt, wenn vorhanden (normalerweise redirect())
import type { paths as Paths } from "./@types/ApiTypes";
import { firstValueFrom } from "rxjs";
import {
callApiSsrCache,
redirectToUnauthorizedOnServer401,
} from "@byeolnaerim/typed-rx-http/next";
const apiUrl = process.env.NEXT_PUBLIC_API_URL!;
const headersProvider = async () => {
const { cookies } = await import("next/headers");
const cookieStore = await cookies();
const cookieHeader = cookieStore
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join("; ");
const accessToken = cookieStore.get("accessToken")?.value;
return {
...(cookieHeader ? { Cookie: cookieHeader } : {}),
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
};
};
const obs$ = callApiSsrCache<Paths, { ok: true }, "adminList">({
baseUrl: apiUrl,
serviceArguments: { url: "/api/secure", method: "get" },
cacheForService: { cacheName: "adminList", cacheTime: 10_000 },
headersProvider,
onServer401: redirectToUnauthorizedOnServer401,
});
const res = await firstValueFrom(obs$);Next.js Integrationsbeispiel: Projekt rxjsHttpService/commonService gesamter Code
Die untenstehende rxjsHttpService.ts ist der gemeinsame HTTP-Adapter des Projekts, auf den die commonServiceFile des nachfolgenden Generatorbeispiels verweist. Es handelt sich nicht um eine von der Bibliothek generierte Datei, sondern um eine, die das Projekt direkt verwaltet und core client + session auth + CSR cache + SSR cache helper an einem Ort exportiert. Unten ist der gesamte Code.
rxjsHttpService.ts
tsimport {
createCsrCache,
createHeaderStore,
createHttpClient,
createSessionAuth,
} from "@byeolnaerim/typed-rx-http";
import type {
CacheForService,
ServiceArguments,
} from "@byeolnaerim/typed-rx-http";
import { defer, Observable } from "rxjs";
import { setLogin } from "@ui/handler/hooks/useAccounts";
import type { paths } from "./@types/ApiTypes";
import type { CacheNames } from "./@types/CacheNames";
export const redirectToUnauthorizedOnServer401 = async () => {
const { headers } = await import("next/headers");
const { redirect } = await import("next/navigation");
const h = await headers();
const pageUrl = h.get("x-page-url") || "/";
const redirectUri = encodeURIComponent(pageUrl);
redirect(`/unauthorized?redirect_uri=${redirectUri}&logout=true`);
};
export const headerStore = createHeaderStore({
"Content-Type": "application/json",
});
const headersProvider = async () => {
if (typeof window !== "undefined") {
/**
* 라이브러리쪽 코드가
* if (headersProvider) return headersProvider();
* if (headerStore) return headerStore.get();
* 이렇게 되어있음 같이 못씀, 방향자체는 맞음. 라이브러리쪽에서 marge해버리면 csr/ssr 구분못함
*/
return headerStore.get();
}
const { cookies } = await import("next/headers");
const cookieStore = await cookies();
const cookieHeader = cookieStore
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join("; ");
const authorization = cookieStore.get("Authorization")?.value;
const accessToken = cookieStore.get("accessToken")?.value;
return {
...(cookieHeader ? { Cookie: cookieHeader } : {}),
...(authorization
? {
Authorization: authorization.startsWith("Bearer ")
? authorization
: `Bearer ${authorization}`,
}
: {}),
...(!authorization && accessToken
? {
Authorization: accessToken.startsWith("Bearer ")
? accessToken
: `Bearer ${accessToken}`,
}
: {}),
};
};
export const service = createHttpClient<paths>({
baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
onServer401: redirectToUnauthorizedOnServer401,
defaultErrorMessage: () => {
return "알 수 없는 오류가 발생했습니다.";
},
headerStore,
headersProvider,
dropAuthWhenCacheControl: true,
});
const sessionAuth = createSessionAuth({
headerStore,
tokenUrl: "/api/auth/token",
refreshUrl: "/api/auth/token/refresh",
logoutUrl: "/api/auth/logout",
onLoginChange: (loggedIn) => {
if (typeof window !== "undefined") {
setLogin(loggedIn);
}
},
});
export const callApi = <
R,
TPath extends keyof paths = keyof paths,
TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
): Observable<R> => {
return defer(() =>
service.callApi<R, TPath, TMethod>(serviceArguments),
).pipe(sessionAuth.withSessionAuth());
};
export const callApiStream = <
R,
TPath extends keyof paths = keyof paths,
TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
): Observable<R> => {
return defer(() =>
service.callApiStream<R, TPath, TMethod>(serviceArguments),
).pipe(sessionAuth.withSessionAuth());
};
// CSR cache
const csrCache = createCsrCache<CacheNames>();
export const callApiClientCache = <
R,
TPath extends keyof paths = keyof paths,
TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
cacheForService: CacheForService<CacheNames>,
): Observable<R> => {
return csrCache.callApiCsrCache(callApi, serviceArguments, cacheForService);
};
export const removeCsrCache = csrCache.removeCsrCache;
export const callApiServerCache = <
R,
TPath extends keyof paths = keyof paths,
TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
cacheForService: CacheForService<CacheNames>,
): Observable<R> => {
if (typeof window !== "undefined") {
return callApiClientCache(serviceArguments, cacheForService);
}
return new Observable<R>((subscriber) => {
let closed = false;
import("@byeolnaerim/typed-rx-http/next")
.then(({ callApiSsrCache }) => {
if (closed) {
return;
}
const subscription = callApiSsrCache<paths, R, CacheNames>({
baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
serviceArguments: serviceArguments as ServiceArguments<
paths,
keyof paths,
keyof paths[keyof paths] & string,
R
>,
cacheForService,
headersProvider,
onServer401: redirectToUnauthorizedOnServer401,
}).subscribe(subscriber);
subscriber.add(() => {
closed = true;
subscription.unsubscribe();
});
})
.catch((error) => {
if (!closed) {
subscriber.error(error);
}
});
return () => {
closed = true;
};
});
};API Referenz (core)
createHttpClient<Paths>(options)
Rückgabe:
callApi<R>(args): Observable<R>callApiStream<RChunk>(args): Observable<RChunk>uploadFile({ file, url, ifNoneMatch?, headers? }): Observable<Response>createSSEObservable<R>(args): Observable<R>
Optionen:
baseUrl: stringheaderStore?: HeaderStoreheadersProvider?: () => Record<string, string> | Promise<Record<string, string>>- dropAuthWhenCacheControl?: boolean (Standard: true)
onServer401?: () => void | Promise<void>
createHeaderStore(initial?)
get(), set(), merge(), remove(), clear()
createCsrCache<CacheName>()
callApiCsrCache(callApiFn, serviceArgs, cacheForService)- removeCsrCache(cacheName) (Typ + String)
createSessionAuth(options)
withSessionAuth(), withEnsureToken()ensureToken$(), refreshToken$(), logout$()
Laufzeitanforderungen
- fetch / Response API verwenden (rxjs/fetch)
- Streaming (NDJSON) benötigt ReadableStream + TextDecoder
- SSE benötigt EventSource
In den meisten modernen Browsern und der Next.js Laufzeit ist dies standardmäßig verfügbar. In benutzerdefinierten Node-Laufzeiten kann ein Polyfill erforderlich sein.
Auto Node Script: OpenAPI/Swagger Code-Generierung (optional)
Dieses Paket bietet zusätzlich zum Laufzeit-HTTP-Client ein Node-Skript zur Generierung von Typ-/Service-Code aus OpenAPI/Swagger JSON. Dieses Skript ist eine optionale Funktion.
Allgemeine Benutzer von @byeolnaerim/typed-rx-http, /next, /rsocket müssen dieses Skript nicht ausführen und müssen openapi-typescript nicht installieren.
import {
connectToSwaggerEventStream,
generateSwaggerFromHttp,
generateSwaggerFromFile,
} from "@byeolnaerim/typed-rx-http/auto-node-script/openapi";Abhängigkeitsisolierung
Für die Generierung von OpenAPI-Typen ist die openapi-typescript CLI erforderlich. Dieses Paket fügt jedoch openapi-typescript nicht zu den allgemeinen Abhängigkeiten hinzu.
Die @byeolnaerim/typed-rx-http Bibliothek selbst verwendet TypeScript 6.0.3 in devDependencies.typescript. Die Entry-Points und Bibliotheks-Bauten von typed-rx-http, /next, /rsocket halten sich an diesen TypeScript 6.0.3 Standard.
Allerdings kann openapi-typescript noch eine bestimmte TypeScript 5.x Version erfordern, weshalb das OpenAPI Auto Node Script nur in einer separaten npx temporären Ausführungsumgebung zusammen mit openapi-typescript und [email protected] ausgeführt wird. Diese temporäre Ausführungsumgebung ändert nicht die devDependencies.typescript 6.0.3 der Bibliothek und verwendet auch nicht die in Ihrem Projekt installierte Version von typescript oder openapi-typescript.
Der Standard besteht darin, nur die folgenden Befehle zum Zeitpunkt der Ausführung des Auto-Skripts zu generieren und auszuführen.
npx -y -p openapi-typescript@latest -p [email protected] openapi-typescript swagger.json --output ./src/handler/service/@types/ApiTypes.d.tsDaher ändert sich die Verwendung selbst nicht. Sie können das Auto Node Script wie gewohnt aufrufen, und in der Phase der Generierung von OpenAPI-Typen wird nur die isolierte TypeScript 5.9.3 Umgebung verwendet. Benutzer, die das Auto-Skript nicht verwenden, sind überhaupt nicht an openapi-typescript oder TypeScript 5.9.3 gebunden.
Wenn nötig, können Sie den Befehl direkt mit openApiTypescriptCommand festlegen.
generateSwaggerFromFile({
inputFile: "./swagger.json",
openApiTypescriptCommand:
"npx -y -p [email protected] -p [email protected] openapi-typescript ./swagger.json --output ./src/handler/service/@types/ApiTypes.d.ts",
});Oder Sie können nur die Paketversionen, die den Standardbefehl konfigurieren, ändern.
generateSwaggerFromFile({
inputFile: "./swagger.json",
openApiTypescriptPackage: "[email protected]",
openApiTypescriptTypescriptPackage: "[email protected]",
});Generierte Dateien
Die Standardeinstellungen erzeugen die folgenden Dateien.
swagger.json
src/handler/service/@types/ApiTypes.d.ts
src/handler/service/auto/*Service.ts
src/handler/service/@types/auto/*Types.ts
src/handler/service/apiUnionArrays.tsapiUnionArrays.ts erstellt nicht nur OpenAPI-Schema-Enums, sondern auch Konstantenarrays für Query-, Path-, Header- und Cookie-Parameter-Enums. Es verarbeitet auch die items.enum von Array-Query-Parametern.
EventStream Überwachung
connectToSwaggerEventStream({
hostname: "localhost",
port: 8788,
path: "/oauth2/for-local/get-swagger",
serviceDir: "./src/handler/service",
typesDir: "./src/handler/service/@types",
commonServiceFile: "./src/handler/service/rxjsHttpService.ts",
});HTTP-Einmalanfrage
await generateSwaggerFromHttp({
hostname: "localhost",
port: 8788,
path: "/oauth2/for-local/get-swagger",
});Aus lokalen Dateien generieren
generateSwaggerFromFile({
inputFile: "./swagger.json",
});Beispiel für die Integration eines bestehenden Projekts: WebFlux + Swagger automatische Generierung
Ab hier handelt es sich um ein Beispiel für die Integration eines Projekts, das Backend-Swagger und automatisch generierte Services gemeinsam nutzt. Die oben genannten Core-Nutzungsanweisungen und Auswahlfunktionen können nur mit typed-rx-http verwendet werden, während der folgende Ablauf in Projekten angewendet wird, die die automatische Generierung von Swagger-basierten Services verwenden.
1. Schreiben Sie den REST-Endpunkt im Backend.
Der Backend-Code wird wie gewohnt geschrieben. In diesem Beispiel wird der Name als Pfadvariable und die Nachricht als Abfrageparameter empfangen. MonoAntwortet mit.
TypedRxHttpExampleRouter.java
java@Bean
public RouterFunction<ServerResponse> typedRxHttpExample(
TypedRxHttpExampleHandler handler
) {
return route()
.GET("/typed-rx-http/example/{name}", handler::getExample)
.build();
}2. Erstellen Sie den Front-Service in Swagger.
Nehmen Sie den Backend-Code. swagger.jsonund generieren Sie die Typ- und Dienstdateien. Dieser Vorgang kann einmal beim Ausführen des Entwicklungsservers aufgerufen oder mit einem Watch-Skript verbunden werden.
generateSwagger.cjs
jsconst {
generateSwaggerFromHttp,
} = require("@byeolnaerim/typed-rx-http/auto-node-script/openapi");
generateSwaggerFromHttp({
hostname: "localhost",
port: 8123,
path: "/for-local/get-swagger",
swaggerFile: "./swagger.json",
serviceDir: "./src/handler/service",
typesDir: "./src/handler/service/@types",
typesFile: "./src/handler/service/@types/ApiTypes.d.ts",
commonServiceFile:
"./src/handler/service/rxjsHttpService.ts",
});3. Senden Sie Anfragen mit der generierten Funktion.
Sie können unten Frontcodeund Backend-Codeändern. ErgebnisKlicken Sie darauf, um den Ausführungsbildschirm rechts zu öffnen. Ändern Sie die Werte und drücken Sie die Anfrage-Schaltfläche.
TypedRxHttpRequestExample.tsx
tsximport { byName } from "@handler/service/auto/TypedRxHttpExampleService";
import { firstValueFrom } from "rxjs";
const response = await firstValueFrom(
byName({
path: {
name: name.trim(),
},
params: {
message: message.trim() || undefined,
},
}),
);
setResponse(response);