Mulai Cepat

Klien HTTP berbasis RxJS yang aman untuk tipe untuk TypeScript + (opsional) adapter Next.js/RSocket.


Fitur utama

  • Menyuntikkan tipe Paths gaya OpenAPI yang diperoleh dari skema turunan Swagger/OpenAPI / AsyncAPI / kontrak kustom, dll. (secara konvensional disebut paths) untuk memastikan stabilitas tipe permintaan.
  • Semua API mengembalikan Observable RxJS.
  • Core bersifat independen dari framework (tidak bergantung pada Next.js).
  • Fitur khusus Next.js dipisahkan ke titik masuk @byeolnaerim/typed-rx-http/next → hanya diperlukan Next.js saat mengimpor /next.
  • Fitur khusus RSocket dipisahkan ke titik masuk @byeolnaerim/typed-rx-http/rsocket → hanya diperlukan paket RSocket saat mengimpor /rsocket.

Instalasi

npm
bash
npm i @byeolnaerim/typed-rx-http rxjs

Titik masuk

Core (independen framework)
ts
import {
  createHttpClient,
  createHeaderStore,
  createCsrCache,
  createSessionAuth,
  HttpResponseError,
  isHttpResponseError,
  type ServiceArguments,
  type CacheForService,
} from "@byeolnaerim/typed-rx-http";
Adapter Next.js (opsional)
ts
import {
  callApiSsrCache,
  redirectToUnauthorizedOnServer401,
} from "@byeolnaerim/typed-rx-http/next";

Jangan impor /next di proyek yang tidak menggunakan Next.js.

Adapter RSocket (opsional)

Hanya instal paket peer di proyek yang menggunakan RSocket.

bash
npm i rsocket-core rsocket-types rsocket-websocket-client
ts
import { 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);
});

Jangan impor /rsocket di proyek yang tidak menggunakan RSocket.

Cara menggunakan Core

1) Siapkan tipe Paths (biasanya adalah paths OpenAPI)

Paths dari createHttpClient<Paths>() adalah tipe yang menggambarkan "spesifikasi permintaan (route)". Dalam dokumen, biasanya disebut paths, tetapi tidak harus OpenAPI/Swagger, dan tidak harus bernama paths.

Namun, Core menggunakan batasan OpenApiPathsLike secara internal, sehingga Paths harus memiliki bentuk yang mirip dengan paths OpenAPI seperti di bawah ini.

  • Kunci teratas: string jalur URL (misalnya "/users/{id}")
  • Kunci bawah: metode HTTP (get/post/put/delete/patch …)
  • Setiap metode memiliki bidang seperti parameters.query/path/header/cookie, requestBody, responses (atau never)

Core terutama merujuk pada bidang di atas untuk membangun tipe ServiceArguments.

  • url: keyof Paths
  • method: keyof Paths[url]
  • queryString: parameters.query
  • pathVariable: parameters.path
  • body: requestBody

Contoh: output openapi-typescript biasanya memiliki spesifikasi seperti di bawah ini (beberapa disingkat).

ts
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"];
          };
        };
      };
    };
  };
}
ts
// aliasing: OpenAPI `paths` -> `Paths`
import type { paths as Paths } from "./@types/ApiTypes";

2) Buat HeaderStore

HeaderStore adalah penyimpanan in-memory sederhana untuk mengelola header dasar di CSR.

ts
import { createHeaderStore } from "@byeolnaerim/typed-rx-http";

export const headerStore = createHeaderStore({
  "Content-Type": "application/json",
});

3) Buat klien HTTP

  • headerStore adalah opsional, tetapi disarankan untuk digunakan jika ingin menggunakan header dasar/otentikasi sesi di CSR.
  • headersProvider digunakan ketika perhitungan header diperlukan untuk setiap permintaan seperti di SSR/multi-tenant.
ts
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) Panggilan API (tipe aman)

Tipe permintaan (url/metode/pathVariable/queryString/body) ditentukan oleh tipe yang disuntikkan pengguna ke createHttpClient<Paths>() (secara konvensional jalur OpenAPI). Tipe respons dipilih oleh pemanggil di callApi<R>() dengan generik R (inti tidak secara otomatis menyimpulkan dari respons).

ts
export const getAdminList = () => {
  return client.callApi<{ items: any[]; total: number }>({
    url: "/api/account/search/admin/get-list",
    method: "get",
    queryString: { pageNumber: 1, pageSize: 20 },
  });
};

Pembungkus respons (ResponseWrapper) — opsional

Perpustakaan ini tidak memaksa pembungkus respons. Anda dapat memilih bentuk respons secara generik untuk setiap API.

Respons yang dibungkus
ts
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,
  });
};
Respons tanpa pembungkus
ts
export const bidCreate = (body: unknown) => {
  return client.callApi<{ requestId: string }>({
    url: "/api/bid/create/bid/request",
    method: "post",
    body,
  });
};

Streaming (NDJSON)

Gunakan callApiStream ketika server mengirimkan NDJSON (satu JSON per baris). Jika tidak ada header Accept, secara default diatur ke application/x-ndjson.

