Restore the workspace canvas and atomic Studio creation workflow
This commit is contained in:
+28
-19
@@ -103,6 +103,7 @@ interface Method {
|
|||||||
input: ValueType;
|
input: ValueType;
|
||||||
output: ValueType;
|
output: ValueType;
|
||||||
keyed: boolean;
|
keyed: boolean;
|
||||||
|
explicitInput?: boolean;
|
||||||
}
|
}
|
||||||
function methods(
|
function methods(
|
||||||
schema: BindingSchema,
|
schema: BindingSchema,
|
||||||
@@ -185,7 +186,7 @@ function methods(
|
|||||||
if (
|
if (
|
||||||
(execution.kind === "remote" ? !remote : !execution.targets.includes(target)) ||
|
(execution.kind === "remote" ? !remote : !execution.targets.includes(target)) ||
|
||||||
op.mode !== "call" ||
|
op.mode !== "call" ||
|
||||||
op.scope === "class"
|
(op.scope === "class" && (remote || req.receiver === "input"))
|
||||||
)
|
)
|
||||||
throw new PortableBindingError(
|
throw new PortableBindingError(
|
||||||
`${path}.${member.displayName}.${op.displayName}`,
|
`${path}.${member.displayName}.${op.displayName}`,
|
||||||
@@ -194,24 +195,27 @@ function methods(
|
|||||||
if (execution.kind === "transaction" && !remote) writable();
|
if (execution.kind === "transaction" && !remote) writable();
|
||||||
if (req.receiver === "input" && remote)
|
if (req.receiver === "input" && remote)
|
||||||
throw new PortableBindingError(path, "Reference-selected dependencies require portable execution");
|
throw new PortableBindingError(path, "Reference-selected dependencies require portable execution");
|
||||||
return method(
|
return {
|
||||||
`${member.displayName}.${op.displayName}`,
|
...method(
|
||||||
{ kind: "interface", operationId: op.id },
|
`${member.displayName}.${op.displayName}`,
|
||||||
req.receiver === "input"
|
{ kind: "interface", operationId: op.id },
|
||||||
? {
|
req.receiver === "input"
|
||||||
kind: "record",
|
? {
|
||||||
fields: {
|
kind: "record",
|
||||||
receiver: {
|
fields: {
|
||||||
kind: "object-ref",
|
receiver: {
|
||||||
expectation: { kind: "interface", interfaceRevisionId: req.interfaceRevisionId },
|
kind: "object-ref",
|
||||||
|
expectation: { kind: "interface", interfaceRevisionId: req.interfaceRevisionId },
|
||||||
|
},
|
||||||
|
input: op.inputType,
|
||||||
},
|
},
|
||||||
input: op.inputType,
|
}
|
||||||
},
|
: op.inputType,
|
||||||
}
|
op.outputType,
|
||||||
: op.inputType,
|
execution.kind === "transaction",
|
||||||
op.outputType,
|
),
|
||||||
execution.kind === "transaction",
|
...(op.scope === "class" ? { explicitInput: true } : {}),
|
||||||
);
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -276,7 +280,7 @@ function generateBindings(
|
|||||||
throw new PortableBindingError(port.id, "Duplicate method name");
|
throw new PortableBindingError(port.id, "Duplicate method name");
|
||||||
all.push(...selected);
|
all.push(...selected);
|
||||||
facets.push(
|
facets.push(
|
||||||
`readonly ${q(port.displayName)}: { ${selected.map((m) => `readonly ${q(m.name)}: (${[...(isUnit(m.input) ? [] : [`input: ${tsType(m.input)}`]), ...(m.keyed ? [remote ? "options: ServerMutationOptions" : "options: MutationKey"] : [])].join(", ")}) => Promise<${tsType(m.output)}>`).join("; ")} }`,
|
`readonly ${q(port.displayName)}: { ${selected.map((m) => `readonly ${q(m.name)}: (${[...(isUnit(m.input) && !m.explicitInput ? [] : [`input: ${tsType(m.input)}`]), ...(m.keyed ? [remote ? "options: ServerMutationOptions" : "options: MutationKey"] : [])].join(", ")}) => Promise<${tsType(m.output)}>`).join("; ")} }`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const receiver =
|
const receiver =
|
||||||
@@ -312,6 +316,10 @@ function generateBindings(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const groups = new Map<string, typeof entries>();
|
const groups = new Map<string, typeof entries>();
|
||||||
|
for (const template of pkg.genericExports ?? []) {
|
||||||
|
if (template.execution?.kind !== "remote" && template.execution?.targets.includes(options.target))
|
||||||
|
groups.set(template.displayName, []);
|
||||||
|
}
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.application) continue;
|
if (!entry.application) continue;
|
||||||
const template = pkg.genericExports?.find((item) => item.id === entry.application!.exportId);
|
const template = pkg.genericExports?.find((item) => item.id === entry.application!.exportId);
|
||||||
@@ -319,6 +327,7 @@ function generateBindings(
|
|||||||
groups.set(template.displayName, [...(groups.get(template.displayName) ?? []), entry]);
|
groups.set(template.displayName, [...(groups.get(template.displayName) ?? []), entry]);
|
||||||
}
|
}
|
||||||
const generic = [...groups].map(([name, applications]) => {
|
const generic = [...groups].map(([name, applications]) => {
|
||||||
|
if (!applications.length) return `${q(name)}: (_handler: unknown) => ({})`;
|
||||||
contexts.push(
|
contexts.push(
|
||||||
`readonly ${q(name)}: ${applications.map((entry) => `Contexts[${q(entry.displayName)}]`).join(" | ")}`,
|
`readonly ${q(name)}: ${applications.map((entry) => `Contexts[${q(entry.displayName)}]`).join(" | ")}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1458,6 +1458,21 @@ const validateBoundDependencies = (
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
const selected = requirement.operations?.map(
|
||||||
|
(id) => params.indexes.interfaces.get(requirement.interfaceRevisionId)?.operations.get(id)?.operation,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
selected?.some((op) => op?.scope === "class") &&
|
||||||
|
(interfaceBinding.via || interfaceBinding.receiver || selected.some((op) => op?.scope !== "class"))
|
||||||
|
) {
|
||||||
|
issue(
|
||||||
|
issues,
|
||||||
|
"invalid-dependency-binding",
|
||||||
|
path,
|
||||||
|
"Static interface dependencies require a receiverless selection of class operations on the bound atom",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
// The reference supplies its own checked witness at invocation time.
|
// The reference supplies its own checked witness at invocation time.
|
||||||
// This is not a requirement that the current receiver implements the interface.
|
// This is not a requirement that the current receiver implements the interface.
|
||||||
if (interfaceBinding.receiver === "input") break;
|
if (interfaceBinding.receiver === "input") break;
|
||||||
@@ -1921,8 +1936,20 @@ const validateConformances = (
|
|||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
operation.scope === "class" &&
|
operation.scope === "class" &&
|
||||||
(binding.dependencies.some((entry) => !["constructor", "creation"].includes(entry.binding.kind)) ||
|
packageExport.dependencyPorts.some(({ requirement }) => {
|
||||||
packageExport.dependencyPorts.some((entry) => !["constructor", "creation"].includes(entry.requirement.kind)))
|
if (["constructor", "creation", "reflection"].includes(requirement.kind)) return false;
|
||||||
|
if (requirement.kind !== "interface") return true;
|
||||||
|
if (requirement.receiver === "input") return false;
|
||||||
|
const contract = indexes.interfaces.get(requirement.interfaceRevisionId);
|
||||||
|
return (
|
||||||
|
!requirement.operations?.length ||
|
||||||
|
!requirement.operations.every((id) =>
|
||||||
|
contract?.revision.members
|
||||||
|
.flatMap((m) => m.operations)
|
||||||
|
.some((op) => op.id === id && op.scope === "class"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
issue(
|
issue(
|
||||||
issues,
|
issues,
|
||||||
|
|||||||
@@ -156,6 +156,13 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
|
|||||||
...common,
|
...common,
|
||||||
kind: "interface",
|
kind: "interface",
|
||||||
interface: bound.interfaceRevisionId,
|
interface: bound.interfaceRevisionId,
|
||||||
|
...(!bound.receiver &&
|
||||||
|
!bound.via &&
|
||||||
|
(requirement.operations ?? iface.members.flatMap(m=>m.operations.map(o=>o.id))).every(
|
||||||
|
(id) => iface.members.flatMap((m) => m.operations).find((o) => o.id === id)?.scope === "class",
|
||||||
|
)
|
||||||
|
? { staticAtom: operation.atomId }
|
||||||
|
: {}),
|
||||||
...(bound.receiver ? { receiver: bound.receiver } : {}),
|
...(bound.receiver ? { receiver: bound.receiver } : {}),
|
||||||
operations: requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id)),
|
operations: requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id)),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -114,3 +114,20 @@ test("server projection descriptors are distinct from canonical inputs and have
|
|||||||
/@live is retired/,
|
/@live is retired/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("static interface ports keep unit arguments explicit for generic factory composition", async () => {
|
||||||
|
const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema;
|
||||||
|
const entry = schema.packages.flatMap((p) => p.exports).find((e) => e.displayName === "addHighlight")!;
|
||||||
|
const port = entry.dependencyPorts.find((p) => p.requirement.kind === "interface")!;
|
||||||
|
if (port.requirement.kind !== "interface") throw Error("interface required");
|
||||||
|
const requirement = port.requirement;
|
||||||
|
const iface = schema.interfaces.find((i) => i.revisionId === requirement.interfaceRevisionId)!;
|
||||||
|
const op = iface.members.flatMap((m) => m.operations).find((o) => o.id === requirement.operations![0])!;
|
||||||
|
op.scope = "class";
|
||||||
|
op.inputType = { kind: "builtin", name: "unit" };
|
||||||
|
port.requirement.operations = [op.id];
|
||||||
|
delete port.requirement.receiver;
|
||||||
|
const generated = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" });
|
||||||
|
assert.match(generated, /"explicitInput": true/);
|
||||||
|
assert.match(generated, /input: null/);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user