Merge branch 'work/00001KY8KRJKK-virtual-config-tree' into develop

This commit is contained in:
2026-08-14 03:20:59 +09:00
45 changed files with 3875 additions and 36 deletions
+4 -3
View File
@@ -15,9 +15,10 @@
"@sveltejs/adapter-static": "npm:@sveltejs/adapter-static@3.0.9",
"@sveltejs/kit": "npm:@sveltejs/kit@2.49.4",
"@sveltejs/vite-plugin-svelte": "npm:@sveltejs/vite-plugin-svelte@6.2.1",
"@codemirror/state": "npm:@codemirror/state@6.5.2",
"@codemirror/view": "npm:@codemirror/view@6.38.8",
"decodal-codemirror": "npm:decodal-codemirror@0.1.2",
"@codemirror/autocomplete": "npm:@codemirror/autocomplete@6.20.0",
"@codemirror/state": "npm:@codemirror/state@6.7.1",
"@codemirror/view": "npm:@codemirror/view@6.43.8",
"decodal-codemirror": "npm:decodal-codemirror@0.1.6",
"clsx": "npm:clsx@2.1.1",
"cookie": "npm:cookie@0.6.0",
"devalue": "npm:devalue@5.6.4",
+37 -12
View File
@@ -1,15 +1,18 @@
{
"version": "5",
"specifiers": {
"npm:@codemirror/state@6.5.2": "6.5.2",
"npm:@codemirror/view@6.38.8": "6.38.8",
"jsr:@std/assert@*": "1.0.19",
"jsr:@std/internal@^1.0.12": "1.0.14",
"npm:@codemirror/autocomplete@6.20.0": "6.20.0",
"npm:@codemirror/state@6.7.1": "6.7.1",
"npm:@codemirror/view@6.43.8": "6.43.8",
"npm:@fontsource/ibm-plex-mono@5.3.0": "5.3.0",
"npm:@sveltejs/adapter-static@3.0.9": "3.0.9_@sveltejs+kit@2.49.4__@sveltejs+vite-plugin-svelte@6.2.1___svelte@5.45.6___vite@7.2.7__svelte@5.45.6__typescript@5.9.3__vite@7.2.7_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.45.6__vite@7.2.7_svelte@5.45.6_typescript@5.9.3_vite@7.2.7",
"npm:@sveltejs/kit@2.49.4": "2.49.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.45.6__vite@7.2.7_svelte@5.45.6_typescript@5.9.3_vite@7.2.7",
"npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.45.6_vite@7.2.7",
"npm:clsx@2.1.1": "2.1.1",
"npm:cookie@0.6.0": "0.6.0",
"npm:decodal-codemirror@0.1.2": "0.1.2",
"npm:decodal-codemirror@0.1.6": "0.1.6_@codemirror+view@6.43.8",
"npm:devalue@5.6.4": "5.6.4",
"npm:gen-interface-jp@0.8.0": "0.8.0",
"npm:set-cookie-parser@2.7.2": "2.7.2",
@@ -20,7 +23,27 @@
"npm:typescript@5.9.3": "5.9.3",
"npm:vite@7.2.7": "7.2.7"
},
"jsr": {
"@std/assert@1.0.19": {
"integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/internal@1.0.14": {
"integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7"
}
},
"npm": {
"@codemirror/autocomplete@6.20.0": {
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
"dependencies": [
"@codemirror/language",
"@codemirror/state",
"@codemirror/view",
"@lezer/common"
]
},
"@codemirror/language@6.12.4": {
"integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==",
"dependencies": [
@@ -32,14 +55,14 @@
"style-mod"
]
},
"@codemirror/state@6.5.2": {
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"@codemirror/state@6.7.1": {
"integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==",
"dependencies": [
"@marijn/find-cluster-break"
]
},
"@codemirror/view@6.38.8": {
"integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
"@codemirror/view@6.43.8": {
"integrity": "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw==",
"dependencies": [
"@codemirror/state",
"crelt",
@@ -531,10 +554,11 @@
"ms"
]
},
"decodal-codemirror@0.1.2": {
"integrity": "sha512-VR+cFsBLPb9kZU3gJhElZck+IUVhOC1HoWgA8bxP1kxwn+eCZaeBHErHESlRZbsWvN2J5T8VKcDCtxLDYe9QyA==",
"decodal-codemirror@0.1.6_@codemirror+view@6.43.8": {
"integrity": "sha512-XTS5vAY+vTb/yEg5n+1yORtBPuhozG4KO0YGbYEFR8oZX0KghZuHyFEsq/CJMXF223YMC/FQt3SC19NVYGWKMw==",
"dependencies": [
"@codemirror/language",
"@codemirror/view",
"@lezer/highlight",
"@lezer/lr"
]
@@ -975,14 +999,15 @@
},
"workspace": {
"dependencies": [
"npm:@codemirror/state@6.5.2",
"npm:@codemirror/view@6.38.8",
"npm:@codemirror/autocomplete@6.20.0",
"npm:@codemirror/state@6.7.1",
"npm:@codemirror/view@6.43.8",
"npm:@sveltejs/adapter-static@3.0.9",
"npm:@sveltejs/kit@2.49.4",
"npm:@sveltejs/vite-plugin-svelte@6.2.1",
"npm:clsx@2.1.1",
"npm:cookie@0.6.0",
"npm:decodal-codemirror@0.1.2",
"npm:decodal-codemirror@0.1.6",
"npm:devalue@5.6.4",
"npm:set-cookie-parser@2.7.2",
"npm:shiki@3.13.0",
@@ -0,0 +1,350 @@
<script lang="ts">
import { onMount } from "svelte";
import DecodalSourceEditor from "$lib/workspace/settings/DecodalSourceEditor.svelte";
import {
commitConfigTree,
fetchConfigTree,
previewConfigTree,
} from "./api.ts";
import { ConfigSourceToolchain } from "./toolchain.ts";
import type {
ConfigDiagnostic,
ConfigTreeChange,
WorkspaceConfigTreeResponse,
} from "./types.ts";
let { workspaceId }: { workspaceId: string } = $props();
let treeState = $state<WorkspaceConfigTreeResponse | null>(null);
let selectedPath = $state("");
let source = $state("");
let newPath = $state("workspace.dcdl");
let diagnostics = $state<ConfigDiagnostic[]>([]);
let status = $state("Loading source tree…");
let busy = $state(false);
let draftChanges = $state<ConfigTreeChange[]>([]);
let baseRevision = $state(0);
let baseDigest = $state("");
let renamePath = $state("");
let baseSnapshot = $state<WorkspaceConfigTreeResponse["snapshot"] | null>(null);
let preflightDigest = $state("");
let conflict = $state(false);
let candidateContract = $state<WorkspaceConfigTreeResponse["contract"] | null>(null);
let toolchain: ConfigSourceToolchain | null = null;
const paths = $derived(
treeState ? Object.keys(treeState.snapshot.entries).toSorted() : [],
);
const selected = $derived(
treeState && selectedPath ? treeState.snapshot.entries[selectedPath] : undefined,
);
const dirty = $derived(draftChanges.length > 0 || (selected ? source !== selected.content : source.length > 0));
const commitReady = $derived(dirty && preflightDigest === treeState?.snapshot.digest);
onMount(() => {
toolchain = new ConfigSourceToolchain();
void reload();
return () => toolchain?.close();
});
async function reload() {
try {
treeState = await fetchConfigTree(workspaceId);
if (!selectedPath || !treeState.snapshot.entries[selectedPath]) {
selectedPath = Object.keys(treeState.snapshot.entries).toSorted()[0] ?? "";
}
source = selectedPath ? treeState.snapshot.entries[selectedPath].content : "";
baseSnapshot = structuredClone(treeState.snapshot);
baseRevision = treeState.snapshot.revision;
baseDigest = treeState.snapshot.digest;
await toolchain?.setSnapshot(treeState.snapshot);
draftChanges = [];
renamePath = selectedPath;
diagnostics = [];
conflict = false;
candidateContract = null;
status = treeState.snapshot.revision === 0
? "No committed sources yet. Create workspace.dcdl to begin."
: `Revision ${treeState.snapshot.revision} · ${treeState.snapshot.digest.slice(0, 20)}…`;
} catch (error) {
status = String(error);
}
}
async function stageCurrent() {
const change = currentChange();
if (!change || !toolchain) return;
const candidate = await toolchain.applyChanges([change]);
if (treeState) treeState = { ...treeState, snapshot: candidate };
if (baseSnapshot) draftChanges = await toolchain.changesBetween(baseSnapshot, candidate);
preflightDigest = "";
candidateContract = null;
conflict = false;
}
async function select(path: string) {
await stageCurrent();
selectedPath = path;
source = treeState?.snapshot.entries[path]?.content ?? "";
renamePath = path;
diagnostics = [];
}
function currentChange(): ConfigTreeChange | null {
if (!treeState || !selectedPath) return null;
const entry = treeState.snapshot.entries[selectedPath];
if (!entry) {
return {
kind: "create",
path: selectedPath,
content_type: "decodal",
content: source,
};
}
if (source === entry.content) return null;
return {
kind: "update",
path: selectedPath,
expected_digest: entry.content_digest,
content: source,
};
}
function entrypoints(): string[] {
if (!treeState) return [];
const known = new Set(Object.keys(treeState.snapshot.entries));
const configured = treeState.contract.entrypoints.filter((path) => known.has(path));
if (configured.length > 0) return configured;
if (treeState.snapshot.entries["workspace.dcdl"] || selectedPath === "workspace.dcdl") {
return ["workspace.dcdl"];
}
return selectedPath ? [selectedPath] : [];
}
async function analyze() {
if (!toolchain || !treeState || !selectedPath) return;
diagnostics = await toolchain.analyze(selectedPath, source);
status = diagnostics.length === 0 ? "No diagnostics." : `${diagnostics.length} diagnostic(s).`;
}
async function format() {
if (!toolchain) return;
try {
source = await toolchain.format(source);
await analyze();
} catch (error) {
status = String(error);
}
}
async function preview() {
if (!treeState) return;
await stageCurrent();
if (draftChanges.length === 0) {
status = "No draft changes to preview.";
return;
}
busy = true;
try {
const candidate = await previewConfigTree(workspaceId, {
changes: draftChanges,
entrypoints: entrypoints(),
});
await toolchain?.evaluate(candidate.contract);
diagnostics = [];
candidateContract = candidate.contract;
preflightDigest = candidate.snapshot.digest;
status = `Preview valid · projection ${candidate.evaluation.projection_digest.slice(0, 20)}…`;
} catch (error) {
const message = String(error);
conflict = message.includes("conflict") || message.includes("base revision/digest mismatch");
status = conflict ? `${message} Reload the authoritative tree before editing again.` : message;
} finally {
busy = false;
}
}
async function commit() {
if (!treeState) return;
await stageCurrent();
if (draftChanges.length === 0) {
status = "No draft changes to commit.";
return;
}
if (preflightDigest !== treeState.snapshot.digest || !candidateContract) {
status = "Preview the complete candidate successfully before Commit.";
return;
}
busy = true;
try {
treeState = await commitConfigTree(workspaceId, {
base_revision: baseRevision,
base_digest: baseDigest,
changes: draftChanges,
entrypoints: candidateContract.entrypoints,
toolchain_fingerprint: candidateContract.fingerprint,
});
draftChanges = [];
preflightDigest = "";
candidateContract = null;
conflict = false;
baseSnapshot = structuredClone(treeState.snapshot);
baseRevision = treeState.snapshot.revision;
baseDigest = treeState.snapshot.digest;
await toolchain?.setSnapshot(treeState.snapshot);
source = treeState.snapshot.entries[selectedPath]?.content ?? "";
diagnostics = [];
status = `Committed revision ${treeState.snapshot.revision}.`;
} catch (error) {
const message = String(error);
conflict = message.includes("conflict") || message.includes("base revision/digest mismatch");
status = conflict ? `${message} Reload the authoritative tree before editing again.` : message;
} finally {
busy = false;
}
}
async function discardAndReload() {
draftChanges = [];
source = "";
await reload();
}
async function reloadAndReapply() {
if (!toolchain || !treeState) return;
const localChanges = [...draftChanges];
const remote = await fetchConfigTree(workspaceId);
baseSnapshot = structuredClone(remote.snapshot);
baseRevision = remote.snapshot.revision;
baseDigest = remote.snapshot.digest;
await toolchain.setSnapshot(remote.snapshot);
try {
const candidate = await toolchain.applyChanges(localChanges);
draftChanges = localChanges;
treeState = { ...remote, snapshot: candidate };
selectedPath = candidate.entries[selectedPath] ? selectedPath : Object.keys(candidate.entries).toSorted()[0] ?? "";
source = selectedPath ? candidate.entries[selectedPath].content : "";
conflict = false;
preflightDigest = "";
candidateContract = null;
status = "Local changes reapplied to the latest revision. Preview again before Commit.";
} catch (error) {
conflict = true;
status = `Local changes conflict with the latest revision: ${String(error)}. Discard local changes or resolve against a fresh reload.`;
}
}
function createEntry() {
const path = newPath.trim();
if (!path || treeState?.snapshot.entries[path]) return;
selectedPath = path;
source = "{}\n";
diagnostics = [];
status = `Drafting new source ${path}. It is not persisted until Commit succeeds.`;
}
async function deleteEntry() {
if (!treeState || !selected || !toolchain) return;
const change: ConfigTreeChange = {
kind: "delete",
path: selectedPath,
expected_digest: selected.content_digest,
};
const candidate = await toolchain.applyChanges([change]);
treeState = { ...treeState, snapshot: candidate };
if (baseSnapshot) draftChanges = await toolchain.changesBetween(baseSnapshot, candidate);
preflightDigest = "";
candidateContract = null;
conflict = false;
selectedPath = Object.keys(candidate.entries).toSorted()[0] ?? "";
source = selectedPath ? candidate.entries[selectedPath].content : "";
renamePath = selectedPath;
status = "Delete staged. Preview and Commit to persist the candidate tree.";
}
async function renameEntry() {
if (!treeState || !selected || !toolchain) return;
const to = renamePath.trim();
if (!to || to === selectedPath) return;
await stageCurrent();
const change: ConfigTreeChange = {
kind: "rename",
from: selectedPath,
to,
expected_digest: selected.content_digest,
};
const candidate = await toolchain.applyChanges([change]);
treeState = { ...treeState, snapshot: candidate };
if (baseSnapshot) draftChanges = await toolchain.changesBetween(baseSnapshot, candidate);
preflightDigest = "";
candidateContract = null;
conflict = false;
selectedPath = to;
source = candidate.entries[to]?.content ?? "";
status = `Rename to ${to} staged. Preview and Commit to persist.`;
}
</script>
<section class="config-source-shell" aria-label="Workspace configuration source tree">
<aside class="config-source-tree">
<div class="config-source-tree__header">
<strong>Source tree</strong>
<span>{paths.length}</span>
</div>
<nav aria-label="Virtual configuration paths">
{#each paths as path}
<button
type="button"
class:active={path === selectedPath}
onclick={() => select(path)}
>{path}</button>
{/each}
</nav>
<form class="config-source-create" onsubmit={(event) => { event.preventDefault(); createEntry(); }}>
<label for="new-config-path">New path</label>
<input id="new-config-path" bind:value={newPath} placeholder="workspace.dcdl" />
<button type="submit">Create draft</button>
</form>
</aside>
<div class="config-source-workbench">
<header class="config-source-workbench__header">
<div>
<span>Virtual path</span>
<strong>{selectedPath || "Select or create a source"}</strong>
</div>
<div class="config-source-actions">
<input aria-label="Rename path" bind:value={renamePath} disabled={!selected || busy} />
<button type="button" onclick={renameEntry} disabled={!selected || renamePath === selectedPath || busy}>Rename</button>
<button type="button" onclick={format} disabled={!selectedPath || busy}>Format</button>
<button type="button" onclick={analyze} disabled={!selectedPath || busy}>Analyze</button>
<button type="button" onclick={preview} disabled={!dirty || busy}>Preview</button>
<button class="primary" type="button" onclick={commit} disabled={!commitReady || busy}>Commit</button>
<button class="danger" type="button" onclick={deleteEntry} disabled={!selected || busy}>Delete</button>
</div>
</header>
<DecodalSourceEditor
value={source}
readonly={!selectedPath || busy}
onChange={(value) => source = value}
onComplete={(value, offset, explicit) => toolchain?.complete(selectedPath, value, offset, explicit) ?? Promise.resolve(null)}
/>
<p class="config-source-status" aria-live="polite">{status}</p>
{#if conflict}
<div class="config-source-conflict" role="alert">
<button type="button" onclick={discardAndReload}>Discard local candidate and reload</button>
<button type="button" onclick={reloadAndReapply}>Reload and reapply local candidate</button>
</div>
{/if}
{#if diagnostics.length > 0}
<ol class="config-source-diagnostics">
{#each diagnostics as diagnostic}
<li>
<strong>{diagnostic.kind}</strong>
<span>{diagnostic.message}</span>
<small>bytes {diagnostic.span.start_byte}{diagnostic.span.end_byte}</small>
</li>
{/each}
</ol>
{/if}
</div>
</section>
@@ -0,0 +1,84 @@
import type {
ConfigCommitRequest,
ConfigEntry,
ConfigPreviewRequest,
ConfigTreeSnapshot,
EvaluatedConfigCandidate,
WorkspaceConfigTreeResponse,
} from "./types.ts";
function sourceTreeUrl(workspaceId: string): string {
return `/api/w/${encodeURIComponent(workspaceId)}/config/source-tree`;
}
async function readJson<T>(response: Response): Promise<T> {
if (!response.ok) {
const body = await response.text();
throw new Error(body || `${response.status} ${response.statusText}`);
}
return await response.json() as T;
}
export async function fetchConfigTree(
workspaceId: string,
fetcher: typeof fetch = fetch,
): Promise<WorkspaceConfigTreeResponse> {
return await readJson(
await fetcher(sourceTreeUrl(workspaceId), {
headers: { accept: "application/json" },
}),
);
}
export async function fetchConfigEntry(
workspaceId: string,
path: string,
fetcher: typeof fetch = fetch,
): Promise<ConfigEntry> {
return await readJson(
await fetcher(
`${sourceTreeUrl(workspaceId)}/entries/${encodeURIComponent(path)}`,
{ headers: { accept: "application/json" } },
),
);
}
export async function fetchConfigRevision(
workspaceId: string,
revision: number,
fetcher: typeof fetch = fetch,
): Promise<ConfigTreeSnapshot> {
return await readJson(
await fetcher(`${sourceTreeUrl(workspaceId)}/revisions/${revision}`, {
headers: { accept: "application/json" },
}),
);
}
export async function previewConfigTree(
workspaceId: string,
request: ConfigPreviewRequest,
fetcher: typeof fetch = fetch,
): Promise<EvaluatedConfigCandidate> {
return await readJson(
await fetcher(`${sourceTreeUrl(workspaceId)}/preview`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(request),
}),
);
}
export async function commitConfigTree(
workspaceId: string,
request: ConfigCommitRequest,
fetcher: typeof fetch = fetch,
): Promise<WorkspaceConfigTreeResponse> {
return await readJson(
await fetcher(`${sourceTreeUrl(workspaceId)}/commit`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(request),
}),
);
}
@@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
export function analyze_snapshot(snapshot: any, entrypoint: string, source_override?: string | null): any;
export function apply_changes(changes: any): any;
export function changes_between(base: any, candidate: any): any;
export function complete_current(entrypoint: string, source: string, utf16_offset: number, explicit: boolean): any;
export function evaluate_current(contract: any): any;
export function evaluate_snapshot(snapshot: any, contract: any): any;
export function formatSource(source: string): string;
export function format_source(source: string): string;
export function set_snapshot(snapshot: any): void;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly analyze_snapshot: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
readonly apply_changes: (a: any) => [number, number, number];
readonly changes_between: (a: any, b: any) => [number, number, number];
readonly complete_current: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
readonly evaluate_current: (a: any) => [number, number, number];
readonly evaluate_snapshot: (a: any, b: any) => [number, number, number];
readonly format_source: (a: number, b: number) => [number, number, number, number];
readonly set_snapshot: (a: any) => [number, number];
readonly formatSource: (a: number, b: number) => [number, number];
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_exn_store: (a: number) => void;
readonly __externref_table_alloc: () => number;
readonly __wbindgen_externrefs: WebAssembly.Table;
readonly __externref_table_dealloc: (a: number) => void;
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_start: () => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
*
* @returns {InitOutput}
*/
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
@@ -0,0 +1,658 @@
/* @ts-self-types="./config_source_wasm.d.ts" */
/**
* @param {any} snapshot
* @param {string} entrypoint
* @param {string | null} [source_override]
* @returns {any}
*/
export function analyze_snapshot(snapshot, entrypoint, source_override) {
const ptr0 = passStringToWasm0(entrypoint, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
var ptr1 = isLikeNone(source_override) ? 0 : passStringToWasm0(source_override, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
const ret = wasm.analyze_snapshot(snapshot, ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {any} changes
* @returns {any}
*/
export function apply_changes(changes) {
const ret = wasm.apply_changes(changes);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {any} base
* @param {any} candidate
* @returns {any}
*/
export function changes_between(base, candidate) {
const ret = wasm.changes_between(base, candidate);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {string} entrypoint
* @param {string} source
* @param {number} utf16_offset
* @param {boolean} explicit
* @returns {any}
*/
export function complete_current(entrypoint, source, utf16_offset, explicit) {
const ptr0 = passStringToWasm0(entrypoint, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.complete_current(ptr0, len0, ptr1, len1, utf16_offset, explicit);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {any} contract
* @returns {any}
*/
export function evaluate_current(contract) {
const ret = wasm.evaluate_current(contract);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {any} snapshot
* @param {any} contract
* @returns {any}
*/
export function evaluate_snapshot(snapshot, contract) {
const ret = wasm.evaluate_snapshot(snapshot, contract);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {string} source
* @returns {string}
*/
export function formatSource(source) {
let deferred2_0;
let deferred2_1;
try {
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.formatSource(ptr0, len0);
deferred2_0 = ret[0];
deferred2_1 = ret[1];
return getStringFromWasm0(ret[0], ret[1]);
} finally {
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
/**
* @param {string} source
* @returns {string}
*/
export function format_source(source) {
let deferred3_0;
let deferred3_1;
try {
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.format_source(ptr0, len0);
var ptr2 = ret[0];
var len2 = ret[1];
if (ret[3]) {
ptr2 = 0; len2 = 0;
throw takeFromExternrefTable0(ret[2]);
}
deferred3_0 = ptr2;
deferred3_1 = len2;
return getStringFromWasm0(ptr2, len2);
} finally {
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
}
}
/**
* @param {any} snapshot
*/
export function set_snapshot(snapshot) {
const ret = wasm.set_snapshot(snapshot);
if (ret[1]) {
throw takeFromExternrefTable0(ret[0]);
}
}
function __wbg_get_imports() {
const import0 = {
__proto__: null,
__wbg_Error_2e59b1b37a9a34c3: function(arg0, arg1) {
const ret = Error(getStringFromWasm0(arg0, arg1));
return ret;
},
__wbg_Number_e6ffdb596c888833: function(arg0) {
const ret = Number(arg0);
return ret;
},
__wbg_String_8564e559799eccda: function(arg0, arg1) {
const ret = String(arg1);
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
},
__wbg___wbindgen_bigint_get_as_i64_2c5082002e4826e2: function(arg0, arg1) {
const v = arg1;
const ret = typeof(v) === 'bigint' ? v : undefined;
getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
},
__wbg___wbindgen_boolean_get_a86c216575a75c30: function(arg0) {
const v = arg0;
const ret = typeof(v) === 'boolean' ? v : undefined;
return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
},
__wbg___wbindgen_debug_string_dd5d2d07ce9e6c57: function(arg0, arg1) {
const ret = debugString(arg1);
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
},
__wbg___wbindgen_in_4bd7a57e54337366: function(arg0, arg1) {
const ret = arg0 in arg1;
return ret;
},
__wbg___wbindgen_is_bigint_6c98f7e945dacdde: function(arg0) {
const ret = typeof(arg0) === 'bigint';
return ret;
},
__wbg___wbindgen_is_function_49868bde5eb1e745: function(arg0) {
const ret = typeof(arg0) === 'function';
return ret;
},
__wbg___wbindgen_is_object_40c5a80572e8f9d3: function(arg0) {
const val = arg0;
const ret = typeof(val) === 'object' && val !== null;
return ret;
},
__wbg___wbindgen_is_string_b29b5c5a8065ba1a: function(arg0) {
const ret = typeof(arg0) === 'string';
return ret;
},
__wbg___wbindgen_is_undefined_c0cca72b82b86f4d: function(arg0) {
const ret = arg0 === undefined;
return ret;
},
__wbg___wbindgen_jsval_eq_7d430e744a913d26: function(arg0, arg1) {
const ret = arg0 === arg1;
return ret;
},
__wbg___wbindgen_jsval_loose_eq_3a72ae764d46d944: function(arg0, arg1) {
const ret = arg0 == arg1;
return ret;
},
__wbg___wbindgen_number_get_7579aab02a8a620c: function(arg0, arg1) {
const obj = arg1;
const ret = typeof(obj) === 'number' ? obj : undefined;
getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
},
__wbg___wbindgen_string_get_914df97fcfa788f2: function(arg0, arg1) {
const obj = arg1;
const ret = typeof(obj) === 'string' ? obj : undefined;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
},
__wbg___wbindgen_throw_81fc77679af83bc6: function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
},
__wbg_call_7f2987183bb62793: function() { return handleError(function (arg0, arg1) {
const ret = arg0.call(arg1);
return ret;
}, arguments); },
__wbg_done_547d467e97529006: function(arg0) {
const ret = arg0.done;
return ret;
},
__wbg_entries_616b1a459b85be0b: function(arg0) {
const ret = Object.entries(arg0);
return ret;
},
__wbg_get_4848e350b40afc16: function(arg0, arg1) {
const ret = arg0[arg1 >>> 0];
return ret;
},
__wbg_get_ed0642c4b9d31ddf: function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(arg0, arg1);
return ret;
}, arguments); },
__wbg_get_unchecked_7d7babe32e9e6a54: function(arg0, arg1) {
const ret = arg0[arg1 >>> 0];
return ret;
},
__wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) {
const ret = arg0[arg1];
return ret;
},
__wbg_instanceof_ArrayBuffer_ff7c1337a5e3b33a: function(arg0) {
let result;
try {
result = arg0 instanceof ArrayBuffer;
} catch (_) {
result = false;
}
const ret = result;
return ret;
},
__wbg_instanceof_Map_a10a2795ef4bfe97: function(arg0) {
let result;
try {
result = arg0 instanceof Map;
} catch (_) {
result = false;
}
const ret = result;
return ret;
},
__wbg_instanceof_Uint8Array_4b8da683deb25d72: function(arg0) {
let result;
try {
result = arg0 instanceof Uint8Array;
} catch (_) {
result = false;
}
const ret = result;
return ret;
},
__wbg_isArray_db61795ad004c139: function(arg0) {
const ret = Array.isArray(arg0);
return ret;
},
__wbg_isSafeInteger_ea83862ba994770c: function(arg0) {
const ret = Number.isSafeInteger(arg0);
return ret;
},
__wbg_iterator_de403ef31815a3e6: function() {
const ret = Symbol.iterator;
return ret;
},
__wbg_length_0c32cb8543c8e4c8: function(arg0) {
const ret = arg0.length;
return ret;
},
__wbg_length_6e821edde497a532: function(arg0) {
const ret = arg0.length;
return ret;
},
__wbg_new_4f9fafbb3909af72: function() {
const ret = new Object();
return ret;
},
__wbg_new_99cabae501c0a8a0: function() {
const ret = new Map();
return ret;
},
__wbg_new_a560378ea1240b14: function(arg0) {
const ret = new Uint8Array(arg0);
return ret;
},
__wbg_new_f3c9df4f38f3f798: function() {
const ret = new Array();
return ret;
},
__wbg_next_01132ed6134b8ef5: function(arg0) {
const ret = arg0.next;
return ret;
},
__wbg_next_b3713ec761a9dbfd: function() { return handleError(function (arg0) {
const ret = arg0.next();
return ret;
}, arguments); },
__wbg_prototypesetcall_3e05eb9545565046: function(arg0, arg1, arg2) {
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
},
__wbg_set_08463b1df38a7e29: function(arg0, arg1, arg2) {
const ret = arg0.set(arg1, arg2);
return ret;
},
__wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
arg0[arg1] = arg2;
},
__wbg_set_6c60b2e8ad0e9383: function(arg0, arg1, arg2) {
arg0[arg1 >>> 0] = arg2;
},
__wbg_value_7f6052747ccf940f: function(arg0) {
const ret = arg0.value;
return ret;
},
__wbindgen_cast_0000000000000001: function(arg0) {
// Cast intrinsic for `F64 -> Externref`.
const ret = arg0;
return ret;
},
__wbindgen_cast_0000000000000002: function(arg0) {
// Cast intrinsic for `I64 -> Externref`.
const ret = arg0;
return ret;
},
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
// Cast intrinsic for `Ref(String) -> Externref`.
const ret = getStringFromWasm0(arg0, arg1);
return ret;
},
__wbindgen_cast_0000000000000004: function(arg0) {
// Cast intrinsic for `U64 -> Externref`.
const ret = BigInt.asUintN(64, arg0);
return ret;
},
__wbindgen_init_externref_table: function() {
const table = wasm.__wbindgen_externrefs;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
},
};
return {
__proto__: null,
"./config_source_wasm_bg.js": import0,
};
}
function addToExternrefTable0(obj) {
const idx = wasm.__externref_table_alloc();
wasm.__wbindgen_externrefs.set(idx, obj);
return idx;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches && builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
}
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return decodeText(ptr, len);
}
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
const idx = addToExternrefTable0(e);
wasm.__wbindgen_exn_store(idx);
}
}
function isLikeNone(x) {
return x === undefined || x === null;
}
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = cachedTextEncoder.encodeInto(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_externrefs.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
const MAX_SAFARI_DECODE_BYTES = 2146435072;
let numBytesDecoded = 0;
function decodeText(ptr, len) {
numBytesDecoded += len;
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
numBytesDecoded = len;
}
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
const cachedTextEncoder = new TextEncoder();
if (!('encodeInto' in cachedTextEncoder)) {
cachedTextEncoder.encodeInto = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
};
}
let WASM_VECTOR_LEN = 0;
let wasmModule, wasm;
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
wasmModule = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm;
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
const validResponse = module.ok && expectedResponseType(module.type);
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else { throw e; }
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
function expectedResponseType(type) {
switch (type) {
case 'basic': case 'cors': case 'default': return true;
}
return false;
}
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (module !== undefined) {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(module_or_path) {
if (wasm !== undefined) return wasm;
if (module_or_path !== undefined) {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({module_or_path} = module_or_path)
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
}
}
if (module_or_path === undefined) {
module_or_path = new URL('config_source_wasm_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
module_or_path = fetch(module_or_path);
}
const { instance, module } = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync, __wbg_init as default };
@@ -0,0 +1,20 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const analyze_snapshot: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
export const apply_changes: (a: any) => [number, number, number];
export const changes_between: (a: any, b: any) => [number, number, number];
export const complete_current: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
export const evaluate_current: (a: any) => [number, number, number];
export const evaluate_snapshot: (a: any, b: any) => [number, number, number];
export const format_source: (a: number, b: number) => [number, number, number, number];
export const set_snapshot: (a: any) => [number, number];
export const formatSource: (a: number, b: number) => [number, number];
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_exn_store: (a: number) => void;
export const __externref_table_alloc: () => number;
export const __wbindgen_externrefs: WebAssembly.Table;
export const __externref_table_dealloc: (a: number) => void;
export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __wbindgen_start: () => void;
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigTreeChange } from "./ConfigTreeChange";
import type { VirtualPath } from "./VirtualPath";
export type ConfigCommitRequest = { base_revision: number, base_digest: string, changes: Array<ConfigTreeChange>, entrypoints: Array<VirtualPath>, toolchain_fingerprint: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ConfigContentType = "decodal" | "text";
@@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigDiagnosticLabel } from "./ConfigDiagnosticLabel";
import type { ConfigSpan } from "./ConfigSpan";
import type { VirtualPath } from "./VirtualPath";
export type ConfigDiagnostic = { path: VirtualPath, revision: number, tree_digest: string, kind: string, span: ConfigSpan, message: string, labels: Array<ConfigDiagnosticLabel>, notes: Array<string>, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigSpan } from "./ConfigSpan";
export type ConfigDiagnosticLabel = { span: ConfigSpan, message: string, };
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigContentType } from "./ConfigContentType";
import type { VirtualPath } from "./VirtualPath";
export type ConfigEntry = { path: VirtualPath, content_type: ConfigContentType, content: string, content_digest: string, };
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigTreeChange } from "./ConfigTreeChange";
import type { VirtualPath } from "./VirtualPath";
export type ConfigPreviewRequest = { changes: Array<ConfigTreeChange>, entrypoints: Array<VirtualPath>, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ConfigSpan = { start_byte: number, end_byte: number, };
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigContentType } from "./ConfigContentType";
import type { VirtualPath } from "./VirtualPath";
export type ConfigTreeChange = { "kind": "create", path: VirtualPath, content_type: ConfigContentType, content: string, } | { "kind": "update", path: VirtualPath, expected_digest: string, content: string, } | { "kind": "rename", from: VirtualPath, to: VirtualPath, expected_digest: string, } | { "kind": "delete", path: VirtualPath, expected_digest: string, };
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigEntry } from "./ConfigEntry";
import type { VirtualPath } from "./VirtualPath";
export type ConfigTreeSnapshot = { revision: number, digest: string, entries: { [key in VirtualPath]: ConfigEntry }, };
@@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigTreeSnapshot } from "./ConfigTreeSnapshot";
import type { EvaluationResult } from "./EvaluationResult";
import type { ToolchainContract } from "./ToolchainContract";
export type EvaluatedConfigCandidate = { base_revision: number, base_digest: string, snapshot: ConfigTreeSnapshot, contract: ToolchainContract, evaluation: EvaluationResult, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { VirtualPath } from "./VirtualPath";
export type EvaluatedProjection = { entrypoint: VirtualPath, data_json: unknown, projection_digest: string, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EvaluatedProjection } from "./EvaluatedProjection";
export type EvaluationResult = { projections: Array<EvaluatedProjection>, projection_digest: string, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { VirtualPath } from "./VirtualPath";
export type ToolchainContract = { contract_version: number, decodal_version: string, schema_version: number, entrypoints: Array<VirtualPath>, import_policy_version: number, fingerprint: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type VirtualPath = string;
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConfigTreeSnapshot } from "./ConfigTreeSnapshot";
import type { ToolchainContract } from "./ToolchainContract";
export type WorkspaceConfigState = { snapshot: ConfigTreeSnapshot, contract: ToolchainContract, projection_digest: string, };
@@ -0,0 +1,62 @@
import type { ConfigDiagnostic, ConfigTreeChange, ConfigTreeSnapshot, ToolchainContract } from "./types.ts";
import type { ConfigSourceWorkerRequest, ConfigSourceWorkerResponse } from "./toolchain.worker.ts";
type Command =
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "set_snapshot" }>, "id">
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "apply_changes" }>, "id">
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "changes_between" }>, "id">
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "analyze" }>, "id">
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "evaluate" }>, "id">
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "complete" }>, "id">
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "format" }>, "id">;
export class ConfigSourceToolchain {
#worker: Worker;
#nextId = 1;
#pending = new Map<number, { resolve: (value: unknown) => void; reject: (reason: unknown) => void }>();
constructor(worker = new Worker(new URL("./toolchain.worker.ts", import.meta.url), { type: "module" })) {
this.#worker = worker;
worker.addEventListener("message", (event: MessageEvent<ConfigSourceWorkerResponse>) => {
const pending = this.#pending.get(event.data.id);
if (!pending) return;
this.#pending.delete(event.data.id);
if (event.data.ok) pending.resolve(event.data.result);
else pending.reject(event.data.error);
});
}
setSnapshot(snapshot: ConfigTreeSnapshot): Promise<void> {
return this.#request({ kind: "set_snapshot", snapshot });
}
applyChanges(changes: ConfigTreeChange[]): Promise<ConfigTreeSnapshot> {
return this.#request({ kind: "apply_changes", changes });
}
changesBetween(base: ConfigTreeSnapshot, candidate: ConfigTreeSnapshot): Promise<ConfigTreeChange[]> {
return this.#request({ kind: "changes_between", base, candidate });
}
analyze(path: string, source?: string): Promise<ConfigDiagnostic[]> {
return this.#request({ kind: "analyze", path, source });
}
evaluate(contract: ToolchainContract) {
return this.#request({ kind: "evaluate", contract });
}
complete(path: string, source: string, utf16Offset: number, explicit = false): Promise<import("@codemirror/autocomplete").CompletionResult | null> {
return this.#request({ kind: "complete", path, source, utf16Offset, explicit });
}
format(source: string): Promise<string> {
return this.#request({ kind: "format", source });
}
close(): void {
this.#worker.terminate();
for (const pending of this.#pending.values()) pending.reject(new Error("config source toolchain was closed"));
this.#pending.clear();
}
#request<T>(request: Command): Promise<T> {
const id = this.#nextId++;
return new Promise<T>((resolve, reject) => {
this.#pending.set(id, { resolve: (value) => resolve(value as T), reject });
this.#worker.postMessage({ ...request, id });
});
}
}
@@ -0,0 +1,64 @@
import init, {
analyze_snapshot,
apply_changes,
changes_between,
complete_current,
evaluate_current,
format_source,
set_snapshot,
} from "./generated/config_source_wasm.js";
import type { ConfigTreeChange } from "./types.ts";
export type ConfigSourceWorkerRequest =
| { id: number; kind: "set_snapshot"; snapshot: unknown }
| { id: number; kind: "apply_changes"; changes: ConfigTreeChange[] }
| { id: number; kind: "changes_between"; base: unknown; candidate: unknown }
| { id: number; kind: "analyze"; path: string; source?: string }
| { id: number; kind: "evaluate"; contract: unknown }
| { id: number; kind: "complete"; path: string; source: string; utf16Offset: number; explicit: boolean }
| { id: number; kind: "format"; source: string };
export type ConfigSourceWorkerResponse =
| { id: number; ok: true; result: unknown }
| { id: number; ok: false; error: unknown };
const ready = init();
let snapshot: unknown = null;
self.onmessage = async (event: MessageEvent<ConfigSourceWorkerRequest>): Promise<void> => {
const request = event.data;
try {
await ready;
let result: unknown;
switch (request.kind) {
case "set_snapshot":
snapshot = request.snapshot;
set_snapshot(request.snapshot);
result = null;
break;
case "apply_changes":
snapshot = apply_changes(request.changes);
result = snapshot;
break;
case "changes_between":
result = changes_between(request.base, request.candidate);
break;
case "analyze":
if (!snapshot) throw new Error("config source snapshot is not initialized");
result = analyze_snapshot(snapshot, request.path, request.source);
break;
case "evaluate":
result = evaluate_current(request.contract);
break;
case "complete":
result = complete_current(request.path, request.source, request.utf16Offset, request.explicit);
break;
case "format":
result = format_source(request.source);
break;
}
self.postMessage({ id: request.id, ok: true, result });
} catch (error) {
self.postMessage({ id: request.id, ok: false, error: error instanceof Error ? error.message : error });
}
};
@@ -0,0 +1,16 @@
// Core and HTTP DTOs are generated from Rust with ts-rs.
export type { ConfigContentType } from "./generated/types/ConfigContentType.ts";
export type { ConfigDiagnostic } from "./generated/types/ConfigDiagnostic.ts";
export type { ConfigDiagnosticLabel } from "./generated/types/ConfigDiagnosticLabel.ts";
export type { ConfigEntry } from "./generated/types/ConfigEntry.ts";
export type { ConfigSpan } from "./generated/types/ConfigSpan.ts";
export type { ConfigTreeChange } from "./generated/types/ConfigTreeChange.ts";
export type { ConfigTreeSnapshot } from "./generated/types/ConfigTreeSnapshot.ts";
export type { EvaluatedProjection } from "./generated/types/EvaluatedProjection.ts";
export type { EvaluationResult } from "./generated/types/EvaluationResult.ts";
export type { ToolchainContract } from "./generated/types/ToolchainContract.ts";
export type { VirtualPath } from "./generated/types/VirtualPath.ts";
export type { ConfigCommitRequest } from "./generated/types/ConfigCommitRequest.ts";
export type { ConfigPreviewRequest } from "./generated/types/ConfigPreviewRequest.ts";
export type { EvaluatedConfigCandidate } from "./generated/types/EvaluatedConfigCandidate.ts";
export type { WorkspaceConfigState as WorkspaceConfigTreeResponse } from "./generated/types/WorkspaceConfigState.ts";
@@ -1,5 +1,6 @@
<script lang="ts">
import { untrack } from 'svelte';
import { autocompletion, type CompletionContext, type CompletionResult } from '@codemirror/autocomplete';
import { EditorState } from '@codemirror/state';
import { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection } from '@codemirror/view';
import { decodal } from 'decodal-codemirror';
@@ -9,11 +10,13 @@
readonly = false,
ariaLabel = 'Decodal source',
onChange = (_value: string) => {},
onComplete = undefined,
}: {
value?: string;
readonly?: boolean;
ariaLabel?: string;
onChange?: (value: string) => void;
onComplete?: (source: string, utf16Offset: number, explicit: boolean) => Promise<CompletionResult | null>;
} = $props();
let host = $state<HTMLDivElement | null>(null);
@@ -39,6 +42,7 @@
const initialValue = untrack(() => value);
const initialReadonly = untrack(() => readonly);
const handleChange = untrack(() => onChange);
const handleComplete = untrack(() => onComplete);
const editor = new EditorView({
parent: host,
state: EditorState.create({
@@ -48,6 +52,10 @@
drawSelection(),
highlightActiveLine(),
decodal(),
...(handleComplete ? [autocompletion({ override: [async (context: CompletionContext) => {
const doc = context.state.doc.toString();
return await handleComplete(doc, context.pos, context.explicit);
}] })] : []),
keymap.of([]),
EditorState.readOnly.of(initialReadonly),
EditorView.editable.of(!initialReadonly),
@@ -7,6 +7,7 @@ export type Diagnostic = {
export type SettingsSectionId =
| "runtime-connections"
| "runtime-inventory"
| "configuration-sources"
| "profile-sources"
| "backend-config"
| "workspace-identity";
@@ -98,6 +99,18 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
"Console routes may still target a Runtime handle directly, but Runtime discovery belongs under Settings.",
],
},
{
id: "configuration-sources",
label: "Configuration Sources",
status: "editable",
summary:
"Edit the Server-owned virtual Decodal source tree through one native/WASM toolchain contract.",
bullets: [
"Virtual paths and imports resolve inside the committed Workspace tree, never from browser or Server host paths.",
"Browser analysis is advisory; Server evaluation is required before an atomic revision commit.",
"Profile, Skill, Prompt, and Plugin consumers remain on their existing authorities until their follow-up cutovers.",
],
},
{
id: "profile-sources",
label: "Profile Sources",
@@ -160,6 +173,8 @@ export function settingsSectionHref(id: SettingsSectionId): string {
return `${SETTINGS_ROUTE}/runtime-connections`;
case "runtime-inventory":
return `${SETTINGS_ROUTE}/runtimes`;
case "configuration-sources":
return `${SETTINGS_ROUTE}/configuration`;
case "profile-sources":
return `${SETTINGS_ROUTE}/profiles`;
case "workspace-identity":
@@ -515,6 +515,174 @@
font-weight: 800;
text-transform: uppercase;
}
.config-source-shell {
display: grid;
grid-template-columns: minmax(12rem, 18rem) minmax(0, 1fr);
min-height: 40rem;
overflow: hidden;
border: 1px solid var(--line);
border-radius: var(--radius-panel);
background: var(--bg-raised);
}
.config-source-tree {
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
border-right: 1px solid var(--line);
background: var(--bg-subtle);
}
.config-source-tree__header,
.config-source-workbench__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
padding: var(--space-3);
border-bottom: 1px solid var(--line);
}
.config-source-tree nav {
display: grid;
align-content: start;
overflow: auto;
padding: var(--space-2);
}
.config-source-tree nav button {
border: 0;
border-radius: 0.45rem;
background: transparent;
color: var(--text-muted);
padding: 0.45rem 0.55rem;
font: inherit;
font-family: var(--font-mono);
text-align: left;
cursor: pointer;
}
.config-source-tree nav button.active,
.config-source-tree nav button:hover {
background: var(--interactive-hover);
color: var(--text-strong);
}
.config-source-create {
display: grid;
gap: var(--space-2);
padding: var(--space-3);
border-top: 1px solid var(--line);
}
.config-source-create label,
.config-source-workbench__header span {
color: var(--text-muted);
font-size: 0.75rem;
}
.config-source-create input,
.config-source-actions input {
min-width: 0;
border: 1px solid var(--line);
border-radius: 0.45rem;
background: var(--bg-raised);
color: var(--text-strong);
padding: 0.45rem;
font: inherit;
font-family: var(--font-mono);
}
.config-source-create button,
.config-source-actions button {
border: 1px solid var(--line);
border-radius: 0.45rem;
background: var(--bg-subtle);
color: var(--text-strong);
padding: 0.4rem 0.6rem;
cursor: pointer;
}
.config-source-actions button.primary {
border-color: transparent;
background: var(--accent);
color: var(--bg);
}
.config-source-actions button.danger {
color: var(--danger);
}
.config-source-actions button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
.config-source-workbench {
display: grid;
grid-template-rows: auto minmax(20rem, 1fr) auto auto;
min-width: 0;
}
.config-source-workbench__header > div:first-child {
display: grid;
min-width: 0;
}
.config-source-workbench__header strong {
overflow: hidden;
font-family: var(--font-mono);
text-overflow: ellipsis;
white-space: nowrap;
}
.config-source-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.config-source-status {
margin: 0;
padding: var(--space-2) var(--space-3);
border-top: 1px solid var(--line);
color: var(--text-muted);
font-size: 0.8rem;
}
.config-source-conflict {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
padding: var(--space-3);
border-top: 1px solid var(--danger);
background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.config-source-conflict button {
border: 1px solid var(--danger);
border-radius: 0.45rem;
background: var(--bg-raised);
color: var(--danger);
padding: 0.4rem 0.6rem;
cursor: pointer;
}
.config-source-diagnostics {
display: grid;
gap: var(--space-2);
max-height: 12rem;
overflow: auto;
margin: 0;
padding: var(--space-3);
border-top: 1px solid var(--line);
}
.config-source-diagnostics li {
display: grid;
grid-template-columns: auto 1fr auto;
gap: var(--space-2);
color: var(--text-muted);
}
.config-source-diagnostics strong {
color: var(--danger);
}
@media (max-width: 48rem) {
.config-source-shell {
grid-template-columns: 1fr;
}
.config-source-tree {
grid-template-rows: auto auto auto;
border-right: 0;
border-bottom: 1px solid var(--line);
}
.config-source-tree nav {
max-height: 12rem;
}
.config-source-workbench__header {
align-items: stretch;
flex-direction: column;
}
}
.status-message.error {
color: var(--danger);
}
@@ -0,0 +1,27 @@
<script lang="ts">
import ConfigSourceEditor from "$lib/workspace/config-source/ConfigSourceEditor.svelte";
import type { PageProps } from "./$types";
let { data }: PageProps = $props();
let workspaceId = $derived(data.workspace?.workspace_id ?? "");
let workspaceName = $derived(data.workspace?.display_name ?? "Workspace");
</script>
<svelte:head>
<title>Configuration | {workspaceName}</title>
</svelte:head>
<section class="settings-page">
<header class="page-header">
<div>
<p class="eyebrow">Workspace settings</p>
<h2>Configuration</h2>
<p>
Edit the Server-owned virtual Decodal source tree. Drafts stay in this browser;
the Server persists only a fully evaluated candidate.
</p>
</div>
</header>
<ConfigSourceEditor {workspaceId} />
</section>
@@ -0,0 +1,64 @@
/// <reference lib="deno.ns" />
import { assert, assertEquals } from "jsr:@std/assert";
import {
commitConfigTree,
fetchConfigEntry,
fetchConfigRevision,
fetchConfigTree,
previewConfigTree,
} from "../../src/lib/workspace/config-source/api.ts";
function response(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "content-type": "application/json" },
});
}
Deno.test("config source API stays workspace-scoped and separates preview from commit", async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
const fetcher = ((input: string | URL | Request, init?: RequestInit) => {
calls.push({ url: String(input), init });
return Promise.resolve(response({ ok: true }));
}) as typeof fetch;
await fetchConfigTree("w/one", fetcher);
await fetchConfigRevision("w/one", 7, fetcher);
await fetchConfigEntry("w/one", "profiles/main.dcdl", fetcher);
await previewConfigTree("w/one", { changes: [], entrypoints: [] }, fetcher);
await commitConfigTree("w/one", {
base_revision: 4,
base_digest: "sha256:base",
changes: [],
entrypoints: [],
toolchain_fingerprint: "sha256:toolchain",
}, fetcher);
assertEquals(calls.map((call) => call.url), [
"/api/w/w%2Fone/config/source-tree",
"/api/w/w%2Fone/config/source-tree/revisions/7",
"/api/w/w%2Fone/config/source-tree/entries/profiles%2Fmain.dcdl",
"/api/w/w%2Fone/config/source-tree/preview",
"/api/w/w%2Fone/config/source-tree/commit",
]);
assertEquals(calls[3].init?.method, "POST");
assertEquals(calls[4].init?.method, "POST");
assert(
String(calls[4].init?.body).includes('"base_digest":"sha256:base"'),
);
});
Deno.test("config source API surfaces failed evaluation instead of treating it as a draft write", async () => {
const fetcher = (() =>
Promise.resolve(
new Response("structured diagnostics", { status: 422 }),
)) as typeof fetch;
let message = "";
try {
await previewConfigTree("w", { changes: [], entrypoints: [] }, fetcher);
} catch (error) {
message = String(error);
}
assert(message.includes("structured diagnostics"));
});
@@ -0,0 +1,67 @@
/// <reference lib="deno.ns" />
import { assertEquals } from "jsr:@std/assert";
// The generated wasm-bindgen loader is JavaScript with an adjacent declaration file.
// @ts-expect-error Deno checks the generated JS implementation rather than its .d.ts.
import init, {
analyze_snapshot,
evaluate_snapshot,
} from "../../src/lib/workspace/config-source/generated/config_source_wasm.js";
import type { ConfigTreeSnapshot, ToolchainContract } from "../../src/lib/workspace/config-source/types.ts";
const bytes = await Deno.readFile(
new URL("../../src/lib/workspace/config-source/generated/config_source_wasm_bg.wasm", import.meta.url),
);
await init({ module_or_path: bytes });
const snapshot: ConfigTreeSnapshot = {
revision: 4,
digest: "sha256:test-tree",
entries: {
"workspace.dcdl": {
path: "workspace.dcdl",
content_type: "decodal",
content: 'import "./lib/value.dcdl"',
content_digest: "sha256:root",
},
"lib/value.dcdl": {
path: "lib/value.dcdl",
content_type: "decodal",
content: "{ answer = 42; }",
content_digest: "sha256:value",
},
},
};
const contract: ToolchainContract = {
contract_version: 1,
decodal_version: "0.2.0",
schema_version: 1,
entrypoints: ["workspace.dcdl"],
import_policy_version: 1,
fingerprint: "sha256:test-contract",
};
Deno.test("generated WASM evaluates the same virtual import contract", () => {
const result = evaluate_snapshot(snapshot, contract) as {
projections: Array<{ data_json: { answer: number } }>;
};
assertEquals(result.projections[0].data_json, { answer: 42 });
});
Deno.test("generated WASM diagnostics carry snapshot provenance", () => {
const diagnostics = analyze_snapshot(
snapshot,
"workspace.dcdl",
"{ broken = ; }",
) as Array<{
path: string;
revision: number;
tree_digest: string;
kind: string;
}>;
assertEquals(diagnostics[0].path, "workspace.dcdl");
assertEquals(diagnostics[0].revision, 4);
assertEquals(diagnostics[0].tree_digest, "sha256:test-tree");
assertEquals(diagnostics[0].kind, "syntax");
});