Implement AXIS parity UI and execution baseline
This commit is contained in:
76
web/workspace/opfs-runtime.mjs
Normal file
76
web/workspace/opfs-runtime.mjs
Normal file
@@ -0,0 +1,76 @@
|
||||
import { fnv1a64 } from "../worker/replay.mjs";
|
||||
|
||||
export const OPFS_RUNTIME_SCHEMA = "opfs-runtime.v1";
|
||||
const FILE_NAME = "commit-record.json";
|
||||
const TEXT_ENCODER = new TextEncoder();
|
||||
const PROBE_TIMEOUT_MS = 1500;
|
||||
|
||||
function capability(storage) {
|
||||
return storage && typeof storage.getDirectory === "function";
|
||||
}
|
||||
|
||||
export function opfsCapability(scope = globalThis) {
|
||||
const storage = scope?.navigator?.storage;
|
||||
if (!capability(storage)) return { status: "blocked", code: "opfs_unavailable", reason: "storage_get_directory_missing" };
|
||||
return { status: "verified", code: "opfs_available", mode: "dedicated_worker_sync_access_handle" };
|
||||
}
|
||||
|
||||
function validProjectId(projectId) {
|
||||
return typeof projectId === "string" && /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(projectId);
|
||||
}
|
||||
|
||||
function committedRecord(projectId) {
|
||||
const content = "s6-05-opfs-round-trip";
|
||||
return { schema: OPFS_RUNTIME_SCHEMA, project_id: projectId, phase: "commit", commit_seq: 1, content, content_hash: fnv1a64(content) };
|
||||
}
|
||||
|
||||
function validateCommittedRecord(record, projectId) {
|
||||
return record?.schema === OPFS_RUNTIME_SCHEMA && record.project_id === projectId && record.phase === "commit" && record.commit_seq === 1 && record.content_hash === fnv1a64(record.content);
|
||||
}
|
||||
|
||||
async function rootHandle(storage) {
|
||||
const timeout = new Promise((resolve) => setTimeout(() => resolve({ timeout: true }), PROBE_TIMEOUT_MS));
|
||||
const rootResult = await Promise.race([storage.getDirectory().then((root) => ({ root })), timeout]);
|
||||
return rootResult.timeout ? { blocked: { code: "opfs_probe_timeout", stage: "root_start" } } : { root: rootResult.root };
|
||||
}
|
||||
|
||||
export async function runOpfsProbe({ storage, projectId = "s6-05-opfs", mode = "round_trip", cleanup = true } = {}) {
|
||||
const probe = opfsCapability({ navigator: { storage } });
|
||||
if (probe.status !== "verified") return { schema: OPFS_RUNTIME_SCHEMA, ...probe, project_id: projectId };
|
||||
if (!validProjectId(projectId)) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_project_id_invalid" };
|
||||
if (!["round_trip", "write", "read"].includes(mode)) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_mode_invalid", project_id: projectId };
|
||||
let access = null;
|
||||
try {
|
||||
const rootResult = await rootHandle(storage);
|
||||
if (rootResult.blocked) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", ...rootResult.blocked, project_id: projectId };
|
||||
const root = rootResult.root;
|
||||
const directory = await root.getDirectoryHandle("linuxcnc-web-wasm", { create: true });
|
||||
const project = await directory.getDirectoryHandle(projectId, { create: true });
|
||||
const file = await project.getFileHandle(FILE_NAME, { create: mode !== "read" });
|
||||
if (mode === "read") {
|
||||
const persisted = JSON.parse(await (await file.getFile()).text());
|
||||
const valid = validateCommittedRecord(persisted, projectId);
|
||||
if (cleanup) { await project.removeEntry(FILE_NAME); await directory.removeEntry(projectId); }
|
||||
return valid ? { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode, project_id: projectId, commit_seq: persisted.commit_seq, content_hash: persisted.content_hash, cleanup } : { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_persisted_record_invalid", project_id: projectId };
|
||||
}
|
||||
if (typeof file.createSyncAccessHandle !== "function") return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_sync_access_handle_missing", project_id: projectId };
|
||||
access = await file.createSyncAccessHandle();
|
||||
const record = committedRecord(projectId);
|
||||
const encoded = TEXT_ENCODER.encode(JSON.stringify(record));
|
||||
access.truncate(0);
|
||||
const written = access.write(encoded, { at: 0 });
|
||||
access.flush();
|
||||
const size = access.getSize();
|
||||
access.close();
|
||||
access = null;
|
||||
if (mode === "write") return { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode, project_id: projectId, bytes: encoded.byteLength, commit_seq: record.commit_seq, content_hash: record.content_hash, cleanup: false };
|
||||
const persisted = JSON.parse(await (await file.getFile()).text());
|
||||
const valid = written === encoded.byteLength && size === encoded.byteLength && JSON.stringify(persisted) === JSON.stringify(record);
|
||||
if (cleanup) { await project.removeEntry(FILE_NAME); await directory.removeEntry(projectId); }
|
||||
if (!valid) return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_round_trip_mismatch", project_id: projectId };
|
||||
return { schema: OPFS_RUNTIME_SCHEMA, status: "verified", mode: "dedicated_worker_sync_access_handle", project_id: projectId, bytes: encoded.byteLength, commit_seq: record.commit_seq, content_hash: record.content_hash, cleanup: true };
|
||||
} catch (error) {
|
||||
try { access?.close(); } catch { /* best effort close after a browser fault */ }
|
||||
return { schema: OPFS_RUNTIME_SCHEMA, status: "blocked", code: "opfs_probe_failed", project_id: projectId, reason: error?.name ?? "unknown" };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user