Compare commits
2 Commits
499b65b274
...
eaf6203b73
| Author | SHA1 | Date | |
|---|---|---|---|
| eaf6203b73 | |||
| 17f6941f64 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
|
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
|
||||||
"sourceCommit": "1de28b236de076d239752b77553f833dfbdb5b43",
|
"sourceCommit": "0355d845d7ad4b797343b681e9b5e1a77c63c720",
|
||||||
"sourcePath": "quixos-protocol",
|
"sourcePath": "quixos-protocol",
|
||||||
"exportName": "quixos-protocol",
|
"exportName": "quixos-protocol",
|
||||||
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import "quixos/refs.proto";
|
|||||||
import "quixos/runtime.proto";
|
import "quixos/runtime.proto";
|
||||||
|
|
||||||
service OrchestratorRuntime {
|
service OrchestratorRuntime {
|
||||||
|
rpc TryConform(TryConformRequest) returns (TryConformResponse);
|
||||||
rpc InvokeCapability(InvokeCapabilityRequest) returns (InvokeCapabilityResponse);
|
rpc InvokeCapability(InvokeCapabilityRequest) returns (InvokeCapabilityResponse);
|
||||||
rpc EditCapabilityField(EditCapabilityFieldRequest) returns (InvokeCapabilityResponse);
|
rpc EditCapabilityField(EditCapabilityFieldRequest) returns (InvokeCapabilityResponse);
|
||||||
rpc InvokeClassCapability(InvokeClassCapabilityRequest) returns (InvokeCapabilityResponse);
|
rpc InvokeClassCapability(InvokeClassCapabilityRequest) returns (InvokeCapabilityResponse);
|
||||||
@@ -22,6 +23,16 @@ service OrchestratorRuntime {
|
|||||||
rpc CloseActivation(CloseActivationRequest) returns (CloseActivationResponse);
|
rpc CloseActivation(CloseActivationRequest) returns (CloseActivationResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exact closed interface lookup; no policy selection or competing conformances.
|
||||||
|
message TryConformRequest {
|
||||||
|
string object_id = 1;
|
||||||
|
string interface_revision_id = 2;
|
||||||
|
}
|
||||||
|
message TryConformResponse {
|
||||||
|
// Absent only when this object lacks a known contract. Other failures are errors.
|
||||||
|
quixos.ConformanceWitness conformance = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message ConstructObjectRequest {
|
message ConstructObjectRequest {
|
||||||
string atom_id = 1;
|
string atom_id = 1;
|
||||||
map<string, camino.Value> input = 2;
|
map<string, camino.Value> input = 2;
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ package quixos;
|
|||||||
message CapabilityRef {
|
message CapabilityRef {
|
||||||
string interface_revision_id = 1;
|
string interface_revision_id = 1;
|
||||||
string operation_id = 2;
|
string operation_id = 2;
|
||||||
|
// Optional fence for a view acquired through TryConform. Not an authority grant.
|
||||||
|
ConformanceWitness conformance = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConformanceWitness {
|
||||||
|
string object_id = 1;
|
||||||
|
string interface_revision_id = 2;
|
||||||
|
string conformance_id = 3;
|
||||||
|
string workspace_revision_id = 4;
|
||||||
|
string workspace_epoch = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PackageExportRef {
|
message PackageExportRef {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const genericImplementationType = (
|
|||||||
.filter((op) => op.mode === "call" && op.scope !== "class")
|
.filter((op) => op.mode === "call" && op.scope !== "class")
|
||||||
.map((op) => ({ ...op, name: `${member.displayName}.${op.displayName}` })),
|
.map((op) => ({ ...op, name: `${member.displayName}.${op.displayName}` })),
|
||||||
);
|
);
|
||||||
return object([
|
const methods: [string, string][] = [
|
||||||
["objectId", `QxObjectRef<${target({ kind: "application", application: requirement.application }, scope)}>`],
|
["objectId", `QxObjectRef<${target({ kind: "application", application: requirement.application }, scope)}>`],
|
||||||
["live", object(operations.map((op) => [op.name, `(${params(op.inputType, local)})=>Promise<QxLiveValue>`]))],
|
["live", object(operations.map((op) => [op.name, `(${params(op.inputType, local)})=>Promise<QxLiveValue>`]))],
|
||||||
...operations.map(
|
...operations.map(
|
||||||
@@ -137,7 +137,8 @@ export const genericImplementationType = (
|
|||||||
string,
|
string,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]);
|
];
|
||||||
|
return object([...methods, ["contract", `QxInterfaceContract<${object(methods)}>`]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -146,6 +147,7 @@ export const genericImplementationType = (
|
|||||||
.join(",");
|
.join(",");
|
||||||
const receiver = definition.receiverRequirement;
|
const receiver = definition.receiverRequirement;
|
||||||
const context = object([
|
const context = object([
|
||||||
|
["conform", "QxConformer"],
|
||||||
...(definition.kind === "function"
|
...(definition.kind === "function"
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
|
|||||||
+42
-18
@@ -7,6 +7,7 @@ import type {
|
|||||||
} from "../capability-model/types.js";
|
} from "../capability-model/types.js";
|
||||||
import type { CompiledCapabilityResourceRepository } from "../capability-language/assembly.js";
|
import type { CompiledCapabilityResourceRepository } from "../capability-language/assembly.js";
|
||||||
import { genericImplementationType } from "./generics.js";
|
import { genericImplementationType } from "./generics.js";
|
||||||
|
import { capabilityId } from "../capability-model/types.js";
|
||||||
|
|
||||||
/** Portable generator input. New backends consume this instead of the parser or TS runtime. */
|
/** Portable generator input. New backends consume this instead of the parser or TS runtime. */
|
||||||
export type BindingSchema = {
|
export type BindingSchema = {
|
||||||
@@ -183,26 +184,28 @@ export const generateTypeScriptBindings = (
|
|||||||
.filter((operation) => operation.mode === "call" && operation.scope !== "class")
|
.filter((operation) => operation.mode === "call" && operation.scope !== "class")
|
||||||
.map((operation) => ({ ...operation, name: `${member.displayName}.${operation.displayName}` })),
|
.map((operation) => ({ ...operation, name: `${member.displayName}.${operation.displayName}` })),
|
||||||
);
|
);
|
||||||
return {
|
const methods: [string, string][] = [
|
||||||
type: object([
|
["objectId", ref({ kind: "interface", interfaceRevisionId: requirement.interfaceRevisionId })],
|
||||||
["objectId", ref({ kind: "interface", interfaceRevisionId: requirement.interfaceRevisionId })],
|
[
|
||||||
[
|
"live",
|
||||||
"live",
|
object(
|
||||||
object(
|
operations.map((operation) => [
|
||||||
operations.map((operation) => [
|
operation.name,
|
||||||
operation.name,
|
`(${params(operation.inputType)}) => Promise<QxLiveValue>`,
|
||||||
`(${params(operation.inputType)}) => Promise<QxLiveValue>`,
|
]),
|
||||||
]),
|
),
|
||||||
),
|
],
|
||||||
],
|
...operations.map((operation): [string, string] => [
|
||||||
...operations.map((operation): [string, string] => [
|
operation.name,
|
||||||
operation.name,
|
`(${params(operation.inputType)}) => Promise<${type(operation.outputType)}>`,
|
||||||
`(${params(operation.inputType)}) => Promise<${type(operation.outputType)}>`,
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
type: object([...methods, ["contract", `QxInterfaceContract<${object(methods)}>`]]),
|
||||||
spec: {
|
spec: {
|
||||||
kind: "interface",
|
kind: "interface",
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
|
interfaceRevisionId: requirement.interfaceRevisionId,
|
||||||
operations: Object.fromEntries(
|
operations: Object.fromEntries(
|
||||||
operations.map(({ name, id, inputType, outputType }) => [name, { id, inputType, outputType }]),
|
operations.map(({ name, id, inputType, outputType }) => [name, { id, inputType, outputType }]),
|
||||||
),
|
),
|
||||||
@@ -245,6 +248,7 @@ export const generateTypeScriptBindings = (
|
|||||||
? `QxObjectRef<${entry.receiverRequirement.interfaceRevisionIds.map((id) => q(`interface:${id}`)).join(" | ") || "never"}>`
|
? `QxObjectRef<${entry.receiverRequirement.interfaceRevisionIds.map((id) => q(`interface:${id}`)).join(" | ") || "never"}>`
|
||||||
: "QxObjectRef<string>";
|
: "QxObjectRef<string>";
|
||||||
const contextShape = object([
|
const contextShape = object([
|
||||||
|
["conform", "QxConformer"],
|
||||||
...(entry.kind === "function" ? [] : [["objectId", receiver] as [string, string]]),
|
...(entry.kind === "function" ? [] : [["objectId", receiver] as [string, string]]),
|
||||||
["input", type(entry.inputType)],
|
["input", type(entry.inputType)],
|
||||||
["ports", object(ports.map((port) => [port.name, port.type]))],
|
["ports", object(ports.map((port) => [port.name, port.type]))],
|
||||||
@@ -281,13 +285,26 @@ export const generateTypeScriptBindings = (
|
|||||||
genericImplementationType(definition, [...schema.interfaces, ...(schema.interfaceTemplates ?? [])], type),
|
genericImplementationType(definition, [...schema.interfaces, ...(schema.interfaceTemplates ?? [])], type),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
const contracts = schema.interfaces.map((iface) => {
|
||||||
|
const generated = port({
|
||||||
|
id: capabilityId.dependencyPort("contract"),
|
||||||
|
displayName: iface.displayName,
|
||||||
|
requirement: { kind: "interface", interfaceRevisionId: iface.revisionId },
|
||||||
|
});
|
||||||
|
const spec = generated.spec as { operations: unknown };
|
||||||
|
return {
|
||||||
|
iface,
|
||||||
|
type: generated.type,
|
||||||
|
code: `defineQxInterfaceContract<${generated.type}>(${q(iface.revisionId)}, ${JSON.stringify(spec.operations)})`,
|
||||||
|
};
|
||||||
|
});
|
||||||
const imports = [...messages].map(([id, alias]) => {
|
const imports = [...messages].map(([id, alias]) => {
|
||||||
const binding = options.messages![id]!;
|
const binding = options.messages![id]!;
|
||||||
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(binding.export))
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(binding.export))
|
||||||
throw new Error(`Invalid message binding export ${binding.export}`);
|
throw new Error(`Invalid message binding export ${binding.export}`);
|
||||||
return `import { ${binding.export} as ${alias} } from ${q(binding.module)};`;
|
return `import { ${binding.export} as ${alias} } from ${q(binding.module)};`;
|
||||||
});
|
});
|
||||||
const signatures = `${object(contexts)} ${object(handlers)} ${object(results)}`;
|
const signatures = `${object(contexts)} ${object(handlers)} ${object(results)} ${contracts.map((entry) => entry.type).join(" ")}`;
|
||||||
const typeImports = [
|
const typeImports = [
|
||||||
"BindingValue",
|
"BindingValue",
|
||||||
"QxObjectRef",
|
"QxObjectRef",
|
||||||
@@ -295,17 +312,24 @@ export const generateTypeScriptBindings = (
|
|||||||
"QxHandler",
|
"QxHandler",
|
||||||
"QxDerived",
|
"QxDerived",
|
||||||
"QxContextLifecycle",
|
"QxContextLifecycle",
|
||||||
|
"QxConformer",
|
||||||
|
"QxInterfaceContract",
|
||||||
"QxLiveValue",
|
"QxLiveValue",
|
||||||
"RelationshipCollection",
|
"RelationshipCollection",
|
||||||
"RelationshipEntry",
|
"RelationshipEntry",
|
||||||
].filter((name) => new RegExp(`\\b${name}\\b`).test(signatures));
|
].filter((name) => new RegExp(`\\b${name}\\b`).test(signatures));
|
||||||
return (
|
return (
|
||||||
`// Generated by quixos-codegen-ts. Do not edit. Binding ABI version 1.\n` +
|
`// Generated by quixos-codegen-ts. Do not edit. Binding ABI version 1.\n` +
|
||||||
`import { ${exports.length ? "bindQxHandler, " : ""}${[...typeImports, "QxHandlerSpec", "QxMessages"].map((name) => `type ${name}`).join(", ")} } from ${q(options.runtimeModule ?? "@quixos/camino-package-runtime")};\n` +
|
`import { ${contracts.length ? "defineQxInterfaceContract, " : ""}${exports.length ? "bindQxHandler, " : ""}${[...typeImports, "QxHandlerSpec", "QxMessages"].map((name) => `type ${name}`).join(", ")} } from ${q(options.runtimeModule ?? "@quixos/camino-package-runtime")};\n` +
|
||||||
imports.join("\n") +
|
imports.join("\n") +
|
||||||
`\nexport const packageRevisionId = ${q(pkg.revisionId)};\n` +
|
`\nexport const packageRevisionId = ${q(pkg.revisionId)};\n` +
|
||||||
`declare const appliedType: unique symbol;\ntype QxApplied<Definition extends string, Arguments extends readonly unknown[]> = string & {readonly [appliedType]: (value: [Definition, Arguments]) => [Definition, Arguments]};\n` +
|
`declare const appliedType: unique symbol;\ntype QxApplied<Definition extends string, Arguments extends readonly unknown[]> = string & {readonly [appliedType]: (value: [Definition, Arguments]) => [Definition, Arguments]};\n` +
|
||||||
`export type Contexts = ${object(contexts)};\nexport type Results = ${object(results)};\nexport type Implementation = ${object(handlers)};\n` +
|
`export type Contexts = ${object(contexts)};\nexport type Results = ${object(results)};\nexport type Implementation = ${object(handlers)};\n` +
|
||||||
|
`export const contracts = {${contracts.map((entry) => `${q(entry.iface.revisionId)}: ${entry.code}`).join(",\n")}};\n` +
|
||||||
|
`export const interfaces = {${contracts
|
||||||
|
.filter((entry) => contracts.filter((other) => other.iface.displayName === entry.iface.displayName).length === 1)
|
||||||
|
.map((entry) => `${q(entry.iface.displayName)}: contracts[${q(entry.iface.revisionId)}]`)
|
||||||
|
.join(",\n")}};\n` +
|
||||||
`const messages = { ${[...messages].map(([id, alias]) => `${q(id)}: ${alias}`).join(", ")} } satisfies QxMessages;\n` +
|
`const messages = { ${[...messages].map(([id, alias]) => `${q(id)}: ${alias}`).join(", ")} } satisfies QxMessages;\n` +
|
||||||
`const specs = ${JSON.stringify(specs, null, 2)} satisfies Record<string, QxHandlerSpec>;\n` +
|
`const specs = ${JSON.stringify(specs, null, 2)} satisfies Record<string, QxHandlerSpec>;\n` +
|
||||||
`export const createRuntime = (implementation: Implementation) => ({\n packageRevisionId,\n exports: {\n` +
|
`export const createRuntime = (implementation: Implementation) => ({\n packageRevisionId,\n exports: {\n` +
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ declare module "@quixos/web-studio-react-runtime" {
|
|||||||
export type WritableField<T> = ReadableField<T> & {readonly $writeType?: (value: T) => T; readonly writable: true; readonly capability: FieldCapability & {setOperationId: string}};
|
export type WritableField<T> = ReadableField<T> & {readonly $writeType?: (value: T) => T; readonly writable: true; readonly capability: FieldCapability & {setOperationId: string}};
|
||||||
export type LiveFieldProp<T> = ReadableField<T>;
|
export type LiveFieldProp<T> = ReadableField<T>;
|
||||||
export type InterfaceReference<I extends string, Fields> = {readonly $quixosRef: string; readonly interfaceRevisionId: I; readonly fields: Fields};
|
export type InterfaceReference<I extends string, Fields> = {readonly $quixosRef: string; readonly interfaceRevisionId: I; readonly fields: Fields};
|
||||||
|
const conformanceView: unique symbol;
|
||||||
|
export type ReactInterfaceContract<View> = {readonly interfaceRevisionId: string; readonly [conformanceView]: (view: View) => View};
|
||||||
|
export function defineReactInterfaceContract<View>(interfaceRevisionId: string, interfaces: unknown): ReactInterfaceContract<View>;
|
||||||
|
export function tryConform<View>(object: string | {readonly $quixosRef: string}, contract: ReactInterfaceContract<View>, options?: {signal?: AbortSignal}): Promise<View | undefined>;
|
||||||
|
export type ConformanceResult<View> = {status: "loading"} | {status: "absent"} | {status: "available"; view: View} | {status: "error"; error: Error};
|
||||||
|
export function useTryConform<View>(object: string | {readonly $quixosRef: string}, contract: ReactInterfaceContract<View>): ConformanceResult<View>;
|
||||||
export type ReactComponentHostProps<Action> = {
|
export type ReactComponentHostProps<Action> = {
|
||||||
onAction?: (action: Action) => void;
|
onAction?: (action: Action) => void;
|
||||||
fallback?: React.ReactNode;
|
fallback?: React.ReactNode;
|
||||||
|
|||||||
+22
-1
@@ -78,8 +78,29 @@ export function generateReactBindings(
|
|||||||
throw new Error("React component check must name a local module relative to generated bindings");
|
throw new Error("React component check must name a local module relative to generated bindings");
|
||||||
return `type Component${i} = CheckedComponent<${q(component.propsExport)}, typeof import(${q(component.module)})["default"]>;`;
|
return `type Component${i} = CheckedComponent<${q(component.propsExport)}, typeof import(${q(component.module)})["default"]>;`;
|
||||||
});
|
});
|
||||||
|
const contracts = schema.interfaces.map((iface) => {
|
||||||
|
const reference = type({
|
||||||
|
kind: "object-ref",
|
||||||
|
expectation: { kind: "interface", interfaceRevisionId: iface.revisionId },
|
||||||
|
});
|
||||||
|
const calls = iface.members.flatMap((member) =>
|
||||||
|
member.operations
|
||||||
|
.filter((operation) => operation.mode === "call" && operation.scope !== "class")
|
||||||
|
.map(
|
||||||
|
(operation) =>
|
||||||
|
`${q(`${member.displayName}.${operation.displayName}`)}: (${operation.inputType.kind === "builtin" && operation.inputType.name === "unit" ? "" : `input: ${type(operation.inputType)}`}) => Promise<${type(operation.outputType)}>`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return { iface, view: `${reference} & {call: {${calls.join(";")}}}` };
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
`// Generated from checked QX contracts. Do not edit.\nimport type {ObjectRef, InterfaceReference, ReadableField, WritableField} from "@quixos/web-studio-react-runtime";\n${declarations.join("\n")}\nexport type ReactResults = {${results.join(";\n")}};\n` +
|
`// Generated from checked QX contracts. Do not edit.\nimport {defineReactInterfaceContract} from "@quixos/web-studio-react-runtime";\nimport type {ObjectRef, InterfaceReference, ReadableField, WritableField} from "@quixos/web-studio-react-runtime";\n${declarations.join("\n")}\nexport type ReactResults = {${results.join(";\n")}};\n` +
|
||||||
|
`const contractsData = ${JSON.stringify(schema.interfaces)};\n` +
|
||||||
|
`export const reactContracts = {${contracts.map(({ iface, view }) => `${q(iface.revisionId)}: defineReactInterfaceContract<${view}>(${q(iface.revisionId)}, contractsData)`).join(",\n")}};\n` +
|
||||||
|
`export const reactInterfaces = {${contracts
|
||||||
|
.filter(({ iface }) => contracts.filter((other) => other.iface.displayName === iface.displayName).length === 1)
|
||||||
|
.map(({ iface }) => `${q(iface.displayName)}: reactContracts[${q(iface.revisionId)}]`)
|
||||||
|
.join(",\n")}};\n` +
|
||||||
(checks.length
|
(checks.length
|
||||||
? `type CheckedComponent<K extends keyof ReactResults, C extends (props: {camino: ReactResults[K]; render: any; dispatch: (action: any) => void}) => unknown> = C;\n${checks.join("\n")}\n`
|
? `type CheckedComponent<K extends keyof ReactResults, C extends (props: {camino: ReactResults[K]; render: any; dispatch: (action: any) => void}) => unknown> = C;\n${checks.join("\n")}\n`
|
||||||
: "")
|
: "")
|
||||||
|
|||||||
+79
-28
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file quixos/refs.proto.
|
* Describes the file quixos/refs.proto.
|
||||||
*/
|
*/
|
||||||
export const file_quixos_refs: GenFile = /*@__PURE__*/
|
export const file_quixos_refs: GenFile = /*@__PURE__*/
|
||||||
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zIkQKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIsQBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
|
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3MilgEKEkNvbmZvcm1hbmNlV2l0bmVzcxIRCglvYmplY3RfaWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgEIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBSABKAkiQgoQUGFja2FnZUV4cG9ydFJlZhIbChNwYWNrYWdlX3JldmlzaW9uX2lkGAEgASgJEhEKCWV4cG9ydF9pZBgCIAEoCSLEAQoSSW5qZWN0ZWREZXBlbmRlbmN5Eg8KB3BvcnRfaWQYASABKAkSFwoNc3RhdGVfc2xvdF9pZBgCIAEoCUgAEiYKBGVkZ2UYAyABKAsyFi5xdWl4b3MuRWRnZURlcGVuZGVuY3lIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAlIABIdChNjb25zdHJ1Y3Rvcl9hdG9tX2lkGAUgASgJSAASEQoJb2JqZWN0X2lkGAYgASgJQgkKB2JpbmRpbmciPQoORWRnZURlcGVuZGVuY3kSFAoMZWRnZV90eXBlX2lkGAEgASgJEhUKDXByb2plY3Rpb25faWQYAiABKAliBnByb3RvMw");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.CapabilityRef
|
* @generated from message quixos.CapabilityRef
|
||||||
@@ -25,6 +25,13 @@ export type CapabilityRef = Message<"quixos.CapabilityRef"> & {
|
|||||||
* @generated from field: string operation_id = 2;
|
* @generated from field: string operation_id = 2;
|
||||||
*/
|
*/
|
||||||
operationId: string;
|
operationId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional fence for a view acquired through TryConform. Not an authority grant.
|
||||||
|
*
|
||||||
|
* @generated from field: quixos.ConformanceWitness conformance = 3;
|
||||||
|
*/
|
||||||
|
conformance?: ConformanceWitness | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +41,43 @@ export type CapabilityRef = Message<"quixos.CapabilityRef"> & {
|
|||||||
export const CapabilityRefSchema: GenMessage<CapabilityRef> = /*@__PURE__*/
|
export const CapabilityRefSchema: GenMessage<CapabilityRef> = /*@__PURE__*/
|
||||||
messageDesc(file_quixos_refs, 0);
|
messageDesc(file_quixos_refs, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.ConformanceWitness
|
||||||
|
*/
|
||||||
|
export type ConformanceWitness = Message<"quixos.ConformanceWitness"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 1;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 2;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string conformance_id = 3;
|
||||||
|
*/
|
||||||
|
conformanceId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string workspace_revision_id = 4;
|
||||||
|
*/
|
||||||
|
workspaceRevisionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string workspace_epoch = 5;
|
||||||
|
*/
|
||||||
|
workspaceEpoch: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.ConformanceWitness.
|
||||||
|
* Use `create(ConformanceWitnessSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ConformanceWitnessSchema: GenMessage<ConformanceWitness> = /*@__PURE__*/
|
||||||
|
messageDesc(file_quixos_refs, 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.PackageExportRef
|
* @generated from message quixos.PackageExportRef
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +98,7 @@ export type PackageExportRef = Message<"quixos.PackageExportRef"> & {
|
|||||||
* Use `create(PackageExportRefSchema)` to create a new message.
|
* Use `create(PackageExportRefSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const PackageExportRefSchema: GenMessage<PackageExportRef> = /*@__PURE__*/
|
export const PackageExportRefSchema: GenMessage<PackageExportRef> = /*@__PURE__*/
|
||||||
messageDesc(file_quixos_refs, 1);
|
messageDesc(file_quixos_refs, 2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.InjectedDependency
|
* @generated from message quixos.InjectedDependency
|
||||||
@@ -108,7 +152,7 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
|
|||||||
* Use `create(InjectedDependencySchema)` to create a new message.
|
* Use `create(InjectedDependencySchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const InjectedDependencySchema: GenMessage<InjectedDependency> = /*@__PURE__*/
|
export const InjectedDependencySchema: GenMessage<InjectedDependency> = /*@__PURE__*/
|
||||||
messageDesc(file_quixos_refs, 2);
|
messageDesc(file_quixos_refs, 3);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.EdgeDependency
|
* @generated from message quixos.EdgeDependency
|
||||||
@@ -130,5 +174,5 @@ export type EdgeDependency = Message<"quixos.EdgeDependency"> & {
|
|||||||
* Use `create(EdgeDependencySchema)` to create a new message.
|
* Use `create(EdgeDependencySchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const EdgeDependencySchema: GenMessage<EdgeDependency> = /*@__PURE__*/
|
export const EdgeDependencySchema: GenMessage<EdgeDependency> = /*@__PURE__*/
|
||||||
messageDesc(file_quixos_refs, 3);
|
messageDesc(file_quixos_refs, 4);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,100 @@ import { genericImplementationType } from "../src/bindings/generics.js";
|
|||||||
import { compileWorkspaceRevision, runtimeContracts } from "../src/capability-model/index.js";
|
import { compileWorkspaceRevision, runtimeContracts } from "../src/capability-model/index.js";
|
||||||
import { generateAppliedClientContracts } from "../src/bindings/client.js";
|
import { generateAppliedClientContracts } from "../src/bindings/client.js";
|
||||||
const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) };
|
const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) };
|
||||||
|
test("runtime conformance descriptors preserve closed and universal collection item types", async (t) => {
|
||||||
|
const sdk = path.resolve("../quixos-instance/packages/camino-package-runtime/dist/index.js");
|
||||||
|
// The protocol is also published and tested as a standalone subtree. This
|
||||||
|
// integration uses the real SDK only when both projects are checked out.
|
||||||
|
if (
|
||||||
|
!(await fs.access(sdk).then(
|
||||||
|
() => true,
|
||||||
|
() => false,
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
t.skip("Real SDK integration requires the monorepo checkout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const collection = compileCapabilityResourceSource(
|
||||||
|
`interface Collection<object Item> id "collection" revision "collection@1" {
|
||||||
|
relation items id "items" : many-unique object Item {resolve id "get"; connect id "add";}
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
assert.ok(collection.ok && collection.resource.kind === "interface");
|
||||||
|
if (!collection.ok || collection.resource.kind !== "interface") throw Error("interface failed");
|
||||||
|
const pkg = compileCapabilityResourceSource(
|
||||||
|
`import interface Collection;
|
||||||
|
external atom Book id "book"; external atom Tool id "tool";
|
||||||
|
package P id "p" revision "p@1" {
|
||||||
|
operation both id "both" : record {book:atom-ref<Book>; tool:atom-ref<Tool>;} -> unit mode call receiver any requires {
|
||||||
|
interface books id "books" : Collection<atom Book>;
|
||||||
|
interface tools id "tools" : Collection<atom Tool>;
|
||||||
|
};
|
||||||
|
operation inspect<object Item> id "inspect" : ref<Item> -> unit mode call receiver any requires {
|
||||||
|
interface items id "items" : Collection<Item>;
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
{ source, environment: { interfaces: new Map([["Collection", collection.resource.revision]]) } },
|
||||||
|
);
|
||||||
|
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||||
|
if (!pkg.ok || pkg.resource.kind !== "package") throw Error("package failed");
|
||||||
|
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-conformance-types-"));
|
||||||
|
try {
|
||||||
|
const generated = generateTypeScriptBindings(
|
||||||
|
{
|
||||||
|
format: "quixos-bindings",
|
||||||
|
version: 1,
|
||||||
|
interfaces: pkg.resource.specializations ?? [],
|
||||||
|
interfaceTemplates: [collection.resource.revision],
|
||||||
|
packages: [pkg.resource.revision],
|
||||||
|
},
|
||||||
|
"p@1",
|
||||||
|
{ runtimeModule: sdk },
|
||||||
|
);
|
||||||
|
await fs.writeFile(path.join(directory, "bindings.mts"), generated);
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(directory, "consumer.mts"),
|
||||||
|
`import type {Implementation} from "./bindings.mjs";
|
||||||
|
const implementation: Implementation = {
|
||||||
|
both: async (context) => {
|
||||||
|
const view = await context.conform.tryConform(context.objectId, context.ports.books.contract);
|
||||||
|
if (view) {
|
||||||
|
await view["items.connect"](context.input.book);
|
||||||
|
// @ts-expect-error Tool is not Book
|
||||||
|
await view["items.connect"](context.input.tool);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
inspect: async (context) => {
|
||||||
|
const view = await context.conform.tryConform(context.objectId, context.ports.items.contract);
|
||||||
|
if (view) await view["items.connect"](context.input);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};`,
|
||||||
|
);
|
||||||
|
const compiler = path.join(
|
||||||
|
path.dirname(createRequire(import.meta.url).resolve("typescript/package.json")),
|
||||||
|
"bin/tsc",
|
||||||
|
);
|
||||||
|
const result = await promisify(execFile)(process.execPath, [
|
||||||
|
compiler,
|
||||||
|
"--ignoreConfig",
|
||||||
|
"--noEmit",
|
||||||
|
"--strict",
|
||||||
|
"--skipLibCheck",
|
||||||
|
"--target",
|
||||||
|
"es2023",
|
||||||
|
"--module",
|
||||||
|
"nodenext",
|
||||||
|
path.join(directory, "consumer.mts"),
|
||||||
|
]).catch((error) => {
|
||||||
|
throw Error(error.stdout + error.stderr);
|
||||||
|
});
|
||||||
|
assert.equal(result.stderr, "");
|
||||||
|
} finally {
|
||||||
|
await fs.rm(directory, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
test("generic port aliases retain their defining scope, including shadowed alias names", () => {
|
test("generic port aliases retain their defining scope, including shadowed alias names", () => {
|
||||||
const iface = compileCapabilityResourceSource(
|
const iface = compileCapabilityResourceSource(
|
||||||
'type Box<value V> = list<V>; interface Data<value V> id "data" revision "data@1" {value payload id "payload" : Box<V> {get id "payload:get";}}',
|
'type Box<value V> = list<V>; interface Data<value V> id "data" revision "data@1" {value payload id "payload" : Box<V> {get id "payload:get";}}',
|
||||||
@@ -108,7 +202,7 @@ test("generated universal implementations compile and cannot assume a concrete v
|
|||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const compiler = path.join(path.dirname(require.resolve("typescript/package.json")), "bin/tsc");
|
const compiler = path.join(path.dirname(require.resolve("typescript/package.json")), "bin/tsc");
|
||||||
const run = promisify(execFile);
|
const run = promisify(execFile);
|
||||||
const preamble = `type QxObjectRef<T extends string>={readonly identity:T}; type QxContextLifecycle<C>={signal?:AbortSignal}; type Handler=${signature};\n`;
|
const preamble = `type QxObjectRef<T extends string>={readonly identity:T}; type QxConformer={}; type QxContextLifecycle<C>={signal?:AbortSignal}; type Handler=${signature};\n`;
|
||||||
try {
|
try {
|
||||||
const file = path.join(directory, "generic.ts");
|
const file = path.join(directory, "generic.ts");
|
||||||
await fs.writeFile(file, preamble + `const handler:Handler=({input})=>input;`);
|
await fs.writeFile(file, preamble + `const handler:Handler=({input})=>input;`);
|
||||||
|
|||||||
@@ -54,8 +54,18 @@ test("React bindings preserve read-only, writable and nested reference contracts
|
|||||||
);
|
);
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
path.join(root, "consumer.ts"),
|
path.join(root, "consumer.ts"),
|
||||||
`import {useLiveField, type ReadableField, type WritableField} from "@quixos/web-studio-react-runtime";
|
`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} from "./react-props.gen.js";
|
||||||
|
async function lookup() {
|
||||||
|
const view = await tryConform("object", reactInterfaces.Fields);
|
||||||
|
if (!view) return;
|
||||||
|
await view.call["title.set"]("works");
|
||||||
|
// @ts-expect-error exact checked setter input
|
||||||
|
await view.call["title.set"](123);
|
||||||
|
// @ts-expect-error no invented operation
|
||||||
|
await view.call["summary.set"]("no setter");
|
||||||
|
}
|
||||||
declare const props: ReactResults["props"];
|
declare const props: ReactResults["props"];
|
||||||
const [title, setTitle] = useLiveField(props.fields.fields.title);
|
const [title, setTitle] = useLiveField(props.fields.fields.title);
|
||||||
setTitle("new");
|
setTitle("new");
|
||||||
|
|||||||
Reference in New Issue
Block a user