ts
export const streamEvents = () => {
  return client.callApiStream<{ eventName: string; content: unknown }>({
    url: "/api/event/stream",
    method: "get",
    // headers: { Accept: "application/x-ndjson" }, // 선택
  });
};

Cache CSR (cache klien)

Fungsi yang disediakan oleh createCsrCache<CacheName>():

  • callApiCsrCache(callApiFn, serviceArgs, cacheOptions)
  • removeCsrCache(cacheName) — mendukung nama cache tipe + string
ts
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"); // 문자열

Plugin otentikasi berbasis sesi (opsional)

createSessionAuth memisahkan logika otentikasi sesi dari inti dan menghubungkannya sebagai opsi.

Tindakan:

  • Menjaga Authorization di headerStore
  • Sinkronisasi token dengan ensureToken$() (/api/auth/token)
  • Saat 401 terjadi, coba refresh sekali (/api/auth/token/refresh) lalu coba ulang permintaan asli
  • Jika refresh gagal, logout (/api/auth/logout) dan sampaikan kesalahan
  • Perubahan status login ditangani dari luar dengan callback onLoginChange
ts
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());
};

Jika hanya perlu sinkronisasi token tanpa refresh/retry:

ts
return client.callApi(...).pipe(auth.withEnsureToken());

Penanganan kesalahan

Jika bukan 2xx, lempar HttpResponseError (termasuk status, respons, args, data).

Kompatibilitas warisan: jika badan kesalahan berbentuk { resultType: ... }, lempar objek itu langsung.

ts
import { isHttpResponseError } from "@byeolnaerim/typed-rx-http";

client.callApi(...).subscribe({
  error: (e) => {
    if (isHttpResponseError(e)) {
      console.log(e.status, e.data);
    }
  },
});

Adaptor Next.js (/next)

redirectToUnauthorizedOnServer401

redirectToUnauthorizedOnServer401 adalah implementasi dasar (fungsi utilitas) untuk melakukan redirect ketika 401 terjadi di lingkungan SSR Next.js (App Router).

ts
import { redirectToUnauthorizedOnServer401 } from "@byeolnaerim/typed-rx-http/next";

Aturan tindakan (tetap):

  • Target redirect: /unauthorized
  • queryString: redirect_uri=<halaman saat ini> + logout=true
  • Halaman saat ini dibaca dari header x-page-url (jika tidak ada, gunakan /)

Artinya, gunakan aturan jalur/query di atas hanya jika sesuai dengan proyek. Jika jalurnya berbeda atau aturan query berbeda, Anda dapat mengimplementasikan onServer401 secara langsung dan menyuntikkannya.

ts
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

Ini adalah pembantu cache SSR berbasis next/cache(unstable_cache) dari Next.

  • GET + cacheTime > 0 → force-cache + revalidate
  • Selain itu → no-store
  • Menyuntikkan Cookie / Authorization per permintaan dengan headersProvider
  • Jika 401 terjadi dan ada onServer401, maka akan dieksekusi (biasanya redirect())
ts
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$);

Contoh integrasi Next.js: Kode lengkap proyek rxjsHttpService/commonService

Di bawah ini adalah rxjsHttpService.ts yang merupakan adapter HTTP umum proyek yang ditunjuk oleh commonServiceFile dalam contoh generator berikut. Ini bukan file yang dihasilkan oleh pustaka, tetapi dikelola langsung oleh proyek, mengekspor core client + session auth + CSR cache + SSR cache helper di satu tempat. Berikut adalah kode lengkapnya.

rxjsHttpService.ts
ts
import {
	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;
		};
	});
};

Referensi API (inti)

createHttpClient<Paths>(options)

Mengembalikan:

  • callApi<R>(args): Observable<R>
  • callApiStream<RChunk>(args): Observable<RChunk>
  • uploadFile({ file, url, ifNoneMatch?, headers? }): Observable<Response>
  • createSSEObservable<R>(args): Observable<R>

Opsi:

  • baseUrl: string
  • headerStore?: HeaderStore
  • headersProvider?: () => Record<string, string> | Promise<Record<string, string>>
  • dropAuthWhenCacheControl?: boolean (default: true)
  • onServer401?: () => void | Promise<void>
createHeaderStore(initial?)

get(), set(), merge(), remove(), clear()

createCsrCache<CacheName>()
  • callApiCsrCache(callApiFn, serviceArgs, cacheForService)
  • removeCsrCache(cacheName) (tipe + string)
createSessionAuth(options)
  • withSessionAuth(), withEnsureToken()
  • ensureToken$(), refreshToken$(), logout$()

Persyaratan runtime

  • Menggunakan fetch / Response API (rxjs/fetch)
  • Streaming (NDJSON) memerlukan ReadableStream + TextDecoder
  • SSE memerlukan EventSource

Tersedia di sebagian besar browser modern dan runtime Next.js. Runtime Node kustom mungkin memerlukan polyfill.

Auto Node Script: Menghasilkan kode OpenAPI/Swagger (opsional)

