UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

212 lines (179 loc) 4.78 kB
import { Alepha, z } from "alepha"; import { $action, ServerProvider } from "alepha/server"; import { describe, it } from "vitest"; import { $client, $remote, AlephaServerLinks, LinkProvider, ServerLinksProvider, } from "../index.ts"; describe("$remote", () => { it("should enable remote action calls between services", async ({ expect, }) => { class App { ping = $action({ schema: { response: z.object({ pong: z.boolean(), }), }, handler: () => { return { pong: true }; }, }); } const alepha1 = Alepha.create().with(AlephaServerLinks).with(App); // -- class Config { alepha1 = $remote({ url: () => alepha1.inject(ServerProvider).hostname, }); } const alepha2 = Alepha.create({ env: {} }) .with(AlephaServerLinks) .with(Config); // -- await alepha1.start(); await alepha2.start(); const app = alepha2.inject(LinkProvider).client<App>(); const ping = () => alepha2.context.run(() => app.ping()); expect(await ping()).toStrictEqual({ pong: true }); }); it("should handle complex multi-service architecture with proxies", async ({ expect, }) => { const env = { LOG_LEVEL: "warn" as const, }; class ServiceC { print = $action({ handler: () => "TADA!", }); } const c = Alepha.create({ env }).with(ServiceC).with(ServerLinksProvider); class ServiceB { compute = $action({ handler: () => { return "42"; }, }); } const b = Alepha.create({ env }).with(ServiceB).with(ServerLinksProvider); class ServiceA { br = $remote({ url: () => b.inject(ServerProvider).hostname, }); cr = $remote({ url: () => c.inject(ServerProvider).hostname, proxy: true, }); sb = $client<ServiceB>(); sc = $client<ServiceC>(); getReport = $action({ handler: async () => { const b = await this.sb.compute(); const c = await this.sc.print(); return `B: ${b}, C: ${c}`; }, }); } const a = Alepha.create({ env }).with(ServiceA).with(ServerLinksProvider); class WebApp { a = $remote({ url: () => a.inject(ServerProvider).hostname, proxy: true, }); ping = $action({ handler: async () => { return "pong"; }, }); } const front = Alepha.create({ env }).with(WebApp).with(ServerLinksProvider); await c.start(); await b.start(); await a.start(); await front.start(); const linkProvider = front.inject(LinkProvider); // Service C: single action expect(await getRegistry(c)).toEqual({ actions: { print: { path: "/print", }, }, prefix: "/api", }); // Service B: single action expect(await getRegistry(b)).toEqual({ actions: { compute: { path: "/compute", }, }, prefix: "/api", }); // Service A: own action + proxied C action expect(await getRegistry(a)).toEqual({ actions: { getReport: { path: "/getReport", }, print: { path: "/print", service: "cr", }, }, prefix: "/api", }); // Frontend: own action + proxied A actions (including A's proxied C) expect(await getRegistry(front)).toEqual({ actions: { ping: { path: "/ping", }, getReport: { path: "/getReport", service: "a", }, print: { path: "/cr/print", service: "a", }, }, prefix: "/api", }); expect(await linkProvider.client<WebApp>().ping.run({})).toEqual("pong"); expect( await linkProvider .client<WebApp>(front.inject(ServerProvider)) .ping.fetch({}) .then((it) => it.data), ).toEqual("pong"); expect(await linkProvider.client<ServiceA>().getReport()).toEqual( "B: 42, C: TADA!", ); expect( await linkProvider .client<ServiceA>() .getReport.fetch({}) .then((it) => it.data), ).toEqual("B: 42, C: TADA!"); expect(await linkProvider.client<ServiceC>().print()).toEqual("TADA!"); expect( await linkProvider .client<ServiceC>() .print.fetch({}) .then((it) => it.data), ).toEqual("TADA!"); await expect(() => linkProvider.client<ServiceB>().compute(), ).rejects.toThrow("Action compute not found"); }); }); const getRegistry = (a: Alepha) => fetch(`${a.inject(ServerProvider).hostname}/api/_links`).then((res) => res.json(), );