config: enforce browser preflight and generated DTOs
This commit is contained in:
Generated
+1
@@ -601,6 +601,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"ts-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ pub fn apply_changes(changes: JsValue) -> Result<JsValue, JsValue> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn changes_between(base: JsValue, candidate: JsValue) -> Result<JsValue, JsValue> {
|
||||||
|
let base: ConfigTreeSnapshot = decode(base)?;
|
||||||
|
let candidate: ConfigTreeSnapshot = decode(candidate)?;
|
||||||
|
encode(base.changes_to(&candidate))
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn evaluate_current(contract: JsValue) -> Result<JsValue, JsValue> {
|
pub fn evaluate_current(contract: JsValue) -> Result<JsValue, JsValue> {
|
||||||
let contract: ToolchainContract = decode(contract)?;
|
let contract: ToolchainContract = decode(contract)?;
|
||||||
@@ -68,7 +75,7 @@ struct WasmCompletionItem {
|
|||||||
pub fn complete_current(
|
pub fn complete_current(
|
||||||
entrypoint: String,
|
entrypoint: String,
|
||||||
source: String,
|
source: String,
|
||||||
utf8_byte_offset: usize,
|
utf16_offset: usize,
|
||||||
explicit: bool,
|
explicit: bool,
|
||||||
) -> Result<JsValue, JsValue> {
|
) -> Result<JsValue, JsValue> {
|
||||||
let entrypoint = VirtualPath::parse(entrypoint).map_err(js_error)?;
|
let entrypoint = VirtualPath::parse(entrypoint).map_err(js_error)?;
|
||||||
@@ -77,6 +84,7 @@ pub fn complete_current(
|
|||||||
let snapshot = session
|
let snapshot = session
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| JsValue::from_str("config source snapshot is not initialized"))?;
|
.ok_or_else(|| JsValue::from_str("config source snapshot is not initialized"))?;
|
||||||
|
let utf8_byte_offset = utf16_to_utf8_offset(&source, utf16_offset)?;
|
||||||
let result = SnapshotEnvironment::new(snapshot.clone())
|
let result = SnapshotEnvironment::new(snapshot.clone())
|
||||||
.complete(&entrypoint, &source, utf8_byte_offset, explicit)
|
.complete(&entrypoint, &source, utf8_byte_offset, explicit)
|
||||||
.map_err(|error| JsValue::from_str(&format!("{error:?}")))?
|
.map_err(|error| JsValue::from_str(&format!("{error:?}")))?
|
||||||
@@ -128,6 +136,24 @@ pub fn format_source(source: String) -> Result<String, JsValue> {
|
|||||||
.map_err(|error| JsValue::from_str(&error))
|
.map_err(|error| JsValue::from_str(&error))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn utf16_to_utf8_offset(source: &str, utf16_offset: usize) -> Result<usize, JsValue> {
|
||||||
|
let mut units = 0usize;
|
||||||
|
for (byte_offset, character) in source.char_indices() {
|
||||||
|
if units == utf16_offset {
|
||||||
|
return Ok(byte_offset);
|
||||||
|
}
|
||||||
|
units += character.len_utf16();
|
||||||
|
if units > utf16_offset {
|
||||||
|
return Err(JsValue::from_str("UTF-16 offset splits a surrogate pair"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if units == utf16_offset {
|
||||||
|
Ok(source.len())
|
||||||
|
} else {
|
||||||
|
Err(JsValue::from_str("UTF-16 offset is outside the source"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn decode<T: serde::de::DeserializeOwned>(value: JsValue) -> Result<T, JsValue> {
|
fn decode<T: serde::de::DeserializeOwned>(value: JsValue) -> Result<T, JsValue> {
|
||||||
from_value(value).map_err(|error| JsValue::from_str(&error.to_string()))
|
from_value(value).map_err(|error| JsValue::from_str(&error.to_string()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
ts-rs = "12.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub const MAX_ENTRY_BYTES: usize = 256 * 1024;
|
|||||||
pub const MAX_TOTAL_BYTES: usize = 4 * 1024 * 1024;
|
pub const MAX_TOTAL_BYTES: usize = 4 * 1024 * 1024;
|
||||||
pub const MAX_PATH_BYTES: usize = 512;
|
pub const MAX_PATH_BYTES: usize = 512;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, ts_rs::TS)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct VirtualPath(String);
|
pub struct VirtualPath(String);
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ impl fmt::Display for VirtualPath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ConfigContentType {
|
pub enum ConfigContentType {
|
||||||
Decodal,
|
Decodal,
|
||||||
@@ -87,7 +87,7 @@ impl ConfigContentType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct ConfigEntry {
|
pub struct ConfigEntry {
|
||||||
pub path: VirtualPath,
|
pub path: VirtualPath,
|
||||||
pub content_type: ConfigContentType,
|
pub content_type: ConfigContentType,
|
||||||
@@ -114,8 +114,9 @@ impl ConfigEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct ConfigTreeSnapshot {
|
pub struct ConfigTreeSnapshot {
|
||||||
|
#[ts(type = "number")]
|
||||||
pub revision: u64,
|
pub revision: u64,
|
||||||
pub digest: String,
|
pub digest: String,
|
||||||
pub entries: BTreeMap<VirtualPath, ConfigEntry>,
|
pub entries: BTreeMap<VirtualPath, ConfigEntry>,
|
||||||
@@ -174,6 +175,38 @@ impl ConfigTreeSnapshot {
|
|||||||
self.entries.get(path)
|
self.entries.get(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn changes_to(&self, candidate: &Self) -> Vec<ConfigTreeChange> {
|
||||||
|
let mut changes = Vec::new();
|
||||||
|
for (path, base_entry) in &self.entries {
|
||||||
|
match candidate.entries.get(path) {
|
||||||
|
None => changes.push(ConfigTreeChange::Delete {
|
||||||
|
path: path.clone(),
|
||||||
|
expected_digest: base_entry.content_digest.clone(),
|
||||||
|
}),
|
||||||
|
Some(candidate_entry)
|
||||||
|
if candidate_entry.content_digest != base_entry.content_digest =>
|
||||||
|
{
|
||||||
|
changes.push(ConfigTreeChange::Update {
|
||||||
|
path: path.clone(),
|
||||||
|
expected_digest: base_entry.content_digest.clone(),
|
||||||
|
content: candidate_entry.content.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (path, entry) in &candidate.entries {
|
||||||
|
if !self.entries.contains_key(path) {
|
||||||
|
changes.push(ConfigTreeChange::Create {
|
||||||
|
path: path.clone(),
|
||||||
|
content_type: entry.content_type,
|
||||||
|
content: entry.content.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changes
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply(&self, changes: &[ConfigTreeChange]) -> Result<Self, ConfigTreeError> {
|
pub fn apply(&self, changes: &[ConfigTreeChange]) -> Result<Self, ConfigTreeError> {
|
||||||
if changes.len() > MAX_CHANGE_COUNT {
|
if changes.len() > MAX_CHANGE_COUNT {
|
||||||
return Err(ConfigTreeError::LimitExceeded("change count"));
|
return Err(ConfigTreeError::LimitExceeded("change count"));
|
||||||
@@ -253,7 +286,7 @@ impl ConfigTreeSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum ConfigTreeChange {
|
pub enum ConfigTreeChange {
|
||||||
Create {
|
Create {
|
||||||
@@ -288,7 +321,7 @@ impl ConfigTreeChange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct ToolchainContract {
|
pub struct ToolchainContract {
|
||||||
pub contract_version: u32,
|
pub contract_version: u32,
|
||||||
pub decodal_version: String,
|
pub decodal_version: String,
|
||||||
@@ -329,21 +362,22 @@ impl ToolchainContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct ConfigSpan {
|
pub struct ConfigSpan {
|
||||||
pub start_byte: u32,
|
pub start_byte: u32,
|
||||||
pub end_byte: u32,
|
pub end_byte: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct ConfigDiagnosticLabel {
|
pub struct ConfigDiagnosticLabel {
|
||||||
pub span: ConfigSpan,
|
pub span: ConfigSpan,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct ConfigDiagnostic {
|
pub struct ConfigDiagnostic {
|
||||||
pub path: VirtualPath,
|
pub path: VirtualPath,
|
||||||
|
#[ts(type = "number")]
|
||||||
pub revision: u64,
|
pub revision: u64,
|
||||||
pub tree_digest: String,
|
pub tree_digest: String,
|
||||||
pub kind: String,
|
pub kind: String,
|
||||||
@@ -353,14 +387,16 @@ pub struct ConfigDiagnostic {
|
|||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
|
#[ts(export)]
|
||||||
pub struct EvaluatedProjection {
|
pub struct EvaluatedProjection {
|
||||||
pub entrypoint: VirtualPath,
|
pub entrypoint: VirtualPath,
|
||||||
|
#[ts(type = "unknown")]
|
||||||
pub data_json: serde_json::Value,
|
pub data_json: serde_json::Value,
|
||||||
pub projection_digest: String,
|
pub projection_digest: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
pub struct EvaluationResult {
|
pub struct EvaluationResult {
|
||||||
pub projections: Vec<EvaluatedProjection>,
|
pub projections: Vec<EvaluatedProjection>,
|
||||||
pub projection_digest: String,
|
pub projection_digest: String,
|
||||||
@@ -779,6 +815,30 @@ pub enum ConfigTreeError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exports_typescript_contract() {
|
||||||
|
let output = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../web/workspace/src/lib/workspace/config-source/generated/types");
|
||||||
|
std::fs::create_dir_all(&output).unwrap();
|
||||||
|
macro_rules! export {
|
||||||
|
($type:ty) => {
|
||||||
|
<$type>::export_all(&ts_rs::Config::default().with_out_dir(&output)).unwrap();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export!(VirtualPath);
|
||||||
|
export!(ConfigContentType);
|
||||||
|
export!(ConfigEntry);
|
||||||
|
export!(ConfigTreeSnapshot);
|
||||||
|
export!(ConfigTreeChange);
|
||||||
|
export!(ToolchainContract);
|
||||||
|
export!(ConfigSpan);
|
||||||
|
export!(ConfigDiagnosticLabel);
|
||||||
|
export!(ConfigDiagnostic);
|
||||||
|
export!(EvaluatedProjection);
|
||||||
|
export!(EvaluationResult);
|
||||||
|
}
|
||||||
|
|
||||||
fn path(value: &str) -> VirtualPath {
|
fn path(value: &str) -> VirtualPath {
|
||||||
VirtualPath::parse(value).unwrap()
|
VirtualPath::parse(value).unwrap()
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ use crate::{Error, Result, SqliteWorkspaceStore};
|
|||||||
|
|
||||||
pub const DEFAULT_CONFIG_ENTRYPOINT: &str = "workspace.dcdl";
|
pub const DEFAULT_CONFIG_ENTRYPOINT: &str = "workspace.dcdl";
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
|
#[ts(export)]
|
||||||
pub struct WorkspaceConfigState {
|
pub struct WorkspaceConfigState {
|
||||||
pub snapshot: ConfigTreeSnapshot,
|
pub snapshot: ConfigTreeSnapshot,
|
||||||
pub contract: ToolchainContract,
|
pub contract: ToolchainContract,
|
||||||
pub projection_digest: String,
|
pub projection_digest: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
|
#[ts(export)]
|
||||||
pub struct EvaluatedConfigCandidate {
|
pub struct EvaluatedConfigCandidate {
|
||||||
|
#[ts(type = "number")]
|
||||||
pub base_revision: u64,
|
pub base_revision: u64,
|
||||||
pub base_digest: String,
|
pub base_digest: String,
|
||||||
pub snapshot: ConfigTreeSnapshot,
|
pub snapshot: ConfigTreeSnapshot,
|
||||||
@@ -27,8 +30,10 @@ pub struct EvaluatedConfigCandidate {
|
|||||||
pub evaluation: EvaluationResult,
|
pub evaluation: EvaluationResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
|
#[ts(export)]
|
||||||
pub struct ConfigCommitRequest {
|
pub struct ConfigCommitRequest {
|
||||||
|
#[ts(type = "number")]
|
||||||
pub base_revision: u64,
|
pub base_revision: u64,
|
||||||
pub base_digest: String,
|
pub base_digest: String,
|
||||||
pub changes: Vec<ConfigTreeChange>,
|
pub changes: Vec<ConfigTreeChange>,
|
||||||
@@ -36,7 +41,8 @@ pub struct ConfigCommitRequest {
|
|||||||
pub toolchain_fingerprint: String,
|
pub toolchain_fingerprint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
|
||||||
|
#[ts(export)]
|
||||||
pub struct ConfigPreviewRequest {
|
pub struct ConfigPreviewRequest {
|
||||||
pub changes: Vec<ConfigTreeChange>,
|
pub changes: Vec<ConfigTreeChange>,
|
||||||
pub entrypoints: Vec<VirtualPath>,
|
pub entrypoints: Vec<VirtualPath>,
|
||||||
@@ -494,6 +500,18 @@ mod tests {
|
|||||||
assert!(store.load_workspace_config("w-config").unwrap().is_none());
|
assert!(store.load_workspace_config("w-config").unwrap().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exports_typescript_transport_contract() {
|
||||||
|
use ts_rs::TS;
|
||||||
|
let output = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../web/workspace/src/lib/workspace/config-source/generated/types");
|
||||||
|
let config = ts_rs::Config::default().with_out_dir(&output);
|
||||||
|
WorkspaceConfigState::export_all(&config).unwrap();
|
||||||
|
EvaluatedConfigCandidate::export_all(&config).unwrap();
|
||||||
|
ConfigCommitRequest::export_all(&config).unwrap();
|
||||||
|
ConfigPreviewRequest::export_all(&config).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn migration_creates_config_authority_without_changing_applied_migrations() {
|
fn migration_creates_config_authority_without_changing_applied_migrations() {
|
||||||
let store = SqliteWorkspaceStore::in_memory().unwrap();
|
let store = SqliteWorkspaceStore::in_memory().unwrap();
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
let baseRevision = $state(0);
|
let baseRevision = $state(0);
|
||||||
let baseDigest = $state("");
|
let baseDigest = $state("");
|
||||||
let renamePath = $state("");
|
let renamePath = $state("");
|
||||||
|
let baseSnapshot = $state<WorkspaceConfigTreeResponse["snapshot"] | null>(null);
|
||||||
|
let preflightDigest = $state("");
|
||||||
let toolchain: ConfigSourceToolchain | null = null;
|
let toolchain: ConfigSourceToolchain | null = null;
|
||||||
|
|
||||||
const paths = $derived(
|
const paths = $derived(
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
treeState && selectedPath ? treeState.snapshot.entries[selectedPath] : undefined,
|
treeState && selectedPath ? treeState.snapshot.entries[selectedPath] : undefined,
|
||||||
);
|
);
|
||||||
const dirty = $derived(draftChanges.length > 0 || (selected ? source !== selected.content : source.length > 0));
|
const dirty = $derived(draftChanges.length > 0 || (selected ? source !== selected.content : source.length > 0));
|
||||||
|
const commitReady = $derived(dirty && preflightDigest === treeState?.snapshot.digest);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
toolchain = new ConfigSourceToolchain();
|
toolchain = new ConfigSourceToolchain();
|
||||||
@@ -48,6 +51,7 @@
|
|||||||
selectedPath = Object.keys(treeState.snapshot.entries).toSorted()[0] ?? "";
|
selectedPath = Object.keys(treeState.snapshot.entries).toSorted()[0] ?? "";
|
||||||
}
|
}
|
||||||
source = selectedPath ? treeState.snapshot.entries[selectedPath].content : "";
|
source = selectedPath ? treeState.snapshot.entries[selectedPath].content : "";
|
||||||
|
baseSnapshot = structuredClone(treeState.snapshot);
|
||||||
baseRevision = treeState.snapshot.revision;
|
baseRevision = treeState.snapshot.revision;
|
||||||
baseDigest = treeState.snapshot.digest;
|
baseDigest = treeState.snapshot.digest;
|
||||||
await toolchain?.setSnapshot(treeState.snapshot);
|
await toolchain?.setSnapshot(treeState.snapshot);
|
||||||
@@ -65,13 +69,10 @@
|
|||||||
async function stageCurrent() {
|
async function stageCurrent() {
|
||||||
const change = currentChange();
|
const change = currentChange();
|
||||||
if (!change || !toolchain) return;
|
if (!change || !toolchain) return;
|
||||||
draftChanges = [...draftChanges.filter((item) => !changeTouches(item, selectedPath)), change];
|
|
||||||
const candidate = await toolchain.applyChanges([change]);
|
const candidate = await toolchain.applyChanges([change]);
|
||||||
if (treeState) treeState = { ...treeState, snapshot: candidate };
|
if (treeState) treeState = { ...treeState, snapshot: candidate };
|
||||||
}
|
if (baseSnapshot) draftChanges = await toolchain.changesBetween(baseSnapshot, candidate);
|
||||||
|
preflightDigest = "";
|
||||||
function changeTouches(change: ConfigTreeChange, path: string): boolean {
|
|
||||||
return change.kind === "rename" ? change.from === path || change.to === path : change.path === path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function select(path: string) {
|
async function select(path: string) {
|
||||||
@@ -144,6 +145,7 @@
|
|||||||
toolchain_fingerprint: treeState.contract.fingerprint,
|
toolchain_fingerprint: treeState.contract.fingerprint,
|
||||||
});
|
});
|
||||||
diagnostics = [];
|
diagnostics = [];
|
||||||
|
preflightDigest = candidate.snapshot.digest;
|
||||||
status = `Preview valid · projection ${candidate.evaluation.projection_digest.slice(0, 20)}…`;
|
status = `Preview valid · projection ${candidate.evaluation.projection_digest.slice(0, 20)}…`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
status = String(error);
|
status = String(error);
|
||||||
@@ -159,6 +161,10 @@
|
|||||||
status = "No draft changes to commit.";
|
status = "No draft changes to commit.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (preflightDigest !== treeState.snapshot.digest) {
|
||||||
|
status = "Preview the complete candidate successfully before Commit.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
busy = true;
|
busy = true;
|
||||||
try {
|
try {
|
||||||
treeState = await commitConfigTree(workspaceId, {
|
treeState = await commitConfigTree(workspaceId, {
|
||||||
@@ -169,6 +175,8 @@
|
|||||||
toolchain_fingerprint: treeState.contract.fingerprint,
|
toolchain_fingerprint: treeState.contract.fingerprint,
|
||||||
});
|
});
|
||||||
draftChanges = [];
|
draftChanges = [];
|
||||||
|
preflightDigest = "";
|
||||||
|
baseSnapshot = structuredClone(treeState.snapshot);
|
||||||
baseRevision = treeState.snapshot.revision;
|
baseRevision = treeState.snapshot.revision;
|
||||||
baseDigest = treeState.snapshot.digest;
|
baseDigest = treeState.snapshot.digest;
|
||||||
await toolchain?.setSnapshot(treeState.snapshot);
|
await toolchain?.setSnapshot(treeState.snapshot);
|
||||||
@@ -198,9 +206,10 @@
|
|||||||
path: selectedPath,
|
path: selectedPath,
|
||||||
expected_digest: selected.content_digest,
|
expected_digest: selected.content_digest,
|
||||||
};
|
};
|
||||||
draftChanges = [...draftChanges.filter((item) => !changeTouches(item, selectedPath)), change];
|
|
||||||
const candidate = await toolchain.applyChanges([change]);
|
const candidate = await toolchain.applyChanges([change]);
|
||||||
treeState = { ...treeState, snapshot: candidate };
|
treeState = { ...treeState, snapshot: candidate };
|
||||||
|
if (baseSnapshot) draftChanges = await toolchain.changesBetween(baseSnapshot, candidate);
|
||||||
|
preflightDigest = "";
|
||||||
selectedPath = Object.keys(candidate.entries).toSorted()[0] ?? "";
|
selectedPath = Object.keys(candidate.entries).toSorted()[0] ?? "";
|
||||||
source = selectedPath ? candidate.entries[selectedPath].content : "";
|
source = selectedPath ? candidate.entries[selectedPath].content : "";
|
||||||
renamePath = selectedPath;
|
renamePath = selectedPath;
|
||||||
@@ -218,9 +227,10 @@
|
|||||||
to,
|
to,
|
||||||
expected_digest: selected.content_digest,
|
expected_digest: selected.content_digest,
|
||||||
};
|
};
|
||||||
draftChanges = [...draftChanges.filter((item) => !changeTouches(item, selectedPath)), change];
|
|
||||||
const candidate = await toolchain.applyChanges([change]);
|
const candidate = await toolchain.applyChanges([change]);
|
||||||
treeState = { ...treeState, snapshot: candidate };
|
treeState = { ...treeState, snapshot: candidate };
|
||||||
|
if (baseSnapshot) draftChanges = await toolchain.changesBetween(baseSnapshot, candidate);
|
||||||
|
preflightDigest = "";
|
||||||
selectedPath = to;
|
selectedPath = to;
|
||||||
source = candidate.entries[to]?.content ?? "";
|
source = candidate.entries[to]?.content ?? "";
|
||||||
status = `Rename to ${to} staged. Preview and Commit to persist.`;
|
status = `Rename to ${to} staged. Preview and Commit to persist.`;
|
||||||
@@ -261,7 +271,7 @@
|
|||||||
<button type="button" onclick={format} disabled={!selectedPath || busy}>Format</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={analyze} disabled={!selectedPath || busy}>Analyze</button>
|
||||||
<button type="button" onclick={preview} disabled={!dirty || busy}>Preview</button>
|
<button type="button" onclick={preview} disabled={!dirty || busy}>Preview</button>
|
||||||
<button class="primary" type="button" onclick={commit} disabled={!dirty || busy}>Commit</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>
|
<button class="danger" type="button" onclick={deleteEntry} disabled={!selected || busy}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
+4
-1
@@ -5,7 +5,9 @@ export function analyze_snapshot(snapshot: any, entrypoint: string, source_overr
|
|||||||
|
|
||||||
export function apply_changes(changes: any): any;
|
export function apply_changes(changes: any): any;
|
||||||
|
|
||||||
export function complete_current(entrypoint: string, source: string, utf8_byte_offset: number, explicit: boolean): 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_current(contract: any): any;
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ export interface InitOutput {
|
|||||||
readonly memory: WebAssembly.Memory;
|
readonly memory: WebAssembly.Memory;
|
||||||
readonly analyze_snapshot: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
|
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 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 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_current: (a: any) => [number, number, number];
|
||||||
readonly evaluate_snapshot: (a: any, b: any) => [number, number, number];
|
readonly evaluate_snapshot: (a: any, b: any) => [number, number, number];
|
||||||
|
|||||||
@@ -30,19 +30,32 @@ export function apply_changes(changes) {
|
|||||||
return takeFromExternrefTable0(ret[0]);
|
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} entrypoint
|
||||||
* @param {string} source
|
* @param {string} source
|
||||||
* @param {number} utf8_byte_offset
|
* @param {number} utf16_offset
|
||||||
* @param {boolean} explicit
|
* @param {boolean} explicit
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export function complete_current(entrypoint, source, utf8_byte_offset, explicit) {
|
export function complete_current(entrypoint, source, utf16_offset, explicit) {
|
||||||
const ptr0 = passStringToWasm0(entrypoint, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(entrypoint, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN;
|
||||||
const ptr1 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr1 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
const len1 = WASM_VECTOR_LEN;
|
const len1 = WASM_VECTOR_LEN;
|
||||||
const ret = wasm.complete_current(ptr0, len0, ptr1, len1, utf8_byte_offset, explicit);
|
const ret = wasm.complete_current(ptr0, len0, ptr1, len1, utf16_offset, explicit);
|
||||||
if (ret[2]) {
|
if (ret[2]) {
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
+1
@@ -3,6 +3,7 @@
|
|||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export const analyze_snapshot: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
|
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 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 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_current: (a: any) => [number, number, number];
|
||||||
export const evaluate_snapshot: (a: any, b: any) => [number, number, number];
|
export const evaluate_snapshot: (a: any, b: any) => [number, number, 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 { 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 }, };
|
||||||
+6
@@ -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, };
|
||||||
@@ -4,6 +4,7 @@ import type { ConfigSourceWorkerRequest, ConfigSourceWorkerResponse } from "./to
|
|||||||
type Command =
|
type Command =
|
||||||
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "set_snapshot" }>, "id">
|
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "set_snapshot" }>, "id">
|
||||||
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "apply_changes" }>, "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: "analyze" }>, "id">
|
||||||
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "evaluate" }>, "id">
|
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "evaluate" }>, "id">
|
||||||
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "complete" }>, "id">
|
| Omit<Extract<ConfigSourceWorkerRequest, { kind: "complete" }>, "id">
|
||||||
@@ -31,14 +32,17 @@ export class ConfigSourceToolchain {
|
|||||||
applyChanges(changes: ConfigTreeChange[]): Promise<ConfigTreeSnapshot> {
|
applyChanges(changes: ConfigTreeChange[]): Promise<ConfigTreeSnapshot> {
|
||||||
return this.#request({ kind: "apply_changes", changes });
|
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[]> {
|
analyze(path: string, source?: string): Promise<ConfigDiagnostic[]> {
|
||||||
return this.#request({ kind: "analyze", path, source });
|
return this.#request({ kind: "analyze", path, source });
|
||||||
}
|
}
|
||||||
evaluate(contract: ToolchainContract) {
|
evaluate(contract: ToolchainContract) {
|
||||||
return this.#request({ kind: "evaluate", contract });
|
return this.#request({ kind: "evaluate", contract });
|
||||||
}
|
}
|
||||||
complete(path: string, source: string, utf8ByteOffset: number, explicit = false): Promise<import("@codemirror/autocomplete").CompletionResult | null> {
|
complete(path: string, source: string, utf16Offset: number, explicit = false): Promise<import("@codemirror/autocomplete").CompletionResult | null> {
|
||||||
return this.#request({ kind: "complete", path, source, utf8ByteOffset, explicit });
|
return this.#request({ kind: "complete", path, source, utf16Offset, explicit });
|
||||||
}
|
}
|
||||||
format(source: string): Promise<string> {
|
format(source: string): Promise<string> {
|
||||||
return this.#request({ kind: "format", source });
|
return this.#request({ kind: "format", source });
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import init, {
|
import init, {
|
||||||
analyze_snapshot,
|
analyze_snapshot,
|
||||||
apply_changes,
|
apply_changes,
|
||||||
|
changes_between,
|
||||||
complete_current,
|
complete_current,
|
||||||
evaluate_current,
|
evaluate_current,
|
||||||
format_source,
|
format_source,
|
||||||
@@ -11,9 +12,10 @@ import type { ConfigTreeChange } from "./types.ts";
|
|||||||
export type ConfigSourceWorkerRequest =
|
export type ConfigSourceWorkerRequest =
|
||||||
| { id: number; kind: "set_snapshot"; snapshot: unknown }
|
| { id: number; kind: "set_snapshot"; snapshot: unknown }
|
||||||
| { id: number; kind: "apply_changes"; changes: ConfigTreeChange[] }
|
| { 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: "analyze"; path: string; source?: string }
|
||||||
| { id: number; kind: "evaluate"; contract: unknown }
|
| { id: number; kind: "evaluate"; contract: unknown }
|
||||||
| { id: number; kind: "complete"; path: string; source: string; utf8ByteOffset: number; explicit: boolean }
|
| { id: number; kind: "complete"; path: string; source: string; utf16Offset: number; explicit: boolean }
|
||||||
| { id: number; kind: "format"; source: string };
|
| { id: number; kind: "format"; source: string };
|
||||||
|
|
||||||
export type ConfigSourceWorkerResponse =
|
export type ConfigSourceWorkerResponse =
|
||||||
@@ -38,6 +40,9 @@ self.onmessage = async (event: MessageEvent<ConfigSourceWorkerRequest>): Promise
|
|||||||
snapshot = apply_changes(request.changes);
|
snapshot = apply_changes(request.changes);
|
||||||
result = snapshot;
|
result = snapshot;
|
||||||
break;
|
break;
|
||||||
|
case "changes_between":
|
||||||
|
result = changes_between(request.base, request.candidate);
|
||||||
|
break;
|
||||||
case "analyze":
|
case "analyze":
|
||||||
if (!snapshot) throw new Error("config source snapshot is not initialized");
|
if (!snapshot) throw new Error("config source snapshot is not initialized");
|
||||||
result = analyze_snapshot(snapshot, request.path, request.source);
|
result = analyze_snapshot(snapshot, request.path, request.source);
|
||||||
@@ -46,7 +51,7 @@ self.onmessage = async (event: MessageEvent<ConfigSourceWorkerRequest>): Promise
|
|||||||
result = evaluate_current(request.contract);
|
result = evaluate_current(request.contract);
|
||||||
break;
|
break;
|
||||||
case "complete":
|
case "complete":
|
||||||
result = complete_current(request.path, request.source, request.utf8ByteOffset, request.explicit);
|
result = complete_current(request.path, request.source, request.utf16Offset, request.explicit);
|
||||||
break;
|
break;
|
||||||
case "format":
|
case "format":
|
||||||
result = format_source(request.source);
|
result = format_source(request.source);
|
||||||
|
|||||||
@@ -1,93 +1,16 @@
|
|||||||
export type VirtualPath = string;
|
// Core and HTTP DTOs are generated from Rust with ts-rs.
|
||||||
|
export type { ConfigContentType } from "./generated/types/ConfigContentType.ts";
|
||||||
export type ConfigContentType = "decodal" | "text";
|
export type { ConfigDiagnostic } from "./generated/types/ConfigDiagnostic.ts";
|
||||||
|
export type { ConfigDiagnosticLabel } from "./generated/types/ConfigDiagnosticLabel.ts";
|
||||||
export interface ConfigEntry {
|
export type { ConfigEntry } from "./generated/types/ConfigEntry.ts";
|
||||||
path: VirtualPath;
|
export type { ConfigSpan } from "./generated/types/ConfigSpan.ts";
|
||||||
content_type: ConfigContentType;
|
export type { ConfigTreeChange } from "./generated/types/ConfigTreeChange.ts";
|
||||||
content: string;
|
export type { ConfigTreeSnapshot } from "./generated/types/ConfigTreeSnapshot.ts";
|
||||||
content_digest: string;
|
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 interface ConfigTreeSnapshot {
|
export type { VirtualPath } from "./generated/types/VirtualPath.ts";
|
||||||
revision: number;
|
export type { ConfigCommitRequest } from "./generated/types/ConfigCommitRequest.ts";
|
||||||
digest: string;
|
export type { ConfigPreviewRequest } from "./generated/types/ConfigPreviewRequest.ts";
|
||||||
entries: Record<VirtualPath, ConfigEntry>;
|
export type { EvaluatedConfigCandidate } from "./generated/types/EvaluatedConfigCandidate.ts";
|
||||||
}
|
export type { WorkspaceConfigState as WorkspaceConfigTreeResponse } from "./generated/types/WorkspaceConfigState.ts";
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ToolchainContract {
|
|
||||||
contract_version: number;
|
|
||||||
decodal_version: string;
|
|
||||||
schema_version: number;
|
|
||||||
entrypoints: VirtualPath[];
|
|
||||||
import_policy_version: number;
|
|
||||||
fingerprint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfigDiagnostic {
|
|
||||||
path: VirtualPath;
|
|
||||||
revision: number;
|
|
||||||
tree_digest: string;
|
|
||||||
kind: string;
|
|
||||||
span: { start_byte: number; end_byte: number };
|
|
||||||
message: string;
|
|
||||||
labels: Array<{
|
|
||||||
span: { start_byte: number; end_byte: number };
|
|
||||||
message: string;
|
|
||||||
}>;
|
|
||||||
notes: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkspaceConfigTreeResponse {
|
|
||||||
snapshot: ConfigTreeSnapshot;
|
|
||||||
contract: ToolchainContract;
|
|
||||||
projection_digest: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EvaluatedConfigCandidate {
|
|
||||||
base_revision: number;
|
|
||||||
base_digest: string;
|
|
||||||
snapshot: ConfigTreeSnapshot;
|
|
||||||
contract: ToolchainContract;
|
|
||||||
evaluation: {
|
|
||||||
projections: Array<{
|
|
||||||
entrypoint: VirtualPath;
|
|
||||||
data_json: unknown;
|
|
||||||
projection_digest: string;
|
|
||||||
}>;
|
|
||||||
projection_digest: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfigCommitRequest {
|
|
||||||
base_revision: number;
|
|
||||||
base_digest: string;
|
|
||||||
changes: ConfigTreeChange[];
|
|
||||||
entrypoints: VirtualPath[];
|
|
||||||
toolchain_fingerprint: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onComplete?: (source: string, utf8ByteOffset: number, explicit: boolean) => Promise<CompletionResult | null>;
|
onComplete?: (source: string, utf16Offset: number, explicit: boolean) => Promise<CompletionResult | null>;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let host = $state<HTMLDivElement | null>(null);
|
let host = $state<HTMLDivElement | null>(null);
|
||||||
@@ -54,8 +54,7 @@
|
|||||||
decodal(),
|
decodal(),
|
||||||
...(handleComplete ? [autocompletion({ override: [async (context: CompletionContext) => {
|
...(handleComplete ? [autocompletion({ override: [async (context: CompletionContext) => {
|
||||||
const doc = context.state.doc.toString();
|
const doc = context.state.doc.toString();
|
||||||
const utf8ByteOffset = new TextEncoder().encode(doc.slice(0, context.pos)).byteLength;
|
return await handleComplete(doc, context.pos, context.explicit);
|
||||||
return await handleComplete(doc, utf8ByteOffset, context.explicit);
|
|
||||||
}] })] : []),
|
}] })] : []),
|
||||||
keymap.of([]),
|
keymap.of([]),
|
||||||
EditorState.readOnly.of(initialReadonly),
|
EditorState.readOnly.of(initialReadonly),
|
||||||
|
|||||||
Reference in New Issue
Block a user