t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/Local/fixtures/rpc-spawner-devserver-parent.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

111 lines
3.5 KiB
TypeScript

// Test fixture: boots an RpcSpawner, starts the dev sidecar, reconciles a
// real DevServer through its Command.Dev provider group, prints the
// dev-server pid, then idles until the test harness kills this parent process.
// Relative imports (not `@/` alias) so this file runs under both Bun and Node
// without a paths-aware loader.
import { newWebSocketRpcSession } from "capnweb";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Schedule from "effect/Schedule";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
import * as HttpBody from "effect/http/HttpBody";
import * as HttpClient from "effect/http/HttpClient";
import { unwrapRpcHandlers } from "../../../src/Local/RpcSerialization.ts";
import type { RpcProxyApi } from "../../../src/Local/RpcServer.ts";
import {
encodeSessionEnvironment,
SESSION_ENV_PARAM,
} from "../../../src/Local/RpcServerEnvironment.ts";
import { layerServer, RpcSpawner } from "../../../src/Local/RpcSpawner.ts";
import { PlatformServices } from "../../../src/Util/PlatformServices.ts";
const sidecarEntry = process.argv[2];
const providersUrl = process.argv[3];
const command = process.argv[4];
const pidFile = process.argv[5];
if (!sidecarEntry || !providersUrl || !command || !pidFile) {
console.error(
"usage: rpc-spawner-devserver-parent.ts <sidecar-entry-url> <providers-url> <command> <pid-file>",
);
process.exit(2);
}
const program = Effect.gen(function* () {
const sp = yield* RpcSpawner;
const http = yield* HttpClient.HttpClient;
const wsUrl = yield* http
.post(sp.url, {
body: yield* HttpBody.json({ serverEntryUrl: sidecarEntry }),
})
.pipe(Effect.flatMap((res) => res.text));
// Sessions carry the stack environment (children are shared across stacks).
const sessionUrl = new URL(wsUrl);
sessionUrl.searchParams.set(
SESSION_ENV_PARAM,
encodeSessionEnvironment({
alchemyContext: {
dotAlchemy: "/tmp/.alchemy",
updateStateStore: false,
dev: true,
adopt: false,
},
stack: { name: "test", stage: "dev" },
}),
);
const session = newWebSocketRpcSession<RpcProxyApi>(sessionUrl.toString());
const wrapped = yield* Effect.promise(
() =>
session.getProvider("Command.Dev", providersUrl) as ReturnType<
RpcProxyApi["getProvider"]
>,
);
const provider = unwrapRpcHandlers(wrapped, []);
yield* provider.reconcile({
id: "Dev",
fqn: "Dev",
instanceId: "Dev",
news: {
command,
env: { PID_FILE: pidFile, MARKER: "rpc-devserver" },
},
olds: undefined,
output: undefined,
session: {} as never,
bindings: [],
});
const pid = yield* Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const content = yield* fs.readFileString(pidFile);
return (JSON.parse(content) as { pid: number }).pid;
}).pipe(Effect.retry({ schedule: Schedule.spaced("100 millis"), times: 50 }));
console.log(`PARENT_PID=${process.pid}\n`);
console.log(`DEVSERVER_PID=${pid}\n`);
const stop = yield* Deferred.make<void>();
yield* Deferred.await(stop);
});
program
.pipe(
Effect.provide([
Layer.provide(
layerServer({ profile: undefined, envFile: undefined }),
PlatformServices,
),
PlatformServices,
FetchHttpClient.layer,
]),
Effect.scoped,
Effect.runPromise,
)
.catch((e) => {
console.error(e);
process.exit(1);
});