Paket ini menyediakan skrip Node yang menghasilkan kode tipe/layanan dari JSON OpenAPI/Swagger, terpisah dari klien HTTP runtime. Skrip ini adalah fitur opsional.

Pengguna umum @byeolnaerim/typed-rx-http, /next, /rsocket tidak perlu menjalankan skrip ini dan tidak perlu menginstal openapi-typescript.

ts
import {
  connectToSwaggerEventStream,
  generateSwaggerFromHttp,
  generateSwaggerFromFile,
} from "@byeolnaerim/typed-rx-http/auto-node-script/openapi";
Isolasi ketergantungan

Menghasilkan tipe OpenAPI memerlukan openapi-typescript CLI. Namun, paket ini tidak memasukkan openapi-typescript ke dalam dependencies umum.

Pustaka @byeolnaerim/typed-rx-http sendiri menggunakan TypeScript 6.0.3 di devDependencies.typescript. Entry point typed-rx-http, /next, /rsocket dan build pustaka mempertahankan standar TypeScript 6.0.3 ini.

Namun, openapi-typescript mungkin masih memerlukan versi TypeScript 5.x tertentu, sehingga hanya OpenAPI auto node script yang dijalankan dalam lingkungan eksekusi sementara npx terpisah dengan openapi-typescript dan [email protected]. Lingkungan eksekusi sementara ini tidak mengubah devDependencies.typescript 6.0.3 pustaka dan tidak menggunakan versi typescript atau openapi-typescript yang diinstal di proyek pengguna.

Nilai default adalah menghasilkan dan menjalankan perintah di bawah ini hanya pada saat eksekusi skrip otomatis.

bash
npx -y -p openapi-typescript@latest -p [email protected] openapi-typescript swagger.json --output ./src/handler/service/@types/ApiTypes.d.ts

Oleh karena itu, cara penggunaannya tidak berubah. Anda dapat memanggil skrip node otomatis seperti biasa, dan hanya lingkungan TypeScript 5.9.3 yang terisolasi yang digunakan pada tahap pembuatan tipe OpenAPI. Pengguna yang tidak menggunakan skrip otomatis tidak terikat pada openapi-typescript atau TypeScript 5.9.3 sama sekali.

Jika perlu, Anda dapat mengunci perintah secara langsung dengan openApiTypescriptCommand.

ts
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",
});

Atau Anda hanya dapat mengubah versi paket yang mengonfigurasi perintah dasar.

ts
generateSwaggerFromFile({
  inputFile: "./swagger.json",
  openApiTypescriptPackage: "[email protected]",
  openApiTypescriptTypescriptPackage: "[email protected]",
});
File yang dihasilkan

Pengaturan default menghasilkan file-file di bawah ini.

text
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.ts

apiUnionArrays.ts tidak hanya menghasilkan enum skema OpenAPI tetapi juga menghasilkan enum parameter query, path, header, dan cookie sebagai array konstan. Juga memproses items.enum dari parameter query array.

Pemantauan EventStream
ts
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",
});
Permintaan HTTP satu kali
ts
await generateSwaggerFromHttp({
  hostname: "localhost",
  port: 8788,
  path: "/oauth2/for-local/get-swagger",
});
Dihasilkan dari file lokal
ts
generateSwaggerFromFile({
  inputFile: "./swagger.json",
});

Contoh integrasi proyek yang ada: WebFlux + Swagger otomatis

Dari sini adalah contoh integrasi proyek yang menggunakan Swagger backend dan layanan otomatis. Cara penggunaan Core di atas dan fitur pilihan dapat digunakan hanya dengan typed-rx-http itu sendiri, dan alur di bawah ini diterapkan pada proyek yang menggunakan pembuatan layanan otomatis berbasis Swagger.

1. Tulis endpoint REST di backend.

Tulis kode backend seperti biasa. Dalam contoh ini, terima nama sebagai variabel jalur dan pesan sebagai parameter kueri. Monodan balas.

TypedRxHttpExampleRouter.java
java
@Bean
public RouterFunction<ServerResponse> typedRxHttpExample(
  TypedRxHttpExampleHandler handler
) {
  return route()
    .GET("/typed-rx-http/example/{name}", handler::getExample)
    .build();
}

2. Buat layanan front di Swagger.

Ambil dari backend. swagger.jsonHasilkan file tipe dan layanan. Tugas ini dapat dipanggil sekali saat menjalankan server pengembangan atau terhubung dengan skrip watch.

generateSwagger.cjs
js
const {
  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. Kirim permintaan dengan fungsi yang dihasilkan.

Anda dapat mencoba mengubah di bawah ini. Kode Backenddan HasilTekan untuk membuka layar eksekusi di sebelah kanan. Ubah nilai dan tekan tombol permintaan. Bagian yang terlihat di proyek nyata.Kode Front

TypedRxHttpRequestExample.tsx
tsx
import { 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);
© 2026 Byeolnaerim. Semua hak dilindungi.PengenalanKebijakan Penanganan Data Pribadi