Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import fs from "node:fs/promises";
import { z } from "zod";
import { writeJsonAtomic } from "../../infra/json-files.js";
import { safeParseJsonWithSchema } from "../../utils/zod-parse.js";
import { acquireSessionWriteLock } from "../session-write-lock.js";
import { SANDBOX_BROWSER_REGISTRY_PATH, SANDBOX_REGISTRY_PATH } from "./constants.js";
export type SandboxRegistryEntry = {
containerName: string;
backendId?: string;
runtimeLabel?: string;
sessionKey: string;
createdAtMs: number;
lastUsedAtMs: number;
image: string;
configLabelKind?: string;
configHash?: string;
};
type SandboxRegistry = {
entries: SandboxRegistryEntry[];
};
export type SandboxBrowserRegistryEntry = {
containerName: string;
sessionKey: string;
createdAtMs: number;
lastUsedAtMs: number;
image: string;
configHash?: string;
cdpPort: number;
noVncPort?: number;
};
type SandboxBrowserRegistry = {
entries: SandboxBrowserRegistryEntry[];
};
type RegistryReadMode = "strict" | "fallback";
type RegistryEntry = {
containerName: string;
};
type RegistryFile<T extends RegistryEntry> = {
entries: T[];
};
type UpsertEntry = RegistryEntry & {
backendId?: string;
runtimeLabel?: string;
createdAtMs: number;
image: string;
configLabelKind?: string;
configHash?: string;
};
const RegistryEntrySchema = z
.object({
containerName: z.string(),
})
.passthrough();
const RegistryFileSchema = z.object({
entries: z.array(RegistryEntrySchema),
});
function normalizeSandboxRegistryEntry(entry: SandboxRegistryEntry): SandboxRegistryEntry {
return {
...entry,
backendId: entry.backendId?.trim() || "docker",
runtimeLabel: entry.runtimeLabel?.trim() || entry.containerName,
configLabelKind: entry.configLabelKind?.trim() || "Image",
};
}
async function withRegistryLock<T>(registryPath: string, fn: () => Promise<T>): Promise<T> {
const lock = await acquireSessionWriteLock({
sessionFile: registryPath,
allowReentrant: false,
timeoutMs: 60_000,
});
try {
return await fn();
} finally {
await lock.release();
}
}
async function readRegistryFromFile<T extends RegistryEntry>(
registryPath: string,
mode: RegistryReadMode,
): Promise<RegistryFile<T>> {
try {
const raw = await fs.readFile(registryPath, "utf-8");
const parsed = safeParseJsonWithSchema(RegistryFileSchema, raw) as RegistryFile<T> | null;
if (parsed) {
return parsed;
}
if (mode === "fallback") {
return { entries: [] };
}
throw new Error(`Invalid sandbox registry format: ${registryPath}`);
} catch (error) {
const code = (error as { code?: string } | null)?.code;
if (code === "ENOENT") {
return { entries: [] };
}
if (mode === "fallback") {
return { entries: [] };
}
if (error instanceof Error) {
throw error;
}
throw new Error(`Failed to read sandbox registry file: ${registryPath}`, { cause: error });
}
}
async function writeRegistryFile<T extends RegistryEntry>(
registryPath: string,
registry: RegistryFile<T>,
): Promise<void> {
await writeJsonAtomic(registryPath, registry, { trailingNewline: true });
}
export async function readRegistry(): Promise<SandboxRegistry> {
const registry = await readRegistryFromFile<SandboxRegistryEntry>(
SANDBOX_REGISTRY_PATH,
"fallback",
);
return {
entries: registry.entries.map((entry) => normalizeSandboxRegistryEntry(entry)),
};
}
function upsertEntry<T extends UpsertEntry>(entries: T[], entry: T): T[] {
const existing = entries.find((item) => item.containerName === entry.containerName);
const next = entries.filter((item) => item.containerName !== entry.containerName);
next.push({
...entry,
backendId: entry.backendId ?? existing?.backendId,
runtimeLabel: entry.runtimeLabel ?? existing?.runtimeLabel,
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
image: existing?.image ?? entry.image,
configLabelKind: entry.configLabelKind ?? existing?.configLabelKind,
configHash: entry.configHash ?? existing?.configHash,
});
return next;
}
function removeEntry<T extends RegistryEntry>(entries: T[], containerName: string): T[] {
return entries.filter((entry) => entry.containerName !== containerName);
}
async function withRegistryMutation<T extends RegistryEntry>(
registryPath: string,
mutate: (entries: T[]) => T[] | null,
): Promise<void> {
await withRegistryLock(registryPath, async () => {
const registry = await readRegistryFromFile<T>(registryPath, "strict");
const next = mutate(registry.entries);
if (next === null) {
return;
}
await writeRegistryFile(registryPath, { entries: next });
});
}
export async function updateRegistry(entry: SandboxRegistryEntry) {
await withRegistryMutation<SandboxRegistryEntry>(SANDBOX_REGISTRY_PATH, (entries) =>
upsertEntry(entries, entry),
);
}
export async function removeRegistryEntry(containerName: string) {
await withRegistryMutation<SandboxRegistryEntry>(SANDBOX_REGISTRY_PATH, (entries) => {
const next = removeEntry(entries, containerName);
if (next.length === entries.length) {
return null;
}
return next;
});
}
export async function readBrowserRegistry(): Promise<SandboxBrowserRegistry> {
return await readRegistryFromFile<SandboxBrowserRegistryEntry>(
SANDBOX_BROWSER_REGISTRY_PATH,
"fallback",
);
}
export async function updateBrowserRegistry(entry: SandboxBrowserRegistryEntry) {
await withRegistryMutation<SandboxBrowserRegistryEntry>(
SANDBOX_BROWSER_REGISTRY_PATH,
(entries) => upsertEntry(entries, entry),
);
}
export async function removeBrowserRegistryEntry(containerName: string) {
await withRegistryMutation<SandboxBrowserRegistryEntry>(
SANDBOX_BROWSER_REGISTRY_PATH,
(entries) => {
const next = removeEntry(entries, containerName);
if (next.length === entries.length) {
return null;
}
return next;
},
);
}
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|