mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
feat(sdk): variable CRUD (declare/remove/get/list) + export gaps
Closes the remaining Tier 2/3 gaps from the SDK surface audit that motivated #2092 — real, contained fixes short of the two genuinely architectural items (a live-DOM apply adapter, structural editing ops) that need their own design pass, not a quick patch. Variable CRUD was write-only and creation-blocked: setVariableValue existed, but there was no getVariableValue, listVariables, declareVariable, or removeVariable — and writeVariableDefault intentionally refuses to create an undeclared variable ("keep the schema authoritative"), so a variables panel (list what exists, read current values, let someone add one) could not be built against the SDK at all. - getVariableValue(id) / listVariables(): thin reads over the existing readVariableDefault / a new listVariableDecls. - declareVariable(decl) / removeVariable(id): new EditOps with full undo/redo support via a new patch path (/variable-decls/{id}, distinct from /variables/{id} which is default-only) — removeVariable's inverse bundles the original array index so undo reinserts at the same position instead of appending, mirroring handleRemoveElement's siblingIndex. Export gaps (same shape as #2092's fixes — the logic already existed, just wasn't reachable): resolveScoped, findById, escapeHfId from engine/model.ts; readVariableDefault from engine/variableModel.ts. 17 new tests across mutate.test.ts (declareVariable/removeVariable engine semantics + undo), session.test.ts (Composition-level API), and smoke.test.ts (export-surface import check). 439/439 sdk tests passing. Full workspace build (incl. studio) verified clean.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* (engine/apply-patches.ts) can never disagree on the model's shape.
|
||||
*/
|
||||
|
||||
type VariableDecl = { id: string; default?: unknown; [key: string]: unknown };
|
||||
export type VariableDecl = { id: string; default?: unknown; [key: string]: unknown };
|
||||
|
||||
function getHtmlEl(document: Document): Element | null {
|
||||
return (document as Document & { documentElement?: Element }).documentElement ?? null;
|
||||
@@ -77,3 +77,66 @@ export function clearVariableDefault(document: Document, id: string): boolean {
|
||||
decls.htmlEl.setAttribute("data-composition-variables", JSON.stringify(decls.arr));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** All declared variables, or [] when the attribute is absent/invalid. */
|
||||
export function listVariableDecls(document: Document): VariableDecl[] {
|
||||
return readDecls(document)?.arr ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert a full variable declaration (id/type/label/default/…), unlike
|
||||
* writeVariableDefault which only ever touches the `default` field of an
|
||||
* ALREADY-declared variable and refuses to create new ones. This is the
|
||||
* "let someone add a variable" path a declarations panel needs — creates the
|
||||
* `data-composition-variables` attribute from scratch when absent.
|
||||
*
|
||||
* Replaces the whole existing decl when `decl.id` is already declared (so
|
||||
* editing a variable's type/label/options goes through the same call as
|
||||
* creating one). Returns the previous decl (for inverse-patch capture) or
|
||||
* null when this was a fresh create.
|
||||
*/
|
||||
export function declareVariableDecl(
|
||||
document: Document,
|
||||
decl: VariableDecl,
|
||||
opts?: { atIndex?: number },
|
||||
): VariableDecl | null {
|
||||
const htmlEl = getHtmlEl(document);
|
||||
if (!htmlEl) return null;
|
||||
const existing = readDecls(document);
|
||||
const arr = existing?.arr ?? [];
|
||||
const idx = indexOfId(arr, decl.id);
|
||||
const previous = idx < 0 ? null : arr[idx]!;
|
||||
if (idx >= 0) {
|
||||
arr[idx] = decl; // edit in place — position is already preserved
|
||||
} else if (opts?.atIndex !== undefined) {
|
||||
// Undo of removeVariable: reinsert at the exact index it was removed
|
||||
// from, so a remove-then-undo round-trips the array order, not just
|
||||
// set-membership (mirrors handleRemoveElement's siblingIndex).
|
||||
arr.splice(opts.atIndex, 0, decl);
|
||||
} else {
|
||||
arr.push(decl); // a genuinely new declaration goes to the end of the list
|
||||
}
|
||||
htmlEl.setAttribute("data-composition-variables", JSON.stringify(arr));
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a variable's declaration entirely (not just its default — the whole
|
||||
* schema entry). Live `var.{id}` overrides and any data-var-* DOM references
|
||||
* are left untouched; removing the declaration doesn't reach into either.
|
||||
* Returns the removed decl AND its array index (for inverse-patch capture, so
|
||||
* undo can reinsert at the original position — mirrors handleRemoveElement's
|
||||
* siblingIndex), or null when the attribute/decl was absent.
|
||||
*/
|
||||
export function removeVariableDecl(
|
||||
document: Document,
|
||||
id: string,
|
||||
): { decl: VariableDecl; index: number } | null {
|
||||
const decls = readDecls(document);
|
||||
if (!decls) return null;
|
||||
const idx = indexOfId(decls.arr, id);
|
||||
if (idx < 0) return null;
|
||||
const [removed] = decls.arr.splice(idx, 1);
|
||||
decls.htmlEl.setAttribute("data-composition-variables", JSON.stringify(decls.arr));
|
||||
return removed ? { decl: removed, index: idx } : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user