feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/blocks",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"type": "module",
|
||||
"description": "Pure dynamic block document engine for WRNexus builders.",
|
||||
"exports": {
|
||||
|
||||
@@ -161,7 +161,10 @@ export function applyCommand(
|
||||
).length;
|
||||
root = insert(
|
||||
remove(root, new Set(command.intent.nodeIds)),
|
||||
{ ...command.intent.to, index: Math.max(0, command.intent.to.index - removedBeforeDestination) },
|
||||
{
|
||||
...command.intent.to,
|
||||
index: Math.max(0, command.intent.to.index - removedBeforeDestination),
|
||||
},
|
||||
moving as BlockNode[],
|
||||
);
|
||||
affected = command.intent.nodeIds;
|
||||
@@ -180,19 +183,43 @@ export function applyCommand(
|
||||
const originals = command.nodeIds.map((id) => findBlock(root, id));
|
||||
const locations = command.nodeIds.map((id) => locationOf(root, id));
|
||||
if (originals.some((node) => !node) || locations.some((location) => !location))
|
||||
return { ok: false, errors: [{ code: "command.missing", message: "A duplicated block does not exist.", severity: "error" }] };
|
||||
return {
|
||||
ok: false,
|
||||
errors: [
|
||||
{
|
||||
code: "command.missing",
|
||||
message: "A duplicated block does not exist.",
|
||||
severity: "error",
|
||||
},
|
||||
],
|
||||
};
|
||||
const countNodes = (node: BlockNode): number =>
|
||||
1 + Object.values(node.slots).flat().reduce((total, child) => total + countNodes(child), 0);
|
||||
1 +
|
||||
Object.values(node.slots)
|
||||
.flat()
|
||||
.reduce((total, child) => total + countNodes(child), 0);
|
||||
const requiredIds = (originals as BlockNode[]).reduce(
|
||||
(total, node) => total + countNodes(node),
|
||||
0,
|
||||
);
|
||||
if (command.ids.length !== requiredIds || new Set(command.ids).size !== command.ids.length)
|
||||
return { ok: false, errors: [{ code: "command.ids", message: "Duplicate requires one unique id for every copied node.", severity: "error" }] };
|
||||
const copies = duplicateNodes(originals as BlockNode[], (() => {
|
||||
let index = 0;
|
||||
return () => command.ids[index++]!;
|
||||
})());
|
||||
return {
|
||||
ok: false,
|
||||
errors: [
|
||||
{
|
||||
code: "command.ids",
|
||||
message: "Duplicate requires one unique id for every copied node.",
|
||||
severity: "error",
|
||||
},
|
||||
],
|
||||
};
|
||||
const copies = duplicateNodes(
|
||||
originals as BlockNode[],
|
||||
(() => {
|
||||
let index = 0;
|
||||
return () => command.ids[index++]!;
|
||||
})(),
|
||||
);
|
||||
const commands = copies.map((node, index) => ({
|
||||
type: "insert" as const,
|
||||
location: { ...locations[index]!, index: locations[index]!.index + 1 },
|
||||
|
||||
@@ -31,13 +31,27 @@ export function resolveDocumentVariables(
|
||||
): Result<BlockDocument> {
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
if (options.preserveTokens && options.requireValues)
|
||||
return { ok: false, errors: [{ code: "variable.mode", message: "Template tokens cannot be preserved when resolved values are required.", severity: "error" }] };
|
||||
return {
|
||||
ok: false,
|
||||
errors: [
|
||||
{
|
||||
code: "variable.mode",
|
||||
message: "Template tokens cannot be preserved when resolved values are required.",
|
||||
severity: "error",
|
||||
},
|
||||
],
|
||||
};
|
||||
const visit = (value: JsonValue, path: string): JsonValue => {
|
||||
if (typeof value === "string")
|
||||
return value.replace(TOKEN, (raw, key: string, fallback: string | undefined) => {
|
||||
const type = catalog[key];
|
||||
if (!type) {
|
||||
diagnostics.push({ code: "variable.unknown", message: `Unknown variable ${key}.`, severity: "error", path });
|
||||
diagnostics.push({
|
||||
code: "variable.unknown",
|
||||
message: `Unknown variable ${key}.`,
|
||||
severity: "error",
|
||||
path,
|
||||
});
|
||||
return raw;
|
||||
}
|
||||
if (options.preserveTokens) return raw;
|
||||
@@ -45,11 +59,21 @@ export function resolveDocumentVariables(
|
||||
if (supplied === undefined || supplied === null || supplied === "") {
|
||||
if (fallback !== undefined) return fallback.trim();
|
||||
if (options.requireValues)
|
||||
diagnostics.push({ code: "variable.missing", message: `Variable ${key} is required.`, severity: "error", path });
|
||||
diagnostics.push({
|
||||
code: "variable.missing",
|
||||
message: `Variable ${key} is required.`,
|
||||
severity: "error",
|
||||
path,
|
||||
});
|
||||
return raw;
|
||||
}
|
||||
if (!validType(supplied, type)) {
|
||||
diagnostics.push({ code: "variable.type", message: `Variable ${key} must be ${type}.`, severity: "error", path });
|
||||
diagnostics.push({
|
||||
code: "variable.type",
|
||||
message: `Variable ${key} must be ${type}.`,
|
||||
severity: "error",
|
||||
path,
|
||||
});
|
||||
return raw;
|
||||
}
|
||||
return supplied instanceof Date ? supplied.toISOString() : String(supplied);
|
||||
@@ -61,6 +85,9 @@ export function resolveDocumentVariables(
|
||||
);
|
||||
return value;
|
||||
};
|
||||
const resolved = visit(structuredClone(document) as unknown as JsonValue, "document") as unknown as BlockDocument;
|
||||
const resolved = visit(
|
||||
structuredClone(document) as unknown as JsonValue,
|
||||
"document",
|
||||
) as unknown as BlockDocument;
|
||||
return diagnostics.length ? { ok: false, errors: diagnostics } : { ok: true, value: resolved };
|
||||
}
|
||||
@@ -146,30 +146,42 @@ describe("dynamic blocks engine", () => {
|
||||
...text.create({ id: () => nodeId }),
|
||||
props: { text: nodeId },
|
||||
}));
|
||||
const moved = applyCommand(document, {
|
||||
type: "move",
|
||||
intent: {
|
||||
nodeIds: ["a"],
|
||||
from: [{ parentId: "root", slot: "content", index: 0 }],
|
||||
to: { parentId: "root", slot: "content", index: 3 },
|
||||
const moved = applyCommand(
|
||||
document,
|
||||
{
|
||||
type: "move",
|
||||
intent: {
|
||||
nodeIds: ["a"],
|
||||
from: [{ parentId: "root", slot: "content", index: 0 }],
|
||||
to: { parentId: "root", slot: "content", index: 3 },
|
||||
},
|
||||
},
|
||||
}, kind, registry);
|
||||
kind,
|
||||
registry,
|
||||
);
|
||||
expect(moved.ok && moved.value.document.root.slots.content.map((node) => node.id)).toEqual([
|
||||
"b", "c", "a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
]);
|
||||
});
|
||||
|
||||
test("duplicates blocks with deterministic injected ids", () => {
|
||||
const document = doc();
|
||||
document.root.slots.content = [{ ...text.create({ id: () => "a" }), props: { text: "A" } }];
|
||||
const duplicated = applyCommand(document, {
|
||||
type: "duplicate",
|
||||
nodeIds: ["a"],
|
||||
ids: ["a-copy"],
|
||||
}, kind, registry);
|
||||
expect(duplicated.ok && duplicated.value.document.root.slots.content.map((node) => node.id)).toEqual([
|
||||
"a", "a-copy",
|
||||
]);
|
||||
const duplicated = applyCommand(
|
||||
document,
|
||||
{
|
||||
type: "duplicate",
|
||||
nodeIds: ["a"],
|
||||
ids: ["a-copy"],
|
||||
},
|
||||
kind,
|
||||
registry,
|
||||
);
|
||||
expect(
|
||||
duplicated.ok && duplicated.value.document.root.slots.content.map((node) => node.id),
|
||||
).toEqual(["a", "a-copy"]);
|
||||
});
|
||||
|
||||
test("validates and resolves typed personalization tokens", () => {
|
||||
|
||||
Reference in new issue
Block a user