alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
212 lines (179 loc) • 4.78 kB
text/typescript
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(),
);