Implement main-thread Camino sessions and synchronous CRDT editing

This commit is contained in:
Timothy J. Aveni
2026-09-27 23:57:29 -07:00
parent ad72185a6c
commit 696023b4ba
24 changed files with 3784 additions and 3218 deletions
+2
View File
@@ -11,6 +11,8 @@ export const generateClientContracts = (
throw new Error("Host contracts require closed interface applications, not generic definitions");
const type = (value: ValueType): string => {
switch (value.kind) {
case "variant":
throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported");
case "builtin":
return value.name === "unit" ? "undefined" : "string";
case "scalar":
+2
View File
@@ -49,6 +49,8 @@ export const genericImplementationType = (
const value = (type: ValueTypeExpression, scope: Scope, depth = 0): string => {
if (depth > 128) throw new Error("Generic type expansion exceeds depth limit");
switch (type.kind) {
case "variant":
throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported");
case "parameter": {
const bound = scope.arguments.get(type.parameterId);
if (bound) return bound;
+3
View File
@@ -112,6 +112,8 @@ export const generateTypeScriptBindings = (
const messages = new Map<string, string>();
const type = (value: ValueType): string => {
switch (value.kind) {
case "variant":
throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported");
case "builtin":
return value.name === "unit" ? "null" : "QxWatchHandle";
case "scalar":
@@ -340,6 +342,7 @@ export const generateTypeScriptBindings = (
// A used port still fails above if its own required codec is missing.
const hasCodec = (value: ValueType): boolean => {
if (value.kind === "message") return !!options.messages?.[value.descriptorId];
if (value.kind === "variant") return false;
if (value.kind === "record") return Object.values(value.fields).every(hasCodec);
if (value.kind === "list" || value.kind === "optional") return hasCodec(value.value);
return true;
+15
View File
@@ -29,6 +29,17 @@ const expectation = (e: ObjectExpectation) => ({
/** The protocol IR and replica wire schema intentionally share structure, not reference identity. */
export function portableValueSchema(type: ValueType): unknown {
switch (type.kind) {
case "variant":
return {
kind: "variant",
id: type.id,
cases: Object.fromEntries(
Object.entries(type.cases).map(([label, entry]) => [
label,
{ id: entry.id, value: portableValueSchema(entry.value) },
]),
),
};
case "change":
return {
kind: "record",
@@ -65,6 +76,10 @@ export function portableValueSchema(type: ValueType): unknown {
export function tsType(type: ValueType): string {
portableValueSchema(type);
switch (type.kind) {
case "variant":
return `(${Object.entries(type.cases)
.map(([label, entry]) => `{ readonly kind: ${q(label)}; readonly value: ${tsType(entry.value)} }`)
.join(" | ")})`;
case "change":
return `PreparedChange<${tsType(type.value)}>`;
case "scalar":
+3
View File
@@ -15,6 +15,8 @@ export function generateReactBindings(
const declarations: string[] = [];
const type = (value: ValueType): string => {
switch (value.kind) {
case "variant":
throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported");
case "builtin":
if (value.name !== "unit") throw new Error(`Unsupported React props builtin ${value.name}`);
return "null";
@@ -85,6 +87,7 @@ export function generateReactBindings(
const browserValue = (value: ValueType, seen: Set<string>): boolean => {
if (value.kind === "message") return false;
if (value.kind === "builtin") return value.name === "unit";
if (value.kind === "variant") return false;
if (value.kind === "record") return Object.values(value.fields).every((field) => browserValue(field, seen));
if (value.kind === "list" || value.kind === "optional") return browserValue(value.value, seen);
if (value.kind === "object-ref" && value.expectation.kind === "interface") {