feat: Sprint 1 remainder — Postgres store, Docker client, warm pool, sessions route
Mirror to GitHub / mirror (push) Canceled after 0s

- PostgresUserStore (Queryable) + migration + env-gated wiring
- DockerodeClient behind injected DockerEngine
- ImageManager, WarmPool, Dockerfile.wordpress, docker-compose
- POST /sessions spins up sandbox (503 when unconfigured)
- ADRs 0014-0015; 68 unit tests green
This commit is contained in:
SinachPat
2026-08-15 20:18:34 +01:00
parent 97f31315bd
commit df2315087d
23 changed files with 1119 additions and 11 deletions
+43
View File
@@ -0,0 +1,43 @@
import Docker from 'dockerode';
import { randomUUID } from 'node:crypto';
import type { DockerClient, SandboxInfo } from './docker-client.ts';
export type ContainerInspect = {
Id: string;
State: { Running: boolean };
};
export type ContainerLike = {
start(): Promise<void>;
inspect(): Promise<ContainerInspect>;
remove(opts?: { force?: boolean }): Promise<void>;
};
export type DockerEngine = {
createContainer(opts: { Image: string; name: string }): Promise<ContainerLike>;
getContainer(id: string): ContainerLike;
};
export class DockerodeClient implements DockerClient {
constructor(private readonly engine: DockerEngine = new Docker() as unknown as DockerEngine) {}
async createSandbox(image: string): Promise<SandboxInfo> {
const container = await this.engine.createContainer({ Image: image, name: `wursor-${randomUUID()}` });
await container.start();
const info = await container.inspect();
return { id: info.Id, status: info.State.Running ? 'running' : 'destroyed' };
}
async destroySandbox(id: string): Promise<void> {
await this.engine.getContainer(id).remove({ force: true });
}
async status(id: string): Promise<SandboxInfo | undefined> {
try {
const info = await this.engine.getContainer(id).inspect();
return { id: info.Id, status: info.State.Running ? 'running' : 'destroyed' };
} catch {
return undefined;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
export type ImageManagerOptions = {
baseImage: string;
webPort: number;
};
export type SandboxContainerSpec = {
image: string;
name: string;
ports: { container: number; host: number }[];
labels: Record<string, string>;
};
export class ImageManager {
constructor(private readonly opts: ImageManagerOptions) {}
imageRef(): string {
return this.opts.baseImage;
}
containerSpec(name: string): SandboxContainerSpec {
return {
image: this.opts.baseImage,
name,
ports: [{ container: 80, host: this.opts.webPort }],
labels: { 'wursor.managed': 'true' },
};
}
}