Migrate TODO implementations to generated candidate bindings

This commit is contained in:
Timothy J. Aveni
2026-09-13 19:29:33 -07:00
parent 8480674212
commit 2b7035fe81
5 changed files with 78 additions and 19 deletions
+31 -8
View File
@@ -2,6 +2,21 @@ import { create } from "@bufbuild/protobuf";
import { ValueSchema, ObjectValueSchema } from "./camino/api_pb.js";
import { derived, jsToProtoValue, liveValue, protoValueToJs } from "./index.js";
import { assertReferenceFree, referenceToWire } from "./references.js";
const reactPropsDescriptor = "org.quixos.web-studio.ReactProps";
// Explicit temporary props exception, matching orch's RPC contract. Field shapes
// remain unchecked pending generics; ordinary messages and state do not gain it.
export const opaqueReactPropsBinding = {
encode(value) {
if (!value || typeof value !== "object" || Array.isArray(value))
throw new Error("React props must be an object");
return jsToProtoValue(value);
},
decode(value) {
if (value.kind.case !== "objectValue")
throw new Error("React props must be an object");
return protoValueToJs(value);
},
};
export const qxDerived = (get) => ({ kind: "derived", get });
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
export const decodeQxValue = (type, value, messages) => {
@@ -27,9 +42,11 @@ export const decodeQxValue = (type, value, messages) => {
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
}
if (type.kind === "message") {
assertReferenceFree(protoValueToJs(value));
if (type.descriptorId !== reactPropsDescriptor)
assertReferenceFree(protoValueToJs(value));
const decoded = requireMessage(messages, type.descriptorId).decode(value);
assertReferenceFree(decoded);
if (type.descriptorId !== reactPropsDescriptor)
assertReferenceFree(decoded);
return decoded;
}
if (type.kind === "object-ref") {
@@ -82,10 +99,12 @@ export const encodeQxValue = (type, value, messages) => {
referenceToWire(value);
return jsToProtoValue(value);
}
assertReferenceFree(value);
if (type.kind !== "message" || type.descriptorId !== reactPropsDescriptor)
assertReferenceFree(value);
if (type.kind === "message") {
const encoded = requireMessage(messages, type.descriptorId).encode(value);
assertReferenceFree(protoValueToJs(encoded));
if (type.descriptorId !== reactPropsDescriptor)
assertReferenceFree(protoValueToJs(encoded));
return encoded;
}
return jsToProtoValue(value);
@@ -115,7 +134,7 @@ export const bindQxHandler = (spec, handler, messages) => {
case "state": {
const state = raw.state(port.id);
return [name, {
...(port.primitives.includes("read") ? { get: async () => decodeQxValue(port.valueType, (await state.live()).$quixosValue, messages) } : {}),
...(port.primitives.includes("read") ? { get: async () => decodeQxValue(port.valueType, (await state.live()).$quixosValue, messages), live: () => state.live() } : {}),
...(port.primitives.includes("write") ? { set: async (value) => state.set(liveValue(encodeQxValue(port.valueType, value, messages))) } : {}),
}];
}
@@ -127,9 +146,13 @@ export const bindQxHandler = (spec, handler, messages) => {
}
case "interface": {
const target = raw.interface(port.id);
return [name, Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
async (input) => decodeQxValue(operation.outputType, (await target.live(operation.id, inputFields(operation.inputType, input, messages))).$quixosValue, messages),
]))];
return [name, { objectId: target.objectId,
live: Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
(input) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
])),
...Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
async (input) => decodeQxValue(operation.outputType, (await target.live(operation.id, inputFields(operation.inputType, input, messages))).$quixosValue, messages),
])) }];
}
case "constructor": return [name, { construct: (input) => raw.constructor(port.id).construct(inputFields(port.inputType, input, messages)) }];
}