Implement shared live fields and checked query hydration

Add native register acknowledgments and idempotent mutation replay, shared optimistic field controllers, overlapping custom setters, non-suspending hooks and explicit Suspense. Carry checked @live provenance through batched queries and hydrate shared browser fields with coverage leases. Update tracker/scaffolds/guides and verify compiler, PostgreSQL, browser and immutable workspace paths.
This commit is contained in:
Timothy J. Aveni
2026-09-18 01:10:48 -07:00
parent a05f58f7fe
commit 2dffdbcd9f
17 changed files with 527 additions and 148 deletions
+26 -10
View File
@@ -7,13 +7,14 @@ import { spawnSync } from "node:child_process";
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
import { generateReactBindings } from "../src/bindings/react.js";
import { reactPlatformTypes } from "../src/bindings/react-platform.js";
import { compileQuery } from "../src/query/compile.js";
const source = { repository: "https://example.test/fields.git", commit: "a".repeat(40) };
test("React bindings preserve read-only, writable and nested reference contracts", async (t) => {
const iface = compileCapabilityResourceSource(
`interface Fields id "fields" revision "fields@1" {
value title id "title" : string { get id "title:get"; set id "title:set"; watch start id "watch" stop id "stop"; }
value summary id "summary" : string { get id "summary:get"; }
queryable value title id "title" : string { get id "title:get"; set id "title:set"; watch start id "watch" stop id "stop"; }
queryable value summary id "summary" : string { get id "summary:get"; }
}`,
{ source },
);
@@ -22,11 +23,19 @@ test("React bindings preserve read-only, writable and nested reference contracts
const pkg = compileCapabilityResourceSource(
`import interface Fields; package P id "p" revision "p@1" {
function props id "props" : unit -> record {fields: interface-ref<Fields>; caption: string;};
query Editor id "editor" root Fields document "editor.graphql" operation "Editor" {max rows 1; watch;}
}`,
{ source, environment: { interfaces: new Map([["Fields", iface.resource.revision]]) } },
);
assert.ok(pkg.ok && pkg.resource.kind === "package");
if (!pkg.ok || pkg.resource.kind !== "package") throw new Error("package failed");
pkg.resource.revision.checkedQueries = [
await compileQuery(
pkg.resource.revision.queries![0]!,
[iface.resource.revision],
async () => "query Editor {root {title @live summary @live plain: title}}",
),
];
const schema = {
format: "quixos-bindings",
version: 1,
@@ -68,7 +77,14 @@ test("React bindings preserve read-only, writable and nested reference contracts
path.join(root, "consumer.ts"),
`import {useLiveField, tryConform, type ReadableField, type WritableField} from "@quixos/web-studio-react-runtime";
import {reactInterfaces} from "./react-props.gen.js";
import type {ReactResults} from "./react-props.gen.js";
import type {ReactResults, QueryResults} from "./react-props.gen.js";
declare const query: QueryResults["Editor"];
useLiveField(query.root.title).set("new");
// @ts-expect-error live query fields retain exact setter types
useLiveField(query.root.title).set(123);
// @ts-expect-error readonly query fields do not acquire a setter
useLiveField(query.root.summary).set("no");
const plain: string = query.root.plain;
async function lookup() {
const view = await tryConform("object", reactInterfaces.Fields);
if (!view) return;
@@ -79,14 +95,14 @@ async function lookup() {
await view.call["summary.set"]("no setter");
}
declare const props: ReactResults["props"];
const [title, setTitle] = useLiveField(props.fields.fields.title);
setTitle("new");
const title = useLiveField(props.fields.fields.title);
title.set("new");
// @ts-expect-error wrong setter value
setTitle(123);
title.set(123);
// @ts-expect-error read-only hook has no setter
const [summary, setSummary] = useLiveField(props.fields.fields.summary);
const [manual, write] = useLiveField(props.fields.fields.summary, {write: async (value: string) => {}});
write("new");
useLiveField(props.fields.fields.summary).set("no");
const manual = useLiveField(props.fields.fields.summary, {id: "manual", write: async (value: string) => {}});
manual.set("new");
const readonly: ReadableField<string> = props.fields.fields.title;
// @ts-expect-error read-only does not satisfy writable
const writable: WritableField<string> = props.fields.fields.summary;
@@ -94,7 +110,7 @@ declare const narrow: WritableField<"only">;
// @ts-expect-error writable references are invariant
const widened: WritableField<string> = narrow;
// @ts-expect-error callbacks must accept the field's type
useLiveField(props.fields.fields.summary, {write: async (value: number) => {}});
useLiveField(props.fields.fields.summary, {id: "manual", write: async (value: number) => {}});
`,
);
const result = spawnSync(