Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 446fd4e36e | |||
| dac9c48856 | |||
| d0dca13ff9 | |||
| f42d70a4fe | |||
| 424ebec92e | |||
| b86dbe2dfa | |||
| 1982c6fff1 | |||
| 13d2d0b1ee | |||
| c1330ae8e3 | |||
| 7e69e675ba | |||
| 99101ad206 | |||
| 169c0cdc37 | |||
| 47efd9659d | |||
| cd2b04dc8c | |||
| fc838413c5 | |||
| 5bb8ee876d | |||
| 0e183516c0 | |||
| 2319b1f637 | |||
| 0854822923 | |||
| 0d3c013c07 | |||
| bee4452852 | |||
| d3d050d9cb | |||
| e8a175504a |
@@ -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": "22bb3d02264980d74de65c34bbbcb81764dc65c0",
|
"sourceCommit": "8e42108f9ac903cca5f613fefbff3c147c775b81",
|
||||||
"sourcePath": "quixos-instance/packages/camino-package-runtime",
|
"sourcePath": "quixos-instance/packages/camino-package-runtime",
|
||||||
"exportName": "camino-package-runtime",
|
"exportName": "camino-package-runtime",
|
||||||
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
|
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
|
||||||
|
|||||||
Vendored
+20
-1
@@ -10,7 +10,6 @@ export type MessageBinding<T> = {
|
|||||||
decode(value: Value): T;
|
decode(value: Value): T;
|
||||||
};
|
};
|
||||||
export type QxLiveValue = ReturnType<typeof liveValue>;
|
export type QxLiveValue = ReturnType<typeof liveValue>;
|
||||||
export declare const opaqueReactPropsBinding: MessageBinding<Record<string, unknown>>;
|
|
||||||
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
|
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
|
||||||
export type QxHandler<C, O> = (context: C) => O | Promise<O>;
|
export type QxHandler<C, O> = (context: C) => O | Promise<O>;
|
||||||
export type QxDerived<C, O> = {
|
export type QxDerived<C, O> = {
|
||||||
@@ -26,6 +25,17 @@ export type QxContextLifecycle<C> = {
|
|||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
openSession?: () => Promise<QxSession<C>>;
|
openSession?: () => Promise<QxSession<C>>;
|
||||||
};
|
};
|
||||||
|
declare const contractView: unique symbol;
|
||||||
|
/** Generated exact closed contract. A descriptor is type evidence, never authority. */
|
||||||
|
export type QxInterfaceContract<View> = {
|
||||||
|
readonly interfaceRevisionId: string;
|
||||||
|
readonly operations: Record<string, QxOperationSpec>;
|
||||||
|
readonly [contractView]: (value: View) => View;
|
||||||
|
};
|
||||||
|
export declare const defineQxInterfaceContract: <View>(interfaceRevisionId: string, operations: Record<string, QxOperationSpec>) => QxInterfaceContract<View>;
|
||||||
|
export type QxConformer = {
|
||||||
|
tryConform<View>(object: import("./references.js").QxObjectRef, contract: QxInterfaceContract<View>): Promise<View | undefined>;
|
||||||
|
};
|
||||||
export declare const qxDerived: <C, O>(get: QxHandler<C, O>) => QxDerived<C, O>;
|
export declare const qxDerived: <C, O>(get: QxHandler<C, O>) => QxDerived<C, O>;
|
||||||
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
|
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
|
||||||
export type QxValueType = {
|
export type QxValueType = {
|
||||||
@@ -53,6 +63,13 @@ export type QxOperationSpec = {
|
|||||||
outputType: QxValueType;
|
outputType: QxValueType;
|
||||||
};
|
};
|
||||||
export type QxPortSpec = {
|
export type QxPortSpec = {
|
||||||
|
kind: "query";
|
||||||
|
id: string;
|
||||||
|
definitionDigest: string;
|
||||||
|
variables: QxValueType;
|
||||||
|
output: QxValueType;
|
||||||
|
watch: boolean;
|
||||||
|
} | {
|
||||||
kind: "state";
|
kind: "state";
|
||||||
id: string;
|
id: string;
|
||||||
valueType: QxValueType;
|
valueType: QxValueType;
|
||||||
@@ -64,6 +81,7 @@ export type QxPortSpec = {
|
|||||||
} | {
|
} | {
|
||||||
kind: "interface";
|
kind: "interface";
|
||||||
id: string;
|
id: string;
|
||||||
|
interfaceRevisionId: string;
|
||||||
operations: Record<string, QxOperationSpec>;
|
operations: Record<string, QxOperationSpec>;
|
||||||
} | {
|
} | {
|
||||||
kind: "constructor";
|
kind: "constructor";
|
||||||
@@ -71,6 +89,7 @@ export type QxPortSpec = {
|
|||||||
inputType: QxValueType;
|
inputType: QxValueType;
|
||||||
};
|
};
|
||||||
export type QxHandlerSpec = {
|
export type QxHandlerSpec = {
|
||||||
|
receiver?: "none";
|
||||||
inputType: QxValueType;
|
inputType: QxValueType;
|
||||||
outputType: QxValueType;
|
outputType: QxValueType;
|
||||||
eventType?: QxValueType;
|
eventType?: QxValueType;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
|
||||||
Vendored
+115
-42
@@ -1,23 +1,22 @@
|
|||||||
import { create } from "@bufbuild/protobuf";
|
import { create } from "@bufbuild/protobuf";
|
||||||
import { ValueSchema, ObjectValueSchema } from "./camino/api_pb.js";
|
import { ValueSchema, ObjectValueSchema } from "./camino/api_pb.js";
|
||||||
import { derived, jsToProtoValue, liveValue, protoValueToJs } from "./index.js";
|
import { derived, jsToProtoValue, liveValue, protoValueToJs, } from "./index.js";
|
||||||
import { assertReferenceFree, referenceToWire } from "./references.js";
|
import { assertReferenceFree, referenceToWire } from "./references.js";
|
||||||
const reactPropsDescriptor = "org.quixos.web-studio.ReactProps";
|
import { decodeQuerySnapshot } from "./queries.js";
|
||||||
// Explicit temporary props exception, matching orch's RPC contract. Field shapes
|
export const defineQxInterfaceContract = (interfaceRevisionId, operations) => Object.freeze({ interfaceRevisionId, operations });
|
||||||
// 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 });
|
export const qxDerived = (get) => ({ kind: "derived", get });
|
||||||
|
const bindInterfaceView = (target, contract, messages) => ({
|
||||||
|
objectId: target.objectId,
|
||||||
|
contract,
|
||||||
|
live: Object.fromEntries(Object.entries(contract.operations).map(([name, operation]) => [
|
||||||
|
name,
|
||||||
|
(input) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
|
||||||
|
])),
|
||||||
|
...Object.fromEntries(Object.entries(contract.operations).map(([name, operation]) => [
|
||||||
|
name,
|
||||||
|
async (input) => decodeQxValue(operation.outputType, (await target.live(operation.id, inputFields(operation.inputType, input, messages))).$quixosValue, messages),
|
||||||
|
])),
|
||||||
|
});
|
||||||
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
|
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
|
||||||
export const decodeQxValue = (type, value, messages) => {
|
export const decodeQxValue = (type, value, messages) => {
|
||||||
if (type.kind === "builtin" && type.name === "unit")
|
if (type.kind === "builtin" && type.name === "unit")
|
||||||
@@ -42,10 +41,8 @@ export const decodeQxValue = (type, value, messages) => {
|
|||||||
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
|
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
|
||||||
}
|
}
|
||||||
if (type.kind === "message") {
|
if (type.kind === "message") {
|
||||||
if (type.descriptorId !== reactPropsDescriptor)
|
|
||||||
assertReferenceFree(protoValueToJs(value));
|
assertReferenceFree(protoValueToJs(value));
|
||||||
const decoded = requireMessage(messages, type.descriptorId).decode(value);
|
const decoded = requireMessage(messages, type.descriptorId).decode(value);
|
||||||
if (type.descriptorId !== reactPropsDescriptor)
|
|
||||||
assertReferenceFree(decoded);
|
assertReferenceFree(decoded);
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
@@ -99,11 +96,9 @@ export const encodeQxValue = (type, value, messages) => {
|
|||||||
referenceToWire(value);
|
referenceToWire(value);
|
||||||
return jsToProtoValue(value);
|
return jsToProtoValue(value);
|
||||||
}
|
}
|
||||||
if (type.kind !== "message" || type.descriptorId !== reactPropsDescriptor)
|
|
||||||
assertReferenceFree(value);
|
assertReferenceFree(value);
|
||||||
if (type.kind === "message") {
|
if (type.kind === "message") {
|
||||||
const encoded = requireMessage(messages, type.descriptorId).encode(value);
|
const encoded = requireMessage(messages, type.descriptorId).encode(value);
|
||||||
if (type.descriptorId !== reactPropsDescriptor)
|
|
||||||
assertReferenceFree(protoValueToJs(encoded));
|
assertReferenceFree(protoValueToJs(encoded));
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
@@ -111,8 +106,9 @@ export const encodeQxValue = (type, value, messages) => {
|
|||||||
};
|
};
|
||||||
const inputValue = (context, type) => {
|
const inputValue = (context, type) => {
|
||||||
if (type.kind === "message" || type.kind === "record")
|
if (type.kind === "message" || type.kind === "record")
|
||||||
return create(ValueSchema, { kind: { case: "objectValue",
|
return create(ValueSchema, {
|
||||||
value: create(ObjectValueSchema, { fields: context.inputProto }) } });
|
kind: { case: "objectValue", value: create(ObjectValueSchema, { fields: context.inputProto }) },
|
||||||
|
});
|
||||||
return context.inputProto.value;
|
return context.inputProto.value;
|
||||||
};
|
};
|
||||||
const inputFields = (type, value, messages) => {
|
const inputFields = (type, value, messages) => {
|
||||||
@@ -133,37 +129,114 @@ export const bindQxHandler = (spec, handler, messages) => {
|
|||||||
switch (port.kind) {
|
switch (port.kind) {
|
||||||
case "state": {
|
case "state": {
|
||||||
const state = raw.state(port.id);
|
const state = raw.state(port.id);
|
||||||
return [name, {
|
return [
|
||||||
...(port.primitives.includes("read") ? { get: async () => decodeQxValue(port.valueType, (await state.live()).$quixosValue, messages), live: () => state.live() } : {}),
|
name,
|
||||||
...(port.primitives.includes("write") ? { set: async (value) => state.set(liveValue(encodeQxValue(port.valueType, value, 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))),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
case "edge": {
|
case "edge": {
|
||||||
const edge = raw.edge(port.id);
|
const edge = raw.edge(port.id);
|
||||||
return [name, { ...Object.fromEntries(port.primitives.map((primitive) => [primitive, edge[primitive]])),
|
return [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
...Object.fromEntries(port.primitives.map((primitive) => [
|
||||||
|
primitive,
|
||||||
|
edge[primitive],
|
||||||
|
])),
|
||||||
...(port.primitives.includes("resolve") ? { collection: edge.collection } : {}),
|
...(port.primitives.includes("resolve") ? { collection: edge.collection } : {}),
|
||||||
...(port.primitives.includes("resolve") && port.primitives.includes("connect") && port.primitives.includes("disconnect") ? { replace: edge.replace } : {}) }];
|
...(port.primitives.includes("resolve") &&
|
||||||
|
port.primitives.includes("connect") &&
|
||||||
|
port.primitives.includes("disconnect")
|
||||||
|
? { replace: edge.replace }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
case "interface": {
|
case "interface": {
|
||||||
const target = raw.interface(port.id);
|
const target = raw.interface(port.id);
|
||||||
return [name, { objectId: target.objectId,
|
return [
|
||||||
live: Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
|
name,
|
||||||
(input) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
|
bindInterfaceView(target, defineQxInterfaceContract(port.interfaceRevisionId, port.operations), 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)) }];
|
case "query": {
|
||||||
|
const query = raw.query(port.id);
|
||||||
|
const variablesToWire = (variables) => {
|
||||||
|
const value = encodeQxValue(port.variables, variables, messages);
|
||||||
|
if (value.kind.case !== "objectValue")
|
||||||
|
throw new Error("QUERY_VARIABLE_INVALID");
|
||||||
|
return value.kind.value.fields;
|
||||||
|
};
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
async execute(variables) {
|
||||||
|
const response = await query.execute(variablesToWire(variables), port.definitionDigest);
|
||||||
|
if (response.pending.length || response.errors.length)
|
||||||
|
throw new Error("QUERY_INCOMPLETE");
|
||||||
|
return decodeQxValue(port.output, response.value, messages);
|
||||||
|
},
|
||||||
|
...(port.watch
|
||||||
|
? {
|
||||||
|
async *watch(variables, signal) {
|
||||||
|
for await (const event of query.watch(variablesToWire(variables), signal, port.definitionDigest)) {
|
||||||
|
if (!event.snapshot)
|
||||||
|
throw new Error("QUERY_SNAPSHOT_MISSING");
|
||||||
|
yield decodeQuerySnapshot(event.snapshot, port.output, event.runId, event.sequence);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "constructor":
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
construct: (input) => raw.constructor(port.id).construct(inputFields(port.inputType, input, messages)),
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return { objectId: raw.objectId, signal: raw.signal,
|
return {
|
||||||
...(raw.openSession ? { openSession: async () => {
|
conform: {
|
||||||
|
async tryConform(object, contract) {
|
||||||
|
const target = await raw.tryConform(object, contract.interfaceRevisionId);
|
||||||
|
return target
|
||||||
|
? bindInterfaceView(target, contract, messages)
|
||||||
|
: undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(spec.receiver === "none" ? {} : { objectId: raw.objectId }),
|
||||||
|
signal: raw.signal,
|
||||||
|
...(spec.receiver !== "none" && raw.openSession
|
||||||
|
? {
|
||||||
|
openSession: async () => {
|
||||||
const session = await raw.openSession();
|
const session = await raw.openSession();
|
||||||
return { id: session.id, close: () => session.close(),
|
return {
|
||||||
run: (work) => session.run((next) => work(bindContext(next))) };
|
id: session.id,
|
||||||
} } : {}),
|
close: () => session.close(),
|
||||||
input: decodeQxValue(spec.inputType, inputValue(raw, spec.inputType), messages), ports };
|
run: (work) => session.run((next) => work(bindContext(next))),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
input: decodeQxValue(spec.inputType, inputValue(raw, spec.inputType), messages),
|
||||||
|
ports,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
const execute = async (raw) => {
|
const execute = async (raw) => {
|
||||||
const context = bindContext(raw);
|
const context = bindContext(raw);
|
||||||
|
|||||||
Vendored
+504
-1
@@ -1,5 +1,5 @@
|
|||||||
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
import type { PersistencePlan } from "./schema_pb.js";
|
import type { PersistencePlan, QueryRelationalPlan, QuerySelection } from "./schema_pb.js";
|
||||||
import type { Message } from "@bufbuild/protobuf";
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
/**
|
/**
|
||||||
* Describes the file camino/api.proto.
|
* Describes the file camino/api.proto.
|
||||||
@@ -826,10 +826,513 @@ export type CaminoOp = Message<"camino.CaminoOp"> & {
|
|||||||
* Use `create(CaminoOpSchema)` to create a new message.
|
* Use `create(CaminoOpSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export declare const CaminoOpSchema: GenMessage<CaminoOp>;
|
export declare const CaminoOpSchema: GenMessage<CaminoOp>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryRequest
|
||||||
|
*/
|
||||||
|
export type QueryRequest = Message<"camino.QueryRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string query_id = 1;
|
||||||
|
*/
|
||||||
|
queryId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 2;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.Value> variables = 3;
|
||||||
|
*/
|
||||||
|
variables: {
|
||||||
|
[key: string]: Value;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Typed callers require this exact checked definition. Administrative/raw
|
||||||
|
* callers may omit it to explicitly select the currently installed definition.
|
||||||
|
*
|
||||||
|
* @generated from field: string expected_definition_digest = 4;
|
||||||
|
*/
|
||||||
|
expectedDefinitionDigest: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryRequest.
|
||||||
|
* Use `create(QueryRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryRequestSchema: GenMessage<QueryRequest>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryPathPart
|
||||||
|
*/
|
||||||
|
export type QueryPathPart = Message<"camino.QueryPathPart"> & {
|
||||||
|
/**
|
||||||
|
* @generated from oneof camino.QueryPathPart.part
|
||||||
|
*/
|
||||||
|
part: {
|
||||||
|
/**
|
||||||
|
* @generated from field: string field = 1;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
case: "field";
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 index = 2;
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
case: "index";
|
||||||
|
} | {
|
||||||
|
case: undefined;
|
||||||
|
value?: undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryPathPart.
|
||||||
|
* Use `create(QueryPathPartSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryPathPartSchema: GenMessage<QueryPathPart>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryPendingField
|
||||||
|
*/
|
||||||
|
export type QueryPendingField = Message<"camino.QueryPendingField"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryPathPart path = 1;
|
||||||
|
*/
|
||||||
|
path: QueryPathPart[];
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 2;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 3;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string member_id = 4;
|
||||||
|
*/
|
||||||
|
memberId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string operation_id = 5;
|
||||||
|
*/
|
||||||
|
operationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string value_type_json = 6;
|
||||||
|
*/
|
||||||
|
valueTypeJson: string;
|
||||||
|
/**
|
||||||
|
* Internal residual facts are separate from the authored result projection.
|
||||||
|
*
|
||||||
|
* @generated from field: optional uint32 residual_window = 7;
|
||||||
|
*/
|
||||||
|
residualWindow?: number | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 residual_row = 8;
|
||||||
|
*/
|
||||||
|
residualRow: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: string residual_field = 9;
|
||||||
|
*/
|
||||||
|
residualField: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: optional uint32 relational_capture = 10;
|
||||||
|
*/
|
||||||
|
relationalCapture?: number | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 captured_object = 11;
|
||||||
|
*/
|
||||||
|
capturedObject: number;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryPendingField.
|
||||||
|
* Use `create(QueryPendingFieldSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryPendingFieldSchema: GenMessage<QueryPendingField>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryStats
|
||||||
|
*/
|
||||||
|
export type QueryStats = Message<"camino.QueryStats"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 sql_count = 1;
|
||||||
|
*/
|
||||||
|
sqlCount: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: double sql_ms = 2;
|
||||||
|
*/
|
||||||
|
sqlMs: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 rpc_count = 3;
|
||||||
|
*/
|
||||||
|
rpcCount: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: double rpc_ms = 4;
|
||||||
|
*/
|
||||||
|
rpcMs: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 result_bytes = 5;
|
||||||
|
*/
|
||||||
|
resultBytes: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: double preparation_ms = 6;
|
||||||
|
*/
|
||||||
|
preparationMs: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: double total_ms = 7;
|
||||||
|
*/
|
||||||
|
totalMs: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 relational_stages = 8;
|
||||||
|
*/
|
||||||
|
relationalStages: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 captured_candidates = 9;
|
||||||
|
*/
|
||||||
|
capturedCandidates: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: double relational_ms = 10;
|
||||||
|
*/
|
||||||
|
relationalMs: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: double residual_ms = 11;
|
||||||
|
*/
|
||||||
|
residualMs: number;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryStats.
|
||||||
|
* Use `create(QueryStatsSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryStatsSchema: GenMessage<QueryStats>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryResponse
|
||||||
|
*/
|
||||||
|
export type QueryResponse = Message<"camino.QueryResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.Value value = 1;
|
||||||
|
*/
|
||||||
|
value?: Value | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: string data_version = 2;
|
||||||
|
*/
|
||||||
|
dataVersion: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string binding_digest = 3;
|
||||||
|
*/
|
||||||
|
bindingDigest: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryPendingField pending = 4;
|
||||||
|
*/
|
||||||
|
pending: QueryPendingField[];
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryStats stats = 5;
|
||||||
|
*/
|
||||||
|
stats?: QueryStats | undefined;
|
||||||
|
/**
|
||||||
|
* Redeemable only through the host's private control socket, not an RPC grant.
|
||||||
|
*
|
||||||
|
* @generated from field: string preparation_token = 6;
|
||||||
|
*/
|
||||||
|
preparationToken: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryFieldFailure errors = 7;
|
||||||
|
*/
|
||||||
|
errors: QueryFieldFailure[];
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryResidualWindow residual_windows = 8;
|
||||||
|
*/
|
||||||
|
residualWindows: QueryResidualWindow[];
|
||||||
|
/**
|
||||||
|
* Native reads share one database snapshot; package enrichment does not.
|
||||||
|
*
|
||||||
|
* native-snapshot | mixed
|
||||||
|
*
|
||||||
|
* @generated from field: string consistency = 9;
|
||||||
|
*/
|
||||||
|
consistency: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryRelationalCapture relational_captures = 10;
|
||||||
|
*/
|
||||||
|
relationalCaptures: QueryRelationalCapture[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryResponse.
|
||||||
|
* Use `create(QueryResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryResponseSchema: GenMessage<QueryResponse>;
|
||||||
|
/**
|
||||||
|
* Private coordinator input, removed before publishing a result. Memberships
|
||||||
|
* and native facts share one snapshot; package reads are sampled afterwards.
|
||||||
|
*
|
||||||
|
* @generated from message camino.QueryCapturedMember
|
||||||
|
*/
|
||||||
|
export type QueryCapturedMember = Message<"camino.QueryCapturedMember"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 1;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string entry_id = 2;
|
||||||
|
*/
|
||||||
|
entryId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.Value map_key = 3;
|
||||||
|
*/
|
||||||
|
mapKey?: Value | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryCapturedMember.
|
||||||
|
* Use `create(QueryCapturedMemberSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryCapturedMemberSchema: GenMessage<QueryCapturedMember>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryCapturedMembers
|
||||||
|
*/
|
||||||
|
export type QueryCapturedMembers = Message<"camino.QueryCapturedMembers"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryCapturedMember entries = 1;
|
||||||
|
*/
|
||||||
|
entries: QueryCapturedMember[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryCapturedMembers.
|
||||||
|
* Use `create(QueryCapturedMembersSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryCapturedMembersSchema: GenMessage<QueryCapturedMembers>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryCapturedObject
|
||||||
|
*/
|
||||||
|
export type QueryCapturedObject = Message<"camino.QueryCapturedObject"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 1;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.Value> fields = 2;
|
||||||
|
*/
|
||||||
|
fields: {
|
||||||
|
[key: string]: Value;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, string> field_types = 3;
|
||||||
|
*/
|
||||||
|
fieldTypes: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.QueryCapturedMembers> relationships = 4;
|
||||||
|
*/
|
||||||
|
relationships: {
|
||||||
|
[key: string]: QueryCapturedMembers;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryCapturedObject.
|
||||||
|
* Use `create(QueryCapturedObjectSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryCapturedObjectSchema: GenMessage<QueryCapturedObject>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryRelationalCapture
|
||||||
|
*/
|
||||||
|
export type QueryRelationalCapture = Message<"camino.QueryRelationalCapture"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryPathPart path = 1;
|
||||||
|
*/
|
||||||
|
path: QueryPathPart[];
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryRelationalPlan plan = 2;
|
||||||
|
*/
|
||||||
|
plan?: QueryRelationalPlan | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: string root_object_id = 3;
|
||||||
|
*/
|
||||||
|
rootObjectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryCapturedObject objects = 4;
|
||||||
|
*/
|
||||||
|
objects: QueryCapturedObject[];
|
||||||
|
/**
|
||||||
|
* @generated from field: string variables_json = 5;
|
||||||
|
*/
|
||||||
|
variablesJson: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 row_limit = 6;
|
||||||
|
*/
|
||||||
|
rowLimit: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 candidate_limit = 7;
|
||||||
|
*/
|
||||||
|
candidateLimit: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: optional uint32 residual_window = 8;
|
||||||
|
*/
|
||||||
|
residualWindow?: number | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated string result_path = 9;
|
||||||
|
*/
|
||||||
|
resultPath: string[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryRelationalCapture.
|
||||||
|
* Use `create(QueryRelationalCaptureSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryRelationalCaptureSchema: GenMessage<QueryRelationalCapture>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryResidualRow
|
||||||
|
*/
|
||||||
|
export type QueryResidualRow = Message<"camino.QueryResidualRow"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string entry_id = 1;
|
||||||
|
*/
|
||||||
|
entryId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.Value> fields = 2;
|
||||||
|
*/
|
||||||
|
fields: {
|
||||||
|
[key: string]: Value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryResidualRow.
|
||||||
|
* Use `create(QueryResidualRowSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryResidualRowSchema: GenMessage<QueryResidualRow>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryResidualOrder
|
||||||
|
*/
|
||||||
|
export type QueryResidualOrder = Message<"camino.QueryResidualOrder"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string field = 1;
|
||||||
|
*/
|
||||||
|
field: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: bool descending = 2;
|
||||||
|
*/
|
||||||
|
descending: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryResidualOrder.
|
||||||
|
* Use `create(QueryResidualOrderSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryResidualOrderSchema: GenMessage<QueryResidualOrder>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryResidualWindow
|
||||||
|
*/
|
||||||
|
export type QueryResidualWindow = Message<"camino.QueryResidualWindow"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryPathPart path = 1;
|
||||||
|
*/
|
||||||
|
path: QueryPathPart[];
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryResidualRow rows = 2;
|
||||||
|
*/
|
||||||
|
rows: QueryResidualRow[];
|
||||||
|
/**
|
||||||
|
* @generated from field: string predicate_json = 3;
|
||||||
|
*/
|
||||||
|
predicateJson: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryResidualOrder order = 4;
|
||||||
|
*/
|
||||||
|
order: QueryResidualOrder[];
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 limit = 5;
|
||||||
|
*/
|
||||||
|
limit: number;
|
||||||
|
/**
|
||||||
|
* @generated from field: bool bounded_all = 6;
|
||||||
|
*/
|
||||||
|
boundedAll: boolean;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QuerySelection selection = 7;
|
||||||
|
*/
|
||||||
|
selection: QuerySelection[];
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, string> field_types = 8;
|
||||||
|
*/
|
||||||
|
fieldTypes: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from field: bool relational = 9;
|
||||||
|
*/
|
||||||
|
relational: boolean;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated string matched_entries = 10;
|
||||||
|
*/
|
||||||
|
matchedEntries: string[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryResidualWindow.
|
||||||
|
* Use `create(QueryResidualWindowSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryResidualWindowSchema: GenMessage<QueryResidualWindow>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryFieldFailure
|
||||||
|
*/
|
||||||
|
export type QueryFieldFailure = Message<"camino.QueryFieldFailure"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryPathPart path = 1;
|
||||||
|
*/
|
||||||
|
path: QueryPathPart[];
|
||||||
|
/**
|
||||||
|
* @generated from field: string error = 2;
|
||||||
|
*/
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryFieldFailure.
|
||||||
|
* Use `create(QueryFieldFailureSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryFieldFailureSchema: GenMessage<QueryFieldFailure>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryChangesRequest
|
||||||
|
*/
|
||||||
|
export type QueryChangesRequest = Message<"camino.QueryChangesRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string query_id = 1;
|
||||||
|
*/
|
||||||
|
queryId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 2;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string data_version = 3;
|
||||||
|
*/
|
||||||
|
dataVersion: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryChangesRequest.
|
||||||
|
* Use `create(QueryChangesRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryChangesRequestSchema: GenMessage<QueryChangesRequest>;
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryChangesResponse
|
||||||
|
*/
|
||||||
|
export type QueryChangesResponse = Message<"camino.QueryChangesResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: bool changed = 1;
|
||||||
|
*/
|
||||||
|
changed: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryChangesResponse.
|
||||||
|
* Use `create(QueryChangesResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryChangesResponseSchema: GenMessage<QueryChangesResponse>;
|
||||||
/**
|
/**
|
||||||
* @generated from service camino.CaminoService
|
* @generated from service camino.CaminoService
|
||||||
*/
|
*/
|
||||||
export declare const CaminoService: GenService<{
|
export declare const CaminoService: GenService<{
|
||||||
|
/**
|
||||||
|
* @generated from rpc camino.CaminoService.ExecuteQuery
|
||||||
|
*/
|
||||||
|
executeQuery: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof QueryRequestSchema;
|
||||||
|
output: typeof QueryResponseSchema;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from rpc camino.CaminoService.QueryChanges
|
||||||
|
*/
|
||||||
|
queryChanges: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof QueryChangesRequestSchema;
|
||||||
|
output: typeof QueryChangesResponseSchema;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* @generated from rpc camino.CaminoService.InstallPersistencePlan
|
* @generated from rpc camino.CaminoService.InstallPersistencePlan
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+76
-1
File diff suppressed because one or more lines are too long
Vendored
+1044
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+261
-1
File diff suppressed because one or more lines are too long
Vendored
+11
-3
@@ -1,10 +1,11 @@
|
|||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
import { type QxObjectRef } from "./references.js";
|
import { type QxObjectRef } from "./references.js";
|
||||||
export * from "./bindings.js";
|
export * from "./bindings.js";
|
||||||
|
export * from "./queries.js";
|
||||||
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
||||||
export { createMigrationContext, migrationObjectId, serveMigration, type MigrationContext, type MigrationInput, type MigrationOutput, type MigrationEdge } from "./migration.js";
|
export { createMigrationContext, migrationObjectId, serveMigration, type MigrationContext, type MigrationInput, type MigrationOutput, type MigrationEdge, } from "./migration.js";
|
||||||
import { type Client, type ConnectRouter } from "@connectrpc/connect";
|
import { type Client, type ConnectRouter } from "@connectrpc/connect";
|
||||||
import { CaminoService, type Value } from "./camino/api_pb.js";
|
import { CaminoService, type Value, type QueryResponse } from "./camino/api_pb.js";
|
||||||
import { OrchestratorRuntime } from "./quixos/orch_pb.js";
|
import { OrchestratorRuntime } from "./quixos/orch_pb.js";
|
||||||
export type CaminoClient = Client<typeof CaminoService>;
|
export type CaminoClient = Client<typeof CaminoService>;
|
||||||
export type OrchClient = Client<typeof OrchestratorRuntime>;
|
export type OrchClient = Client<typeof OrchestratorRuntime>;
|
||||||
@@ -61,8 +62,14 @@ export type ConstructorPort = {
|
|||||||
atomId: string;
|
atomId: string;
|
||||||
construct(input?: Record<string, unknown>): Promise<QxObjectRef>;
|
construct(input?: Record<string, unknown>): Promise<QxObjectRef>;
|
||||||
};
|
};
|
||||||
export type RuntimePort = StatePort | EdgePort | InterfacePort | ConstructorPort;
|
export type QueryPort = {
|
||||||
|
queryId: string;
|
||||||
|
execute(variables: Record<string, Value>, expectedDefinitionDigest?: string): Promise<QueryResponse>;
|
||||||
|
watch(variables: Record<string, Value>, signal: AbortSignal, expectedDefinitionDigest?: string): AsyncIterable<import("./quixos/orch_pb.js").QueryEvent>;
|
||||||
|
};
|
||||||
|
export type RuntimePort = StatePort | EdgePort | InterfacePort | ConstructorPort | QueryPort;
|
||||||
export type RuntimeContext = {
|
export type RuntimeContext = {
|
||||||
|
tryConform(object: QxObjectRef, interfaceRevisionId: string): Promise<InterfacePort | undefined>;
|
||||||
/** Cooperative cancellation. Completion is acknowledged only after the handler returns. */
|
/** Cooperative cancellation. Completion is acknowledged only after the handler returns. */
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
openSession?: () => Promise<RuntimeSession>;
|
openSession?: () => Promise<RuntimeSession>;
|
||||||
@@ -74,6 +81,7 @@ export type RuntimeContext = {
|
|||||||
edge(portId: string): EdgePort;
|
edge(portId: string): EdgePort;
|
||||||
interface(portId: string): InterfacePort;
|
interface(portId: string): InterfacePort;
|
||||||
constructor(portId: string): ConstructorPort;
|
constructor(portId: string): ConstructorPort;
|
||||||
|
query(portId: string): QueryPort;
|
||||||
};
|
};
|
||||||
export type RuntimeSession = {
|
export type RuntimeSession = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||||
Vendored
+187
-92
@@ -1,12 +1,13 @@
|
|||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { createInvocationRegistry } from "./invocations.js";
|
import { createInvocationRegistry } from "./invocations.js";
|
||||||
import { isObjectReference, referenceFromWire, referenceToWire, assertReferenceFree } from "./references.js";
|
import { isObjectReference, referenceFromWire, referenceToWire, assertReferenceFree, } from "./references.js";
|
||||||
export * from "./bindings.js";
|
export * from "./bindings.js";
|
||||||
|
export * from "./queries.js";
|
||||||
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
||||||
import { AsyncLocalStorage } from "node:async_hooks";
|
import { AsyncLocalStorage } from "node:async_hooks";
|
||||||
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
||||||
export { createMigrationContext, migrationObjectId, serveMigration } from "./migration.js";
|
export { createMigrationContext, migrationObjectId, serveMigration, } from "./migration.js";
|
||||||
import { create, equals } from "@bufbuild/protobuf";
|
import { create, equals } from "@bufbuild/protobuf";
|
||||||
import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
||||||
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
|
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
|
||||||
@@ -27,13 +28,20 @@ const recordDependency = async (dependency) => {
|
|||||||
const bytesToBase64 = (value) => Buffer.from(value).toString("base64");
|
const bytesToBase64 = (value) => Buffer.from(value).toString("base64");
|
||||||
const base64ToBytes = (value) => Buffer.from(value, "base64");
|
const base64ToBytes = (value) => Buffer.from(value, "base64");
|
||||||
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||||
const isWrappedValue = (value) => isRecord(value) && "$quixosValue" in value &&
|
const isWrappedValue = (value) => isRecord(value) &&
|
||||||
isRecord(value.$quixosValue) && value.$quixosValue.$typeName === "camino.Value";
|
"$quixosValue" in value &&
|
||||||
export const objectRef = (reference) => { referenceToWire(reference); return reference; };
|
isRecord(value.$quixosValue) &&
|
||||||
|
value.$quixosValue.$typeName === "camino.Value";
|
||||||
|
export const objectRef = (reference) => {
|
||||||
|
referenceToWire(reference);
|
||||||
|
return reference;
|
||||||
|
};
|
||||||
export const liveValue = (value) => ({ $quixosValue: value });
|
export const liveValue = (value) => ({ $quixosValue: value });
|
||||||
export const jsToProtoValue = (value) => {
|
export const jsToProtoValue = (value) => {
|
||||||
if (isObjectReference(value))
|
if (isObjectReference(value))
|
||||||
return create(ValueSchema, { kind: { case: "refValue", value: create(RefValueSchema, { objectId: referenceToWire(value) }) } });
|
return create(ValueSchema, {
|
||||||
|
kind: { case: "refValue", value: create(RefValueSchema, { objectId: referenceToWire(value) }) },
|
||||||
|
});
|
||||||
if (isWrappedValue(value))
|
if (isWrappedValue(value))
|
||||||
return value.$quixosValue;
|
return value.$quixosValue;
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
@@ -56,37 +64,49 @@ export const jsToProtoValue = (value) => {
|
|||||||
}
|
}
|
||||||
if (isRecord(value) && "$quixosRef" in value)
|
if (isRecord(value) && "$quixosRef" in value)
|
||||||
throw new Error("Raw ID wrappers are not object references");
|
throw new Error("Raw ID wrappers are not object references");
|
||||||
if (isRecord(value) && typeof value.$quixosCrdtType === "string" &&
|
if (isRecord(value) && typeof value.$quixosCrdtType === "string" && typeof value.$quixosCrdtPayload === "string") {
|
||||||
typeof value.$quixosCrdtPayload === "string") {
|
|
||||||
return create(ValueSchema, {
|
return create(ValueSchema, {
|
||||||
kind: { case: "crdtValue", value: create(CrdtValueSchema, {
|
kind: {
|
||||||
|
case: "crdtValue",
|
||||||
|
value: create(CrdtValueSchema, {
|
||||||
type: value.$quixosCrdtType,
|
type: value.$quixosCrdtType,
|
||||||
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
|
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
|
||||||
payload: base64ToBytes(value.$quixosCrdtPayload),
|
payload: base64ToBytes(value.$quixosCrdtPayload),
|
||||||
}) },
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!isRecord(value))
|
if (!isRecord(value))
|
||||||
throw new Error(`Unsupported runtime value ${typeof value}`);
|
throw new Error(`Unsupported runtime value ${typeof value}`);
|
||||||
return create(ValueSchema, {
|
return create(ValueSchema, {
|
||||||
kind: { case: "objectValue", value: create(ObjectValueSchema, {
|
kind: {
|
||||||
|
case: "objectValue",
|
||||||
|
value: create(ObjectValueSchema, {
|
||||||
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
|
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
|
||||||
}) },
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const protoValueToJs = (value) => {
|
export const protoValueToJs = (value) => {
|
||||||
switch (value?.kind.case) {
|
switch (value?.kind.case) {
|
||||||
case "nullValue":
|
case "nullValue":
|
||||||
case undefined: return null;
|
case undefined:
|
||||||
|
return null;
|
||||||
case "boolValue":
|
case "boolValue":
|
||||||
case "numberValue":
|
case "numberValue":
|
||||||
case "stringValue":
|
case "stringValue":
|
||||||
case "integerValue": return value.kind.value;
|
case "integerValue":
|
||||||
case "bytesValue": return bytesToBase64(value.kind.value);
|
return value.kind.value;
|
||||||
case "refValue": return referenceFromWire(value.kind.value.objectId);
|
case "bytesValue":
|
||||||
case "listValue": return value.kind.value.values.map(protoValueToJs);
|
return bytesToBase64(value.kind.value);
|
||||||
case "objectValue": return Object.fromEntries(Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]));
|
case "refValue":
|
||||||
case "crdtValue": return {
|
return referenceFromWire(value.kind.value.objectId);
|
||||||
|
case "listValue":
|
||||||
|
return value.kind.value.values.map(protoValueToJs);
|
||||||
|
case "objectValue":
|
||||||
|
return Object.fromEntries(Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]));
|
||||||
|
case "crdtValue":
|
||||||
|
return {
|
||||||
$quixosCrdtType: value.kind.value.type,
|
$quixosCrdtType: value.kind.value.type,
|
||||||
$quixosCrdtEncoding: value.kind.value.encoding,
|
$quixosCrdtEncoding: value.kind.value.encoding,
|
||||||
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
|
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
|
||||||
@@ -96,13 +116,55 @@ export const protoValueToJs = (value) => {
|
|||||||
export const protoFieldsToJs = (fields) => Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, protoValueToJs(value)]));
|
export const protoFieldsToJs = (fields) => Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, protoValueToJs(value)]));
|
||||||
export class RuntimeAuthorityError extends Error {
|
export class RuntimeAuthorityError extends Error {
|
||||||
retryable;
|
retryable;
|
||||||
constructor(message) { super(message); this.name = "RuntimeAuthorityError"; this.retryable = /WORKSPACE_FENCED|STALE_EPOCH/.test(message); }
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = "RuntimeAuthorityError";
|
||||||
|
this.retryable = /WORKSPACE_FENCED|STALE_EPOCH/.test(message);
|
||||||
}
|
}
|
||||||
const targetForEdge = (edge, projectionId) => edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId;
|
}
|
||||||
|
const targetForEdge = (edge, projectionId) => (edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId);
|
||||||
export const createRuntimeContext = (camino, orch, request) => {
|
export const createRuntimeContext = (camino, orch, request) => {
|
||||||
|
const acquiredPort = (objectId, interfaceRevisionId, conformance) => {
|
||||||
|
const invoke = async (operationId, input = {}) => {
|
||||||
|
const response = await orch.invokeCapability({
|
||||||
|
objectId,
|
||||||
|
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId, conformance }),
|
||||||
|
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
|
||||||
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(response.error || "Capability invocation failed");
|
||||||
|
for (const dependency of response.dependencies) {
|
||||||
|
if (dependency.kind === "state" || dependency.kind === "edge")
|
||||||
|
await recordDependency({
|
||||||
|
kind: dependency.kind,
|
||||||
|
objectId: dependency.objectId,
|
||||||
|
attachmentId: dependency.attachmentId,
|
||||||
|
...(dependency.kind === "edge" ? { projectionId: dependency.projectionId } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!response.result)
|
||||||
|
throw new Error("Capability returned no value");
|
||||||
|
return response.result;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
objectId: referenceFromWire(objectId),
|
||||||
|
interfaceRevisionId,
|
||||||
|
invoke: async (operation, input) => protoValueToJs(await invoke(operation, input)),
|
||||||
|
live: async (operation, input) => liveValue(await invoke(operation, input)),
|
||||||
|
};
|
||||||
|
};
|
||||||
const ports = new Map();
|
const ports = new Map();
|
||||||
for (const dependency of request.dependencies) {
|
for (const dependency of request.dependencies) {
|
||||||
switch (dependency.binding.case) {
|
switch (dependency.binding.case) {
|
||||||
|
case "queryId": {
|
||||||
|
const queryId = dependency.binding.value, objectId = dependency.objectId || request.objectId;
|
||||||
|
ports.set(dependency.portId, {
|
||||||
|
queryId,
|
||||||
|
execute: (variables, expectedDefinitionDigest) => orch.executeQuery({ queryId, objectId, variables, expectedDefinitionDigest }),
|
||||||
|
watch: (variables, signal, expectedDefinitionDigest) => orch.watchQuery({ queryId, objectId, variables, expectedDefinitionDigest }, { signal }),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "stateSlotId": {
|
case "stateSlotId": {
|
||||||
const slotId = dependency.binding.value;
|
const slotId = dependency.binding.value;
|
||||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||||
@@ -130,25 +192,55 @@ export const createRuntimeContext = (camino, orch, request) => {
|
|||||||
case "edge": {
|
case "edge": {
|
||||||
const { edgeTypeId, projectionId } = dependency.binding.value;
|
const { edgeTypeId, projectionId } = dependency.binding.value;
|
||||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||||
const collectionResult = (response) => ({ revision: response.revision,
|
const collectionResult = (response) => ({
|
||||||
entries: response.entries.map((entry) => ({ edgeId: entry.edgeId, target: referenceFromWire(entry.targetObjectId),
|
revision: response.revision,
|
||||||
...(entry.key ? { key: entry.key.kind.case === "integerValue" ? BigInt(entry.key.kind.value) : protoValueToJs(entry.key) } : {}) })) });
|
entries: response.entries.map((entry) => ({
|
||||||
|
edgeId: entry.edgeId,
|
||||||
|
target: referenceFromWire(entry.targetObjectId),
|
||||||
|
...(entry.key
|
||||||
|
? {
|
||||||
|
key: entry.key.kind.case === "integerValue"
|
||||||
|
? BigInt(entry.key.kind.value)
|
||||||
|
: protoValueToJs(entry.key),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})),
|
||||||
|
});
|
||||||
const edge = {
|
const edge = {
|
||||||
edgeTypeId,
|
edgeTypeId,
|
||||||
projectionId,
|
projectionId,
|
||||||
async collection() {
|
async collection() {
|
||||||
await recordDependency({ kind: "edge", objectId: dependencyObjectId, attachmentId: edgeTypeId, projectionId });
|
await recordDependency({
|
||||||
|
kind: "edge",
|
||||||
|
objectId: dependencyObjectId,
|
||||||
|
attachmentId: edgeTypeId,
|
||||||
|
projectionId,
|
||||||
|
});
|
||||||
return collectionResult(await camino.readCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId }));
|
return collectionResult(await camino.readCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId }));
|
||||||
},
|
},
|
||||||
async replace(entries, expectedRevision) {
|
async replace(entries, expectedRevision) {
|
||||||
return collectionResult(await camino.replaceCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId, expectedRevision,
|
return collectionResult(await camino.replaceCollection({
|
||||||
|
objectId: dependencyObjectId,
|
||||||
|
edgeTypeId,
|
||||||
|
projectionId,
|
||||||
|
expectedRevision,
|
||||||
entries: entries.map((entry) => {
|
entries: entries.map((entry) => {
|
||||||
assertReferenceFree(entry.key);
|
assertReferenceFree(entry.key);
|
||||||
return { edgeId: entry.edgeId ?? "", targetObjectId: referenceToWire(entry.target), key: entry.key === undefined ? undefined : jsToProtoValue(entry.key) };
|
return {
|
||||||
}) }));
|
edgeId: entry.edgeId ?? "",
|
||||||
|
targetObjectId: referenceToWire(entry.target),
|
||||||
|
key: entry.key === undefined ? undefined : jsToProtoValue(entry.key),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
async resolve() {
|
async resolve() {
|
||||||
await recordDependency({ kind: "edge", objectId: dependencyObjectId, attachmentId: edgeTypeId, projectionId });
|
await recordDependency({
|
||||||
|
kind: "edge",
|
||||||
|
objectId: dependencyObjectId,
|
||||||
|
attachmentId: edgeTypeId,
|
||||||
|
projectionId,
|
||||||
|
});
|
||||||
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
||||||
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
|
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
|
||||||
},
|
},
|
||||||
@@ -171,47 +263,7 @@ export const createRuntimeContext = (camino, orch, request) => {
|
|||||||
case "interfaceRevisionId": {
|
case "interfaceRevisionId": {
|
||||||
const interfaceRevisionId = dependency.binding.value;
|
const interfaceRevisionId = dependency.binding.value;
|
||||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||||
const invoke = async (operationId, input) => {
|
ports.set(dependency.portId, acquiredPort(dependencyObjectId, interfaceRevisionId));
|
||||||
const response = await orch.invokeCapability({
|
|
||||||
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId }),
|
|
||||||
objectId: dependencyObjectId,
|
|
||||||
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
|
|
||||||
});
|
|
||||||
if (!response.ok)
|
|
||||||
throw new Error(response.error || `Capability ${operationId} failed`);
|
|
||||||
for (const dependency of response.dependencies) {
|
|
||||||
if (dependency.kind === "state") {
|
|
||||||
await recordDependency({
|
|
||||||
kind: "state",
|
|
||||||
objectId: dependency.objectId,
|
|
||||||
attachmentId: dependency.attachmentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (dependency.kind === "edge") {
|
|
||||||
await recordDependency({
|
|
||||||
kind: "edge",
|
|
||||||
objectId: dependency.objectId,
|
|
||||||
attachmentId: dependency.attachmentId,
|
|
||||||
projectionId: dependency.projectionId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response.result;
|
|
||||||
};
|
|
||||||
const capability = {
|
|
||||||
objectId: referenceFromWire(dependencyObjectId),
|
|
||||||
interfaceRevisionId,
|
|
||||||
async invoke(operationId, input = {}) {
|
|
||||||
return protoValueToJs(await invoke(operationId, input));
|
|
||||||
},
|
|
||||||
async live(operationId, input = {}) {
|
|
||||||
const value = await invoke(operationId, input);
|
|
||||||
if (!value)
|
|
||||||
throw new Error(`Capability ${operationId} returned no value`);
|
|
||||||
return liveValue(value);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ports.set(dependency.portId, capability);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "constructorAtomId": {
|
case "constructorAtomId": {
|
||||||
@@ -239,7 +291,16 @@ export const createRuntimeContext = (camino, orch, request) => {
|
|||||||
return port;
|
return port;
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
objectId: referenceFromWire(request.objectId),
|
async tryConform(object, interfaceRevisionId) {
|
||||||
|
const objectId = referenceToWire(object);
|
||||||
|
const { conformance } = await orch.tryConform({ objectId, interfaceRevisionId });
|
||||||
|
if (conformance && (conformance.objectId !== objectId || conformance.interfaceRevisionId !== interfaceRevisionId))
|
||||||
|
throw new Error("Conformance response does not match the requested view");
|
||||||
|
return conformance ? acquiredPort(objectId, interfaceRevisionId, conformance) : undefined;
|
||||||
|
},
|
||||||
|
get objectId() {
|
||||||
|
return referenceFromWire(request.objectId);
|
||||||
|
},
|
||||||
input: protoFieldsToJs(request.input),
|
input: protoFieldsToJs(request.input),
|
||||||
inputProto: request.input,
|
inputProto: request.input,
|
||||||
ports,
|
ports,
|
||||||
@@ -247,6 +308,7 @@ export const createRuntimeContext = (camino, orch, request) => {
|
|||||||
edge: (portId) => requirePort(portId, "edgeTypeId"),
|
edge: (portId) => requirePort(portId, "edgeTypeId"),
|
||||||
interface: (portId) => requirePort(portId, "interfaceRevisionId"),
|
interface: (portId) => requirePort(portId, "interfaceRevisionId"),
|
||||||
constructor: (portId) => requirePort(portId, "atomId"),
|
constructor: (portId) => requirePort(portId, "atomId"),
|
||||||
|
query: (portId) => requirePort(portId, "queryId"),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const derived = (get) => ({ kind: "derived", get });
|
export const derived = (get) => ({ kind: "derived", get });
|
||||||
@@ -265,8 +327,10 @@ const protoDependencies = (dependencies) => dependencies.map((entry) => create(D
|
|||||||
export const createPackageRuntimeRoutes = (config) => {
|
export const createPackageRuntimeRoutes = (config) => {
|
||||||
const invocations = createInvocationRegistry();
|
const invocations = createInvocationRegistry();
|
||||||
const headers = {};
|
const headers = {};
|
||||||
const processToken = process.env.CAMINO_RUNTIME_AUTH_TOKEN ?? (process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
|
const processToken = process.env.CAMINO_RUNTIME_AUTH_TOKEN ??
|
||||||
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim() : "");
|
(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
|
||||||
|
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim()
|
||||||
|
: "");
|
||||||
if (processToken) {
|
if (processToken) {
|
||||||
headers["x-camino-runtime-token"] = processToken;
|
headers["x-camino-runtime-token"] = processToken;
|
||||||
}
|
}
|
||||||
@@ -276,12 +340,14 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
const camino = createClient(CaminoService, createConnectTransport({
|
const camino = createClient(CaminoService, createConnectTransport({
|
||||||
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
|
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
interceptors: headers["x-camino-runtime-token"] ? [
|
interceptors: headers["x-camino-runtime-token"]
|
||||||
|
? [
|
||||||
(next) => async (request) => {
|
(next) => async (request) => {
|
||||||
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]);
|
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]);
|
||||||
return await next(request);
|
return await next(request);
|
||||||
},
|
},
|
||||||
] : [],
|
]
|
||||||
|
: [],
|
||||||
}));
|
}));
|
||||||
const orch = createClient(OrchestratorRuntime, createConnectTransport({
|
const orch = createClient(OrchestratorRuntime, createConnectTransport({
|
||||||
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
|
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
|
||||||
@@ -298,16 +364,23 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
};
|
};
|
||||||
const clientsFor = (request) => {
|
const clientsFor = (request) => {
|
||||||
const context = request.context;
|
const context = request.context;
|
||||||
if (process.env.QUIXOS_RUNTIME_INSTANCE_ID && (!context?.grant || context.instanceId !== process.env.QUIXOS_RUNTIME_INSTANCE_ID || !context.workspaceEpoch)) {
|
if (process.env.QUIXOS_RUNTIME_INSTANCE_ID &&
|
||||||
|
(!context?.grant || context.instanceId !== process.env.QUIXOS_RUNTIME_INSTANCE_ID || !context.workspaceEpoch)) {
|
||||||
throw new ConnectError("Managed invocation requires an exact instance and epoch grant", Code.Unauthenticated);
|
throw new ConnectError("Managed invocation requires an exact instance and epoch grant", Code.Unauthenticated);
|
||||||
}
|
}
|
||||||
if (!context?.grant)
|
if (!context?.grant)
|
||||||
return { camino, orch };
|
return { camino, orch };
|
||||||
const transport = (url) => createConnectTransport({ baseUrl: url, httpVersion: "1.1", interceptors: [(next) => async (call) => {
|
const transport = (url) => createConnectTransport({
|
||||||
|
baseUrl: url,
|
||||||
|
httpVersion: "1.1",
|
||||||
|
interceptors: [
|
||||||
|
(next) => async (call) => {
|
||||||
call.header.set("x-quixos-invocation-grant", context.grant);
|
call.header.set("x-quixos-invocation-grant", context.grant);
|
||||||
call.header.set("x-camino-runtime-token", processToken);
|
call.header.set("x-camino-runtime-token", processToken);
|
||||||
return next(call);
|
return next(call);
|
||||||
}] });
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
camino: createClient(CaminoService, transport(config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310")),
|
camino: createClient(CaminoService, transport(config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310")),
|
||||||
orch: createClient(OrchestratorRuntime, transport(config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311")),
|
orch: createClient(OrchestratorRuntime, transport(config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311")),
|
||||||
@@ -315,9 +388,12 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
};
|
};
|
||||||
const runtimeControl = async (operation, input) => {
|
const runtimeControl = async (operation, input) => {
|
||||||
const response = await fetch(`${config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"}/__runtime/${operation}`, {
|
const response = await fetch(`${config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"}/__runtime/${operation}`, {
|
||||||
method: "POST", headers: { "content-type": "application/json", "x-camino-runtime-token": processToken }, body: JSON.stringify(input), signal: AbortSignal.timeout(10_000),
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json", "x-camino-runtime-token": processToken },
|
||||||
|
body: JSON.stringify(input),
|
||||||
|
signal: AbortSignal.timeout(10_000),
|
||||||
});
|
});
|
||||||
const value = await response.json();
|
const value = (await response.json());
|
||||||
if (!response.ok)
|
if (!response.ok)
|
||||||
throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
|
throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
|
||||||
return value;
|
return value;
|
||||||
@@ -327,8 +403,13 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
return;
|
return;
|
||||||
runtimeContext.openSession = async () => {
|
runtimeContext.openSession = async () => {
|
||||||
const ownerId = request.context.ownerConformanceId;
|
const ownerId = request.context.ownerConformanceId;
|
||||||
const registration = { grant: request.context.grant, objectId: request.objectId, ownerId,
|
const registration = {
|
||||||
sessionId: `session:${randomBytes(16).toString("hex")}`, token: randomBytes(32).toString("base64url") };
|
grant: request.context.grant,
|
||||||
|
objectId: request.objectId,
|
||||||
|
ownerId,
|
||||||
|
sessionId: `session:${randomBytes(16).toString("hex")}`,
|
||||||
|
token: randomBytes(32).toString("base64url"),
|
||||||
|
};
|
||||||
const register = () => runtimeControl("register-session", registration);
|
const register = () => runtimeControl("register-session", registration);
|
||||||
const registered = await register().catch((error) => {
|
const registered = await register().catch((error) => {
|
||||||
// Retry a transport/lost-response failure with exactly the same identity.
|
// Retry a transport/lost-response failure with exactly the same identity.
|
||||||
@@ -347,7 +428,10 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
// by the caller without replaying a side-effecting callback.
|
// by the caller without replaying a side-effecting callback.
|
||||||
const grant = await runtimeControl("acquire-session", registered);
|
const grant = await runtimeControl("acquire-session", registered);
|
||||||
const execution = invocations.begin(grant.invocationId);
|
const execution = invocations.begin(grant.invocationId);
|
||||||
const sessionRequest = { ...request, context: { grant: grant.grant, instanceId: grant.instanceId, workspaceEpoch: grant.epoch } };
|
const sessionRequest = {
|
||||||
|
...request,
|
||||||
|
context: { grant: grant.grant, instanceId: grant.instanceId, workspaceEpoch: grant.epoch },
|
||||||
|
};
|
||||||
const clients = clientsFor(sessionRequest);
|
const clients = clientsFor(sessionRequest);
|
||||||
const context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
|
const context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
|
||||||
context.signal = execution.signal;
|
context.signal = execution.signal;
|
||||||
@@ -359,7 +443,10 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
await runtimeControl("complete-invocation", { invocationId: grant.invocationId }).catch((error) => console.error("Session completion will be reconciled by the host", error));
|
await runtimeControl("complete-invocation", { invocationId: grant.invocationId }).catch((error) => console.error("Session completion will be reconciled by the host", error));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async close() { await runtimeControl("close-session", registered); closed = true; },
|
async close() {
|
||||||
|
await runtimeControl("close-session", registered);
|
||||||
|
closed = true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -370,9 +457,15 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
exportIds: Object.keys(config.exports),
|
exportIds: Object.keys(config.exports),
|
||||||
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
|
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
|
||||||
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||||
authenticationProof: request.nonce && processToken ? createHmac("sha256", processToken)
|
authenticationProof: request.nonce && processToken
|
||||||
.update(JSON.stringify([request.nonce, process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "", config.packageRevisionId]))
|
? createHmac("sha256", processToken)
|
||||||
.digest("hex") : "",
|
.update(JSON.stringify([
|
||||||
|
request.nonce,
|
||||||
|
process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||||
|
config.packageRevisionId,
|
||||||
|
]))
|
||||||
|
.digest("hex")
|
||||||
|
: "",
|
||||||
}),
|
}),
|
||||||
getInvocationStatus: (request, context) => {
|
getInvocationStatus: (request, context) => {
|
||||||
authenticateInstance(context.requestHeader);
|
authenticateInstance(context.requestHeader);
|
||||||
@@ -437,7 +530,12 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
return await pending;
|
return await pending;
|
||||||
const establish = (async () => {
|
const establish = (async () => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const stream = camino.watchObject({ objectId: dependency.objectId, includeSnapshot: true, attachmentIds: request.context?.grant ? [dependency.attachmentId] : [] }, { signal: controller.signal })[Symbol.asyncIterator]();
|
const stream = camino
|
||||||
|
.watchObject({
|
||||||
|
objectId: dependency.objectId,
|
||||||
|
includeSnapshot: true,
|
||||||
|
attachmentIds: request.context?.grant ? [dependency.attachmentId] : [],
|
||||||
|
}, { signal: controller.signal })[Symbol.asyncIterator]();
|
||||||
try {
|
try {
|
||||||
// Camino subscribes before producing the snapshot, so once this
|
// Camino subscribes before producing the snapshot, so once this
|
||||||
// resolves the following state/edge read cannot race the stream.
|
// resolves the following state/edge read cannot race the stream.
|
||||||
@@ -507,10 +605,7 @@ export const createPackageRuntimeRoutes = (config) => {
|
|||||||
await abort;
|
await abort;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const outcome = await Promise.race([
|
const outcome = await Promise.race([...[...subscriptions.values()].map((entry) => entry.next), abort]);
|
||||||
...[...subscriptions.values()].map((entry) => entry.next),
|
|
||||||
abort,
|
|
||||||
]);
|
|
||||||
if (outcome === "abort")
|
if (outcome === "abort")
|
||||||
break;
|
break;
|
||||||
const subscription = subscriptions.get(outcome.key);
|
const subscription = subscriptions.get(outcome.key);
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"invocations.d.ts","sourceRoot":"","sources":["../src/invocations.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB;IAGjC,KAAK,KAAK,MAAM;QAOL,MAAM;QAA2B,MAAM;;IAElD,MAAM,KAAK,MAAM;QAAa,YAAY;QAAM,KAAK;;IACrD,MAAM,KAAK,MAAM;;;;CASpB,CAAC"}
|
{"version":3,"file":"invocations.d.ts","sourceRoot":"","sources":["../src/invocations.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB;IAGjC,KAAK,KAAK,MAAM;QASZ,MAAM;QACN,MAAM;;IAKV,MAAM,KAAK,MAAM;QACN,YAAY;QAAM,KAAK;;IAElC,MAAM,KAAK,MAAM;;;;CASpB,CAAC"}
|
||||||
Vendored
+9
-2
@@ -11,9 +11,16 @@ export const createInvocationRegistry = () => {
|
|||||||
throw new Error("Invocation registry full; explicit runtime retirement required");
|
throw new Error("Invocation registry full; explicit runtime retirement required");
|
||||||
const entry = { state: "running", controller: new AbortController() };
|
const entry = { state: "running", controller: new AbortController() };
|
||||||
entries.set(id, entry);
|
entries.set(id, entry);
|
||||||
return { signal: entry.controller.signal, finish(failed = false) { entry.state = failed ? "failed" : "completed"; } };
|
return {
|
||||||
|
signal: entry.controller.signal,
|
||||||
|
finish(failed = false) {
|
||||||
|
entry.state = failed ? "failed" : "completed";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
status(id) {
|
||||||
|
return { invocationId: id, state: entries.get(id)?.state ?? "unknown" };
|
||||||
},
|
},
|
||||||
status(id) { return { invocationId: id, state: entries.get(id)?.state ?? "unknown" }; },
|
|
||||||
cancel(id) {
|
cancel(id) {
|
||||||
const entry = entries.get(id);
|
const entry = entries.get(id);
|
||||||
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
|
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IACxD,KAAK,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAC5H,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAChD,MAAM,CAAC,EAAE;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAC,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,aAAa,EAAE,CAAA;KAAC,EAAE,CAAC;CAC7E,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAC,CAAC;AACjQ,MAAM,MAAM,eAAe,GAAG;IAAC,aAAa,EAAE,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IACnE,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC,EAAE,CAAC;IAC3D,OAAO,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAChE,gBAAgB,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,EAAE,CAAA;KAAC,EAAE,CAAA;CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC,EAAE,CAAC;IAC9D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAAC;IACrC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;CAC1D,CAAC;AACF,eAAO,MAAM,iBAAiB,gBAAiB,MAAM,QAAQ,MAAM,cAAc,MAAM,WACwB,CAAC;AAEhH;sEACsE;AACtE,eAAO,MAAM,sBAAsB,UAAW,cAAc;;;CAgD3D,CAAC;AAEF;;+EAE+E;AAC/E,eAAO,MAAM,cAAc,YAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,kBAehH,CAAC"}
|
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,CAAC,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;QACpB,MAAM,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC;QACjD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,MAAM,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,EAAE,CAAC;QAChD,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;KACzB,EAAE,CAAC;CACL,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AACF,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,CAAC,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC7D,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE,EAAE,CAAC;CAC9D,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAChE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAAC;IACrC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;CAC1D,CAAC;AACF,eAAO,MAAM,iBAAiB,gBAAiB,MAAM,QAAQ,MAAM,cAAc,MAAM,WAGnE,CAAC;AAErB;sEACsE;AACtE,eAAO,MAAM,sBAAsB,UAAW,cAAc;;;CAgF3D,CAAC;AAEF;;+EAE+E;AAC/E,eAAO,MAAM,cAAc,YAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,kBAgBhH,CAAC"}
|
||||||
Vendored
+22
-7
@@ -1,11 +1,21 @@
|
|||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
export const migrationObjectId = (executionId, port, logicalKey) => `obj:migration:${createHash("sha256").update(JSON.stringify([executionId, port, logicalKey])).digest("hex")}`;
|
export const migrationObjectId = (executionId, port, logicalKey) => `obj:migration:${createHash("sha256")
|
||||||
|
.update(JSON.stringify([executionId, port, logicalKey]))
|
||||||
|
.digest("hex")}`;
|
||||||
/** No ordinary RuntimeContext or network/database clients are supplied here.
|
/** No ordinary RuntimeContext or network/database clients are supplied here.
|
||||||
* Process isolation belongs to the host, not this convenience API. */
|
* Process isolation belongs to the host, not this convenience API. */
|
||||||
export const createMigrationContext = (input) => {
|
export const createMigrationContext = (input) => {
|
||||||
if (input.schemaVersion !== 1 || !input.executionId || new Set(input.ports.map((entry) => entry.name)).size !== input.ports.length)
|
if (input.schemaVersion !== 1 ||
|
||||||
|
!input.executionId ||
|
||||||
|
new Set(input.ports.map((entry) => entry.name)).size !== input.ports.length)
|
||||||
throw new Error("Invalid migration input");
|
throw new Error("Invalid migration input");
|
||||||
const output = { schemaVersion: 1, executionId: input.executionId, writes: [], creates: [], edgeReplacements: [] };
|
const output = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
executionId: input.executionId,
|
||||||
|
writes: [],
|
||||||
|
creates: [],
|
||||||
|
edgeReplacements: [],
|
||||||
|
};
|
||||||
const port = (name, access) => {
|
const port = (name, access) => {
|
||||||
const selected = input.ports.find((entry) => entry.name === name);
|
const selected = input.ports.find((entry) => entry.name === name);
|
||||||
if (!selected?.access.includes(access) || (selected.view === "old" && access !== "read"))
|
if (!selected?.access.includes(access) || (selected.view === "old" && access !== "read"))
|
||||||
@@ -17,7 +27,8 @@ export const createMigrationContext = (input) => {
|
|||||||
const selected = port(name, "read"), states = structuredClone(selected.states ?? []);
|
const selected = port(name, "read"), states = structuredClone(selected.states ?? []);
|
||||||
if (selected.view === "new" && Object.hasOwn(selected, "defaultValue"))
|
if (selected.view === "new" && Object.hasOwn(selected, "defaultValue"))
|
||||||
for (const helper of output.creates) {
|
for (const helper of output.creates) {
|
||||||
if (input.ports.find((entry) => entry.name === helper.port)?.atomId === selected.attachedAtomId && !states.some((entry) => entry.objectId === helper.objectId))
|
if (input.ports.find((entry) => entry.name === helper.port)?.atomId === selected.attachedAtomId &&
|
||||||
|
!states.some((entry) => entry.objectId === helper.objectId))
|
||||||
states.push({ objectId: helper.objectId, value: structuredClone(selected.defaultValue) });
|
states.push({ objectId: helper.objectId, value: structuredClone(selected.defaultValue) });
|
||||||
}
|
}
|
||||||
if (selected.view === "new")
|
if (selected.view === "new")
|
||||||
@@ -30,9 +41,11 @@ export const createMigrationContext = (input) => {
|
|||||||
else
|
else
|
||||||
states[existing] = entry;
|
states[existing] = entry;
|
||||||
}
|
}
|
||||||
return states.sort((a, b) => a.objectId < b.objectId ? -1 : a.objectId > b.objectId ? 1 : 0);
|
return states.sort((a, b) => (a.objectId < b.objectId ? -1 : a.objectId > b.objectId ? 1 : 0));
|
||||||
|
},
|
||||||
|
read(name, objectId) {
|
||||||
|
return context.enumerate(name).find((entry) => entry.objectId === objectId)?.value;
|
||||||
},
|
},
|
||||||
read(name, objectId) { return context.enumerate(name).find((entry) => entry.objectId === objectId)?.value; },
|
|
||||||
write(name, objectId, value) {
|
write(name, objectId, value) {
|
||||||
port(name, "write");
|
port(name, "write");
|
||||||
const previous = output.writes.findIndex((entry) => entry.port === name && entry.objectId === objectId);
|
const previous = output.writes.findIndex((entry) => entry.port === name && entry.objectId === objectId);
|
||||||
@@ -53,7 +66,9 @@ export const createMigrationContext = (input) => {
|
|||||||
},
|
},
|
||||||
edges(name) {
|
edges(name) {
|
||||||
const selected = port(name, "read");
|
const selected = port(name, "read");
|
||||||
const replacement = selected.view === "new" ? output.edgeReplacements.find((entry) => input.ports.find((candidate) => candidate.name === entry.port)?.binding === selected.binding) : undefined;
|
const replacement = selected.view === "new"
|
||||||
|
? output.edgeReplacements.find((entry) => input.ports.find((candidate) => candidate.name === entry.port)?.binding === selected.binding)
|
||||||
|
: undefined;
|
||||||
return structuredClone(replacement?.edges ?? selected.edges ?? []);
|
return structuredClone(replacement?.edges ?? selected.edges ?? []);
|
||||||
},
|
},
|
||||||
replaceEdges(name, edges) {
|
replaceEdges(name, edges) {
|
||||||
|
|||||||
Vendored
+38
@@ -0,0 +1,38 @@
|
|||||||
|
import { type QxValueType } from "./bindings.js";
|
||||||
|
import type { QueryResponse } from "./camino/api_pb.js";
|
||||||
|
import type { QxObjectRef } from "./references.js";
|
||||||
|
export type QxQueryPartial<T> = T extends QxObjectRef ? T : T extends readonly (infer Item)[] ? QxQueryPartial<Item>[] : T extends object ? {
|
||||||
|
[Key in keyof T]?: QxQueryPartial<T[Key]>;
|
||||||
|
} : T;
|
||||||
|
export type QxQuerySnapshot<T> = {
|
||||||
|
runId: string;
|
||||||
|
sequence: bigint;
|
||||||
|
dataVersion: string;
|
||||||
|
bindingDigest: string;
|
||||||
|
consistency: string;
|
||||||
|
fields: {
|
||||||
|
path: readonly (string | number)[];
|
||||||
|
status: "pending" | "error";
|
||||||
|
error?: string;
|
||||||
|
}[];
|
||||||
|
} & ({
|
||||||
|
status: "ready";
|
||||||
|
data: T;
|
||||||
|
} | {
|
||||||
|
status: "partial";
|
||||||
|
data: QxQueryPartial<T>;
|
||||||
|
});
|
||||||
|
declare const queryTypes: unique symbol;
|
||||||
|
/** Generated shape evidence, not permission to run a query. */
|
||||||
|
export interface QxQueryDescriptor<Variables, Result, Root extends string = string> {
|
||||||
|
readonly id: string;
|
||||||
|
readonly definitionDigest: string;
|
||||||
|
readonly rootInterfaceRevisionId: Root;
|
||||||
|
readonly variables: QxValueType;
|
||||||
|
readonly output: QxValueType;
|
||||||
|
readonly watch: boolean;
|
||||||
|
readonly [queryTypes]?: (variables: Variables, result: Result) => [Variables, Result];
|
||||||
|
}
|
||||||
|
export declare function decodeQuerySnapshot<T>(response: QueryResponse, output: QxValueType, runId: string, sequence: bigint): QxQuerySnapshot<T>;
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=queries.d.ts.map
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,WAAW,GACjD,CAAC,GACD,CAAC,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,GAC/B,cAAc,CAAC,IAAI,CAAC,EAAE,GACtB,CAAC,SAAS,MAAM,GACd;KAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;CAAE,GAC7C,CAAC,CAAC;AACV,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/F,GAAG,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC,CAAC;AACpF,OAAO,CAAC,MAAM,UAAU,EAAE,OAAO,MAAM,CAAC;AACxC,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAS,MAAM,GAAG,MAAM;IAChF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,uBAAuB,EAAE,IAAI,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;CACvF;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,eAAe,CAAC,CAAC,CAAC,CA6CpB"}
|
||||||
Vendored
+57
@@ -0,0 +1,57 @@
|
|||||||
|
import { decodeQxValue } from "./bindings.js";
|
||||||
|
export function decodeQuerySnapshot(response, output, runId, sequence) {
|
||||||
|
if (response.preparationToken || response.residualWindows.length || response.relationalCaptures.length)
|
||||||
|
throw new Error("QUERY_RESULT_UNFINISHED: private preparation is not a query result");
|
||||||
|
const fields = [
|
||||||
|
...response.pending.map((field) => ({ path: field.path, status: "pending" })),
|
||||||
|
...response.errors.map((field) => ({ path: field.path, status: "error", error: field.error })),
|
||||||
|
].map((field) => ({
|
||||||
|
...field,
|
||||||
|
path: field.path.map((part) => {
|
||||||
|
if (part.part.case !== "field" && part.part.case !== "index")
|
||||||
|
throw new Error("QUERY_PATCH_INVALID");
|
||||||
|
return part.part.value;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
// Pending/error values are absent, not successful nulls of a scalar type.
|
||||||
|
const shape = structuredClone(output);
|
||||||
|
for (const field of fields) {
|
||||||
|
let cursor = shape;
|
||||||
|
for (const [index, part] of field.path.entries()) {
|
||||||
|
while (cursor.kind === "optional")
|
||||||
|
cursor = cursor.value;
|
||||||
|
const last = index === field.path.length - 1;
|
||||||
|
if (typeof part === "string" && cursor.kind === "record" && cursor.fields[part]) {
|
||||||
|
if (last)
|
||||||
|
cursor.fields[part] = { kind: "optional", value: cursor.fields[part] };
|
||||||
|
else
|
||||||
|
cursor = cursor.fields[part];
|
||||||
|
}
|
||||||
|
else if (typeof part === "number" && cursor.kind === "list")
|
||||||
|
cursor = cursor.value;
|
||||||
|
else
|
||||||
|
throw new Error("QUERY_PATCH_INVALID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = decodeQxValue(shape, response.value, {});
|
||||||
|
for (const field of fields) {
|
||||||
|
let cursor = data;
|
||||||
|
for (const [index, part] of field.path.entries()) {
|
||||||
|
if (!cursor || typeof cursor !== "object")
|
||||||
|
throw new Error("QUERY_PATCH_INVALID");
|
||||||
|
if (index === field.path.length - 1)
|
||||||
|
delete cursor[part];
|
||||||
|
else
|
||||||
|
cursor = cursor[part];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runId,
|
||||||
|
sequence,
|
||||||
|
dataVersion: response.dataVersion,
|
||||||
|
bindingDigest: response.bindingDigest,
|
||||||
|
consistency: response.consistency,
|
||||||
|
fields,
|
||||||
|
...(fields.length ? { status: "partial", data } : { status: "ready", data }),
|
||||||
|
};
|
||||||
|
}
|
||||||
Vendored
+378
-3
@@ -1,13 +1,88 @@
|
|||||||
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
import type { CaminoObject, Value } from "../camino/api_pb.js";
|
import type { CaminoObject, CrdtValue, QueryPathPart, QueryRequestSchema, QueryResponse, QueryResponseSchema, Value } from "../camino/api_pb.js";
|
||||||
|
import type { InstalledQuery } from "../camino/schema_pb.js";
|
||||||
import type { PackageDescriptor } from "./package_pb.js";
|
import type { PackageDescriptor } from "./package_pb.js";
|
||||||
import type { CapabilityRef, PackageExportRef } from "./refs_pb.js";
|
import type { CapabilityRef, ConformanceWitness, PackageExportRef } from "./refs_pb.js";
|
||||||
import type { DerivedDependency } from "./runtime_pb.js";
|
import type { DerivedDependency } from "./runtime_pb.js";
|
||||||
import type { Message } from "@bufbuild/protobuf";
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
/**
|
/**
|
||||||
* Describes the file quixos/orch.proto.
|
* Describes the file quixos/orch.proto.
|
||||||
*/
|
*/
|
||||||
export declare const file_quixos_orch: GenFile;
|
export declare const file_quixos_orch: GenFile;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.QueryEvent
|
||||||
|
*/
|
||||||
|
export type QueryEvent = Message<"quixos.orch.QueryEvent"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string run_id = 1;
|
||||||
|
*/
|
||||||
|
runId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: uint64 sequence = 2;
|
||||||
|
*/
|
||||||
|
sequence: bigint;
|
||||||
|
/**
|
||||||
|
* @generated from field: string kind = 3;
|
||||||
|
*/
|
||||||
|
kind: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryResponse snapshot = 4;
|
||||||
|
*/
|
||||||
|
snapshot?: QueryResponse | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryPathPart path = 5;
|
||||||
|
*/
|
||||||
|
path: QueryPathPart[];
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.Value value = 6;
|
||||||
|
*/
|
||||||
|
value?: Value | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: string error = 7;
|
||||||
|
*/
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.QueryEvent.
|
||||||
|
* Use `create(QueryEventSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryEventSchema: GenMessage<QueryEvent>;
|
||||||
|
/**
|
||||||
|
* Exact closed interface lookup; no policy selection or competing conformances.
|
||||||
|
*
|
||||||
|
* @generated from message quixos.orch.TryConformRequest
|
||||||
|
*/
|
||||||
|
export type TryConformRequest = Message<"quixos.orch.TryConformRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 1;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 2;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.TryConformRequest.
|
||||||
|
* Use `create(TryConformRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const TryConformRequestSchema: GenMessage<TryConformRequest>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.TryConformResponse
|
||||||
|
*/
|
||||||
|
export type TryConformResponse = Message<"quixos.orch.TryConformResponse"> & {
|
||||||
|
/**
|
||||||
|
* Absent only when this object lacks a known contract. Other failures are errors.
|
||||||
|
*
|
||||||
|
* @generated from field: quixos.ConformanceWitness conformance = 1;
|
||||||
|
*/
|
||||||
|
conformance?: ConformanceWitness | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.TryConformResponse.
|
||||||
|
* Use `create(TryConformResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const TryConformResponseSchema: GenMessage<TryConformResponse>;
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.orch.ConstructObjectRequest
|
* @generated from message quixos.orch.ConstructObjectRequest
|
||||||
*/
|
*/
|
||||||
@@ -113,6 +188,30 @@ export type InvokeCapabilityRequest = Message<"quixos.orch.InvokeCapabilityReque
|
|||||||
* Use `create(InvokeCapabilityRequestSchema)` to create a new message.
|
* Use `create(InvokeCapabilityRequestSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export declare const InvokeCapabilityRequestSchema: GenMessage<InvokeCapabilityRequest>;
|
export declare const InvokeCapabilityRequestSchema: GenMessage<InvokeCapabilityRequest>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.InvokeClassCapabilityRequest
|
||||||
|
*/
|
||||||
|
export type InvokeClassCapabilityRequest = Message<"quixos.orch.InvokeClassCapabilityRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string conformance_id = 1;
|
||||||
|
*/
|
||||||
|
conformanceId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string operation_id = 2;
|
||||||
|
*/
|
||||||
|
operationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.Value> input = 3;
|
||||||
|
*/
|
||||||
|
input: {
|
||||||
|
[key: string]: Value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.InvokeClassCapabilityRequest.
|
||||||
|
* Use `create(InvokeClassCapabilityRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const InvokeClassCapabilityRequestSchema: GenMessage<InvokeClassCapabilityRequest>;
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.orch.InvokeCapabilityResponse
|
* @generated from message quixos.orch.InvokeCapabilityResponse
|
||||||
*/
|
*/
|
||||||
@@ -141,12 +240,80 @@ export type InvokeCapabilityResponse = Message<"quixos.orch.InvokeCapabilityResp
|
|||||||
* @generated from field: repeated quixos.runtime.DerivedDependency dependencies = 6;
|
* @generated from field: repeated quixos.runtime.DerivedDependency dependencies = 6;
|
||||||
*/
|
*/
|
||||||
dependencies: DerivedDependency[];
|
dependencies: DerivedDependency[];
|
||||||
|
/**
|
||||||
|
* @generated from field: quixos.orch.FieldEditing field_editing = 7;
|
||||||
|
*/
|
||||||
|
fieldEditing?: FieldEditing | undefined;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.orch.InvokeCapabilityResponse.
|
* Describes the message quixos.orch.InvokeCapabilityResponse.
|
||||||
* Use `create(InvokeCapabilityResponseSchema)` to create a new message.
|
* Use `create(InvokeCapabilityResponseSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export declare const InvokeCapabilityResponseSchema: GenMessage<InvokeCapabilityResponse>;
|
export declare const InvokeCapabilityResponseSchema: GenMessage<InvokeCapabilityResponse>;
|
||||||
|
/**
|
||||||
|
* Resolved from the checked native getter/setter binding, not Value.source.
|
||||||
|
*
|
||||||
|
* @generated from message quixos.orch.FieldEditing
|
||||||
|
*/
|
||||||
|
export type FieldEditing = Message<"quixos.orch.FieldEditing"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string getter_operation_id = 1;
|
||||||
|
*/
|
||||||
|
getterOperationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string setter_operation_id = 2;
|
||||||
|
*/
|
||||||
|
setterOperationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string document_type = 3;
|
||||||
|
*/
|
||||||
|
documentType: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string binding_digest = 4;
|
||||||
|
*/
|
||||||
|
bindingDigest: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.FieldEditing.
|
||||||
|
* Use `create(FieldEditingSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const FieldEditingSchema: GenMessage<FieldEditing>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.EditCapabilityFieldRequest
|
||||||
|
*/
|
||||||
|
export type EditCapabilityFieldRequest = Message<"quixos.orch.EditCapabilityFieldRequest"> & {
|
||||||
|
/**
|
||||||
|
* The public getter; setter must belong to the same value member.
|
||||||
|
*
|
||||||
|
* @generated from field: quixos.CapabilityRef capability = 1;
|
||||||
|
*/
|
||||||
|
capability?: CapabilityRef | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: string object_id = 2;
|
||||||
|
*/
|
||||||
|
objectId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string setter_operation_id = 3;
|
||||||
|
*/
|
||||||
|
setterOperationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string binding_digest = 4;
|
||||||
|
*/
|
||||||
|
bindingDigest: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.CrdtValue update = 5;
|
||||||
|
*/
|
||||||
|
update?: CrdtValue | undefined;
|
||||||
|
/**
|
||||||
|
* @generated from field: string client_mutation_id = 6;
|
||||||
|
*/
|
||||||
|
clientMutationId: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.EditCapabilityFieldRequest.
|
||||||
|
* Use `create(EditCapabilityFieldRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const EditCapabilityFieldRequestSchema: GenMessage<EditCapabilityFieldRequest>;
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.orch.WatchCapabilityRequest
|
* @generated from message quixos.orch.WatchCapabilityRequest
|
||||||
*/
|
*/
|
||||||
@@ -203,6 +370,10 @@ export type WatchCapabilityEvent = Message<"quixos.orch.WatchCapabilityEvent"> &
|
|||||||
* @generated from field: bool initial = 7;
|
* @generated from field: bool initial = 7;
|
||||||
*/
|
*/
|
||||||
initial: boolean;
|
initial: boolean;
|
||||||
|
/**
|
||||||
|
* @generated from field: quixos.orch.FieldEditing field_editing = 8;
|
||||||
|
*/
|
||||||
|
fieldEditing?: FieldEditing | undefined;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.orch.WatchCapabilityEvent.
|
* Describes the message quixos.orch.WatchCapabilityEvent.
|
||||||
@@ -212,7 +383,18 @@ export declare const WatchCapabilityEventSchema: GenMessage<WatchCapabilityEvent
|
|||||||
/**
|
/**
|
||||||
* @generated from message quixos.orch.GetWorkspaceRequest
|
* @generated from message quixos.orch.GetWorkspaceRequest
|
||||||
*/
|
*/
|
||||||
export type GetWorkspaceRequest = Message<"quixos.orch.GetWorkspaceRequest"> & {};
|
export type GetWorkspaceRequest = Message<"quixos.orch.GetWorkspaceRequest"> & {
|
||||||
|
/**
|
||||||
|
* Revision polling must not download the entire interface graph.
|
||||||
|
*
|
||||||
|
* @generated from field: bool include_interface_contracts = 1;
|
||||||
|
*/
|
||||||
|
includeInterfaceContracts: boolean;
|
||||||
|
/**
|
||||||
|
* @generated from field: bool include_query_contracts = 2;
|
||||||
|
*/
|
||||||
|
includeQueryContracts: boolean;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.orch.GetWorkspaceRequest.
|
* Describes the message quixos.orch.GetWorkspaceRequest.
|
||||||
* Use `create(GetWorkspaceRequestSchema)` to create a new message.
|
* Use `create(GetWorkspaceRequestSchema)` to create a new message.
|
||||||
@@ -234,12 +416,165 @@ export type GetWorkspaceResponse = Message<"quixos.orch.GetWorkspaceResponse"> &
|
|||||||
* @generated from field: string source_root_commit = 3;
|
* @generated from field: string source_root_commit = 3;
|
||||||
*/
|
*/
|
||||||
sourceRootCommit: string;
|
sourceRootCommit: string;
|
||||||
|
/**
|
||||||
|
* Checked constructors whose wire input can be empty. The create panel uses
|
||||||
|
* class factory conformances instead of this constructor inventory.
|
||||||
|
*
|
||||||
|
* @generated from field: repeated string empty_input_constructible_atom_ids = 4;
|
||||||
|
*/
|
||||||
|
emptyInputConstructibleAtomIds: string[];
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated quixos.orch.CapabilityInputContract capability_inputs = 5;
|
||||||
|
*/
|
||||||
|
capabilityInputs: CapabilityInputContract[];
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated quixos.orch.ConstructorInputContract constructor_inputs = 6;
|
||||||
|
*/
|
||||||
|
constructorInputs: ConstructorInputContract[];
|
||||||
|
/**
|
||||||
|
* Exact closed interface contracts used by checked presentation consumers.
|
||||||
|
*
|
||||||
|
* @generated from field: string interfaces_json = 7;
|
||||||
|
*/
|
||||||
|
interfacesJson: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated quixos.orch.ClassCapability class_capabilities = 8;
|
||||||
|
*/
|
||||||
|
classCapabilities: ClassCapability[];
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated quixos.orch.QueryDescription queries = 9;
|
||||||
|
*/
|
||||||
|
queries: QueryDescription[];
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 active_query_executions = 10;
|
||||||
|
*/
|
||||||
|
activeQueryExecutions: number;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.orch.GetWorkspaceResponse.
|
* Describes the message quixos.orch.GetWorkspaceResponse.
|
||||||
* Use `create(GetWorkspaceResponseSchema)` to create a new message.
|
* Use `create(GetWorkspaceResponseSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export declare const GetWorkspaceResponseSchema: GenMessage<GetWorkspaceResponse>;
|
export declare const GetWorkspaceResponseSchema: GenMessage<GetWorkspaceResponse>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.QueryDescription
|
||||||
|
*/
|
||||||
|
export type QueryDescription = Message<"quixos.orch.QueryDescription"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string id = 1;
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 2;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string package_revision_id = 3;
|
||||||
|
*/
|
||||||
|
packageRevisionId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string document = 4;
|
||||||
|
*/
|
||||||
|
document: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string schema = 5;
|
||||||
|
*/
|
||||||
|
schema: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated string source_files = 6;
|
||||||
|
*/
|
||||||
|
sourceFiles: string[];
|
||||||
|
/**
|
||||||
|
* @generated from field: string effects_json = 7;
|
||||||
|
*/
|
||||||
|
effectsJson: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.InstalledQuery plan = 8;
|
||||||
|
*/
|
||||||
|
plan?: InstalledQuery | undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.QueryDescription.
|
||||||
|
* Use `create(QueryDescriptionSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const QueryDescriptionSchema: GenMessage<QueryDescription>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.ClassCapability
|
||||||
|
*/
|
||||||
|
export type ClassCapability = Message<"quixos.orch.ClassCapability"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string conformance_id = 1;
|
||||||
|
*/
|
||||||
|
conformanceId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string atom_id = 2;
|
||||||
|
*/
|
||||||
|
atomId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 3;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string definition_id = 4;
|
||||||
|
*/
|
||||||
|
definitionId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string operation_id = 5;
|
||||||
|
*/
|
||||||
|
operationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string input_type_json = 6;
|
||||||
|
*/
|
||||||
|
inputTypeJson: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string output_type_json = 7;
|
||||||
|
*/
|
||||||
|
outputTypeJson: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.ClassCapability.
|
||||||
|
* Use `create(ClassCapabilitySchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const ClassCapabilitySchema: GenMessage<ClassCapability>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.CapabilityInputContract
|
||||||
|
*/
|
||||||
|
export type CapabilityInputContract = Message<"quixos.orch.CapabilityInputContract"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 1;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string operation_id = 2;
|
||||||
|
*/
|
||||||
|
operationId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string type_json = 3;
|
||||||
|
*/
|
||||||
|
typeJson: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.CapabilityInputContract.
|
||||||
|
* Use `create(CapabilityInputContractSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const CapabilityInputContractSchema: GenMessage<CapabilityInputContract>;
|
||||||
|
/**
|
||||||
|
* @generated from message quixos.orch.ConstructorInputContract
|
||||||
|
*/
|
||||||
|
export type ConstructorInputContract = Message<"quixos.orch.ConstructorInputContract"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string atom_id = 1;
|
||||||
|
*/
|
||||||
|
atomId: string;
|
||||||
|
/**
|
||||||
|
* @generated from field: string type_json = 2;
|
||||||
|
*/
|
||||||
|
typeJson: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.orch.ConstructorInputContract.
|
||||||
|
* Use `create(ConstructorInputContractSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export declare const ConstructorInputContractSchema: GenMessage<ConstructorInputContract>;
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.orch.ListActivationsRequest
|
* @generated from message quixos.orch.ListActivationsRequest
|
||||||
*/
|
*/
|
||||||
@@ -453,6 +788,30 @@ export declare const PackageRuntimeStatusSchema: GenMessage<PackageRuntimeStatus
|
|||||||
* @generated from service quixos.orch.OrchestratorRuntime
|
* @generated from service quixos.orch.OrchestratorRuntime
|
||||||
*/
|
*/
|
||||||
export declare const OrchestratorRuntime: GenService<{
|
export declare const OrchestratorRuntime: GenService<{
|
||||||
|
/**
|
||||||
|
* @generated from rpc quixos.orch.OrchestratorRuntime.ExecuteQuery
|
||||||
|
*/
|
||||||
|
executeQuery: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof QueryRequestSchema;
|
||||||
|
output: typeof QueryResponseSchema;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from rpc quixos.orch.OrchestratorRuntime.WatchQuery
|
||||||
|
*/
|
||||||
|
watchQuery: {
|
||||||
|
methodKind: "server_streaming";
|
||||||
|
input: typeof QueryRequestSchema;
|
||||||
|
output: typeof QueryEventSchema;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from rpc quixos.orch.OrchestratorRuntime.TryConform
|
||||||
|
*/
|
||||||
|
tryConform: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof TryConformRequestSchema;
|
||||||
|
output: typeof TryConformResponseSchema;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* @generated from rpc quixos.orch.OrchestratorRuntime.InvokeCapability
|
* @generated from rpc quixos.orch.OrchestratorRuntime.InvokeCapability
|
||||||
*/
|
*/
|
||||||
@@ -461,6 +820,22 @@ export declare const OrchestratorRuntime: GenService<{
|
|||||||
input: typeof InvokeCapabilityRequestSchema;
|
input: typeof InvokeCapabilityRequestSchema;
|
||||||
output: typeof InvokeCapabilityResponseSchema;
|
output: typeof InvokeCapabilityResponseSchema;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @generated from rpc quixos.orch.OrchestratorRuntime.EditCapabilityField
|
||||||
|
*/
|
||||||
|
editCapabilityField: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof EditCapabilityFieldRequestSchema;
|
||||||
|
output: typeof InvokeCapabilityResponseSchema;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @generated from rpc quixos.orch.OrchestratorRuntime.InvokeClassCapability
|
||||||
|
*/
|
||||||
|
invokeClassCapability: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof InvokeClassCapabilityRequestSchema;
|
||||||
|
output: typeof InvokeCapabilityResponseSchema;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* @generated from rpc quixos.orch.OrchestratorRuntime.WatchCapability
|
* @generated from rpc quixos.orch.OrchestratorRuntime.WatchCapability
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+72
-21
File diff suppressed because one or more lines are too long
Vendored
+42
@@ -16,12 +16,48 @@ 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;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.CapabilityRef.
|
* Describes the message quixos.CapabilityRef.
|
||||||
* Use `create(CapabilityRefSchema)` to create a new message.
|
* Use `create(CapabilityRefSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export declare const CapabilityRefSchema: GenMessage<CapabilityRef>;
|
export declare const CapabilityRefSchema: GenMessage<CapabilityRef>;
|
||||||
|
/**
|
||||||
|
* @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 declare const ConformanceWitnessSchema: GenMessage<ConformanceWitness>;
|
||||||
/**
|
/**
|
||||||
* @generated from message quixos.PackageExportRef
|
* @generated from message quixos.PackageExportRef
|
||||||
*/
|
*/
|
||||||
@@ -75,6 +111,12 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
|
|||||||
*/
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
case: "constructorAtomId";
|
case: "constructorAtomId";
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* @generated from field: string query_id = 7;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
case: "queryId";
|
||||||
} | {
|
} | {
|
||||||
case: undefined;
|
case: undefined;
|
||||||
value?: undefined;
|
value?: undefined;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"refs_pb.d.ts","sourceRoot":"","sources":["../../src/quixos/refs_pb.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,OACmjB,CAAC;AAEnlB;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,sBAAsB,CAAC,GAAG;IAC5D;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAAU,CAAC,aAAa,CACxB,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,yBAAyB,CAAC,GAAG;IAClE;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,UAAU,CAAC,gBAAgB,CAC9B,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC,GAAG;IACtE;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE;QACP;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,aAAa,CAAC;KACrB,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,cAAc,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,qBAAqB,CAAC;KAC7B,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,mBAAmB,CAAC;KAC3B,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;IAE3C;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,UAAU,CAAC,kBAAkB,CAClC,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,uBAAuB,CAAC,GAAG;IAC9D;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAAU,CAAC,cAAc,CAC1B,CAAC"}
|
{"version":3,"file":"refs_pb.d.ts","sourceRoot":"","sources":["../../src/quixos/refs_pb.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAC21B,CAAC;AAE33B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,sBAAsB,CAAC,GAAG;IAC5D;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CAC9C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAAU,CAAC,aAAa,CACxB,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC,GAAG;IACtE;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,UAAU,CAAC,kBAAkB,CAClC,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,yBAAyB,CAAC,GAAG;IAClE;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,UAAU,CAAC,gBAAgB,CAC9B,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC,GAAG;IACtE;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE;QACP;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,aAAa,CAAC;KACrB,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,cAAc,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,qBAAqB,CAAC;KAC7B,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,mBAAmB,CAAC;KAC3B,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,SAAS,CAAC;KACjB,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;IAE3C;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,UAAU,CAAC,kBAAkB,CAClC,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,uBAAuB,CAAC,GAAG;IAC9D;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAAU,CAAC,cAAc,CAC1B,CAAC"}
|
||||||
Vendored
+9
-4
@@ -5,24 +5,29 @@ import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
|
|||||||
/**
|
/**
|
||||||
* Describes the file quixos/refs.proto.
|
* Describes the file quixos/refs.proto.
|
||||||
*/
|
*/
|
||||||
export const file_quixos_refs = /*@__PURE__*/ fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zIkQKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIsQBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
|
export const file_quixos_refs = /*@__PURE__*/ fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3MilgEKEkNvbmZvcm1hbmNlV2l0bmVzcxIRCglvYmplY3RfaWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgEIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBSABKAkiQgoQUGFja2FnZUV4cG9ydFJlZhIbChNwYWNrYWdlX3JldmlzaW9uX2lkGAEgASgJEhEKCWV4cG9ydF9pZBgCIAEoCSLYAQoSSW5qZWN0ZWREZXBlbmRlbmN5Eg8KB3BvcnRfaWQYASABKAkSFwoNc3RhdGVfc2xvdF9pZBgCIAEoCUgAEiYKBGVkZ2UYAyABKAsyFi5xdWl4b3MuRWRnZURlcGVuZGVuY3lIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAlIABIdChNjb25zdHJ1Y3Rvcl9hdG9tX2lkGAUgASgJSAASEgoIcXVlcnlfaWQYByABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.CapabilityRef.
|
* Describes the message quixos.CapabilityRef.
|
||||||
* Use `create(CapabilityRefSchema)` to create a new message.
|
* Use `create(CapabilityRefSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const CapabilityRefSchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 0);
|
export const CapabilityRefSchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 0);
|
||||||
|
/**
|
||||||
|
* Describes the message quixos.ConformanceWitness.
|
||||||
|
* Use `create(ConformanceWitnessSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ConformanceWitnessSchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 1);
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.PackageExportRef.
|
* Describes the message quixos.PackageExportRef.
|
||||||
* Use `create(PackageExportRefSchema)` to create a new message.
|
* Use `create(PackageExportRefSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const PackageExportRefSchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 1);
|
export const PackageExportRefSchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 2);
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.InjectedDependency.
|
* Describes the message quixos.InjectedDependency.
|
||||||
* Use `create(InjectedDependencySchema)` to create a new message.
|
* Use `create(InjectedDependencySchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const InjectedDependencySchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 2);
|
export const InjectedDependencySchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 3);
|
||||||
/**
|
/**
|
||||||
* Describes the message quixos.EdgeDependency.
|
* Describes the message quixos.EdgeDependency.
|
||||||
* Use `create(EdgeDependencySchema)` to create a new message.
|
* Use `create(EdgeDependencySchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const EdgeDependencySchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 3);
|
export const EdgeDependencySchema = /*@__PURE__*/ messageDesc(file_quixos_refs, 4);
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,WAAW,WAAW,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;IAC3D,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE;QAAC,QAAQ,EAAE,CAAC,IAAI,QAAQ,GAAG,IAAI;KAAC,CAAC;IAC5D,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;CAC7C;AAQD,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,KAAK,IAAI,WACU,CAAC;AAEvE,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,OAAQ,MAAM,KAAG,WAG9C,CAAC;AACF,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,MAGhD,CAAC;AACF,eAAO,MAAM,mBAAmB,UAAW,OAAO,yBAA6B,IAO9E,CAAC"}
|
{"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,WAAW,WAAW,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;IAC3D,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,QAAQ,GAAG,IAAI;KAAE,CAAC;IAC9D,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;CAC7C;AAmBD,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,KAAK,IAAI,WACU,CAAC;AAEvE,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,OAAQ,MAAM,KAAG,WAG9C,CAAC;AACF,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,MAGhD,CAAC;AACF,eAAO,MAAM,mBAAmB,UAAW,OAAO,yBAA6B,IAU9E,CAAC"}
|
||||||
Vendored
+16
-5
@@ -2,11 +2,22 @@
|
|||||||
* the raw ID. These handles do not themselves confer authority or a lease. */
|
* the raw ID. These handles do not themselves confer authority or a lease. */
|
||||||
const identities = new WeakMap();
|
const identities = new WeakMap();
|
||||||
class Reference {
|
class Reference {
|
||||||
constructor(id) { identities.set(this, id); Object.freeze(this); }
|
constructor(id) {
|
||||||
equals(other) { return isObjectReference(other) && identities.get(this) === identities.get(other); }
|
identities.set(this, id);
|
||||||
toJSON() { throw new Error("Object references cannot be serialized into ordinary data"); }
|
Object.freeze(this);
|
||||||
toString() { throw new Error("Object references cannot be coerced to strings"); }
|
}
|
||||||
[Symbol.toPrimitive]() { throw new Error("Object references cannot be coerced to scalar values"); }
|
equals(other) {
|
||||||
|
return isObjectReference(other) && identities.get(this) === identities.get(other);
|
||||||
|
}
|
||||||
|
toJSON() {
|
||||||
|
throw new Error("Object references cannot be serialized into ordinary data");
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
throw new Error("Object references cannot be coerced to strings");
|
||||||
|
}
|
||||||
|
[Symbol.toPrimitive]() {
|
||||||
|
throw new Error("Object references cannot be coerced to scalar values");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export const isObjectReference = (value) => typeof value === "object" && value !== null && identities.has(value);
|
export const isObjectReference = (value) => typeof value === "object" && value !== null && identities.has(value);
|
||||||
/** Internal transport boundary; intentionally not exported from the SDK entry. */
|
/** Internal transport boundary; intentionally not exported from the SDK entry. */
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAC,sBAAsB,EAAE,iBAAiB,EAAC,MAAM,YAAY,CAAC;AAC1E,KAAK,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AACrC,KAAK,IAAI,CAAC,CAAC,SAAS,WAAW,IAAI;IAAC,UAAU,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAA;CAAC,CAAC;AAM9L,+EAA+E;AAC/E,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,GAAG,GAAG,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC;IACvF,IAAI;IACE,GAAG,MAAM,CAAC;;;;IACV,GAAG,MAAM,CAAC,UAAU,CAAC,oBAAoB,MAAM;IAO/C,MAAM,MAAM,CAAC,oBAAoB,MAAM;CAI7C,CAAC;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI;IACE,MAAM,QAAQ,MAAM,UAAU,CAAC,oBAAoB,MAAM;IAMzD,IAAI,SAAS,MAAM,SAAS,MAAM,oBAAoB,MAAM;IAQ5D,MAAM,SAAS,MAAM,oBAAoB,MAAM;CAKrD,CAAC;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC;IAClE,IAAI;IACE,GAAG,SAAS,CAAC,oBAAoB,MAAM;IAKvC,MAAM,SAAS,CAAC,oBAAoB,MAAM;CAIhD,CAAC"}
|
{"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC5E,KAAK,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AACrC,KAAK,IAAI,CAAC,CAAC,SAAS,WAAW,IAAI;IACjC,UAAU,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CACxG,CAAC;AAMF,+EAA+E;AAC/E,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,GAAG,GAAG,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC;IACvF,IAAI;IACE,GAAG,MAAM,CAAC;;;;IAIV,GAAG,MAAM,CAAC,UAAU,CAAC,oBAAoB,MAAM;IAO/C,MAAM,MAAM,CAAC,oBAAoB,MAAM;CAO7C,CAAC;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI;IACE,MAAM,QAAQ,MAAM,UAAU,CAAC,oBAAoB,MAAM;IAOzD,IAAI,SAAS,MAAM,SAAS,MAAM,oBAAoB,MAAM;IAS5D,MAAM,SAAS,MAAM,oBAAoB,MAAM;CAQrD,CAAC;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC;IAClE,IAAI;IACE,GAAG,SAAS,CAAC,oBAAoB,MAAM;IAKvC,MAAM,SAAS,CAAC,oBAAoB,MAAM;CAOhD,CAAC"}
|
||||||
Vendored
+4
-1
@@ -7,7 +7,10 @@ const checked = async (port, revision) => {
|
|||||||
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
|
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
|
||||||
export const relationshipMap = (port) => ({
|
export const relationshipMap = (port) => ({
|
||||||
read: () => port.collection(),
|
read: () => port.collection(),
|
||||||
async get(key) { const snapshot = await port.collection(); return { revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target }; },
|
async get(key) {
|
||||||
|
const snapshot = await port.collection();
|
||||||
|
return { revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target };
|
||||||
|
},
|
||||||
async set(key, target, expectedRevision) {
|
async set(key, target, expectedRevision) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
const entries = snapshot.entries.filter((entry) => entry.key !== key);
|
const entries = snapshot.entries.filter((entry) => entry.key !== key);
|
||||||
|
|||||||
Generated
+7
-7
@@ -74,17 +74,17 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1789328055,
|
"lastModified": 1789699975,
|
||||||
"narHash": "sha256-tZVGOpDnT80j3roNQXpCxw8BxNZxqVutclxr49Vhg4w=",
|
"narHash": "sha256-OG2ebD1FAzPPXjgFcH0Bd+DR/pS2YL3zlaIQ9XUCpJg=",
|
||||||
"ref": "refs/tags/quixos-reachability/9867bf4552c09ee71ebeefe08e652fd870894130",
|
"ref": "refs/tags/quixos-reachability/93c8cae1e651fa6a98ab5aa26e9ed2696c0bfd05",
|
||||||
"rev": "9867bf4552c09ee71ebeefe08e652fd870894130",
|
"rev": "93c8cae1e651fa6a98ab5aa26e9ed2696c0bfd05",
|
||||||
"revCount": 45,
|
"revCount": 88,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"ref": "refs/tags/quixos-reachability/9867bf4552c09ee71ebeefe08e652fd870894130",
|
"ref": "refs/tags/quixos-reachability/93c8cae1e651fa6a98ab5aa26e9ed2696c0bfd05",
|
||||||
"rev": "9867bf4552c09ee71ebeefe08e652fd870894130",
|
"rev": "93c8cae1e651fa6a98ab5aa26e9ed2696c0bfd05",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,20 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/tags/quixos-reachability/9867bf4552c09ee71ebeefe08e652fd870894130&rev=9867bf4552c09ee71ebeefe08e652fd870894130";
|
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/tags/quixos-reachability/93c8cae1e651fa6a98ab5aa26e9ed2696c0bfd05&rev=93c8cae1e651fa6a98ab5aa26e9ed2696c0bfd05";
|
||||||
quixosNixHelpers = {
|
quixosNixHelpers = {
|
||||||
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/7177130c0365f2fa58ea4877366e1c5d17db4c01&rev=7177130c0365f2fa58ea4877366e1c5d17db4c01";
|
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/7177130c0365f2fa58ea4877366e1c5d17db4c01&rev=7177130c0365f2fa58ea4877366e1c5d17db4c01";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ nixpkgs, flake-utils, quixosNixHelpers, ... }:
|
outputs =
|
||||||
|
inputs@{
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
quixosNixHelpers,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
quixosHelpers = import "${quixosNixHelpers}/quixos-package-helpers.nix";
|
quixosHelpers = import "${quixosNixHelpers}/quixos-package-helpers.nix";
|
||||||
packageOutputs = quixosHelpers.mkCaminoTsYarnNixifyFlake {
|
packageOutputs = quixosHelpers.mkCaminoTsYarnNixifyFlake {
|
||||||
@@ -22,18 +28,37 @@
|
|||||||
nativeBuildInputs = { pkgs, ... }: [
|
nativeBuildInputs = { pkgs, ... }: [
|
||||||
pkgs.protobuf
|
pkgs.protobuf
|
||||||
];
|
];
|
||||||
buildEnv = { inputs, pkgs, system }: {
|
buildEnv =
|
||||||
QUIXOS_PROTO_PATH = "${pkgs.protobuf}/include:${inputs.quixos-protocol.packages.${system}.default}/proto";
|
{
|
||||||
|
inputs,
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
QUIXOS_PROTO_PATH = "${pkgs.protobuf}/include:${
|
||||||
|
inputs.quixos-protocol.packages.${system}.default
|
||||||
|
}/proto";
|
||||||
};
|
};
|
||||||
devShellPackages = { pkgs, ... }: [
|
devShellPackages = { pkgs, ... }: [
|
||||||
pkgs.protobuf
|
pkgs.protobuf
|
||||||
];
|
];
|
||||||
devShellHook = { inputs, pkgs, system, ... }: ''
|
devShellHook =
|
||||||
export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:${inputs.quixos-protocol.packages.${system}.default}/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}"
|
{
|
||||||
|
inputs,
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
''
|
||||||
|
export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:${
|
||||||
|
inputs.quixos-protocol.packages.${system}.default
|
||||||
|
}/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
packageOutputs // flake-utils.lib.eachDefaultSystem (system:
|
packageOutputs
|
||||||
|
// flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
builtPackage = packageOutputs.packages.${system}.default;
|
builtPackage = packageOutputs.packages.${system}.default;
|
||||||
@@ -45,5 +70,6 @@
|
|||||||
${builtPackage}/libexec/-quixos-camino-package-runtime/dist
|
${builtPackage}/libexec/-quixos-camino-package-runtime/dist
|
||||||
touch "$out"
|
touch "$out"
|
||||||
'';
|
'';
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+199
-57
@@ -1,32 +1,44 @@
|
|||||||
import { create } from "@bufbuild/protobuf";
|
import { create } from "@bufbuild/protobuf";
|
||||||
import { ValueSchema, ObjectValueSchema, type Value } from "./camino/api_pb.js";
|
import { ValueSchema, ObjectValueSchema, type Value } from "./camino/api_pb.js";
|
||||||
import { derived, jsToProtoValue, liveValue, protoValueToJs,
|
import {
|
||||||
type RuntimeContext, type RuntimeHandler, type DerivedHandler } from "./index.js";
|
derived,
|
||||||
|
jsToProtoValue,
|
||||||
|
liveValue,
|
||||||
|
protoValueToJs,
|
||||||
|
type RuntimeContext,
|
||||||
|
type RuntimeHandler,
|
||||||
|
type DerivedHandler,
|
||||||
|
} from "./index.js";
|
||||||
|
|
||||||
export type { QxObjectRef } from "./references.js";
|
export type { QxObjectRef } from "./references.js";
|
||||||
import { assertReferenceFree, referenceToWire } from "./references.js";
|
import { assertReferenceFree, referenceToWire } from "./references.js";
|
||||||
|
import { decodeQuerySnapshot } from "./queries.js";
|
||||||
declare const watchBrand: unique symbol;
|
declare const watchBrand: unique symbol;
|
||||||
export type QxWatchHandle = string & { readonly [watchBrand]: true };
|
export type QxWatchHandle = string & { readonly [watchBrand]: true };
|
||||||
export type MessageBinding<T> = { encode(value: T): Value; decode(value: Value): T };
|
export type MessageBinding<T> = { encode(value: T): Value; decode(value: Value): T };
|
||||||
export type QxLiveValue = ReturnType<typeof liveValue>;
|
export type QxLiveValue = ReturnType<typeof liveValue>;
|
||||||
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: MessageBinding<Record<string, unknown>> = {
|
|
||||||
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) as Record<string, unknown>;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
|
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
|
||||||
export type QxHandler<C, O> = (context: C) => O | Promise<O>;
|
export type QxHandler<C, O> = (context: C) => O | Promise<O>;
|
||||||
export type QxDerived<C, O> = { kind: "derived"; get: QxHandler<C, O> };
|
export type QxDerived<C, O> = { kind: "derived"; get: QxHandler<C, O> };
|
||||||
export type QxSession<C> = { id: string; run<T>(work: (context: C) => Promise<T>): Promise<T>; close(): Promise<void> };
|
export type QxSession<C> = { id: string; run<T>(work: (context: C) => Promise<T>): Promise<T>; close(): Promise<void> };
|
||||||
export type QxContextLifecycle<C> = { signal?: AbortSignal; openSession?: () => Promise<QxSession<C>> };
|
export type QxContextLifecycle<C> = { signal?: AbortSignal; openSession?: () => Promise<QxSession<C>> };
|
||||||
|
declare const contractView: unique symbol;
|
||||||
|
/** Generated exact closed contract. A descriptor is type evidence, never authority. */
|
||||||
|
export type QxInterfaceContract<View> = {
|
||||||
|
readonly interfaceRevisionId: string;
|
||||||
|
readonly operations: Record<string, QxOperationSpec>;
|
||||||
|
readonly [contractView]: (value: View) => View;
|
||||||
|
};
|
||||||
|
export const defineQxInterfaceContract = <View>(
|
||||||
|
interfaceRevisionId: string,
|
||||||
|
operations: Record<string, QxOperationSpec>,
|
||||||
|
): QxInterfaceContract<View> => Object.freeze({ interfaceRevisionId, operations }) as QxInterfaceContract<View>;
|
||||||
|
export type QxConformer = {
|
||||||
|
tryConform<View>(
|
||||||
|
object: import("./references.js").QxObjectRef,
|
||||||
|
contract: QxInterfaceContract<View>,
|
||||||
|
): Promise<View | undefined>;
|
||||||
|
};
|
||||||
export const qxDerived = <C, O>(get: QxHandler<C, O>): QxDerived<C, O> => ({ kind: "derived", get });
|
export const qxDerived = <C, O>(get: QxHandler<C, O>): QxDerived<C, O> => ({ kind: "derived", get });
|
||||||
|
|
||||||
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
|
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
|
||||||
@@ -39,16 +51,46 @@ export type QxValueType =
|
|||||||
| { kind: "optional" | "list"; value: QxValueType };
|
| { kind: "optional" | "list"; value: QxValueType };
|
||||||
export type QxOperationSpec = { id: string; inputType: QxValueType; outputType: QxValueType };
|
export type QxOperationSpec = { id: string; inputType: QxValueType; outputType: QxValueType };
|
||||||
export type QxPortSpec =
|
export type QxPortSpec =
|
||||||
|
| { kind: "query"; id: string; definitionDigest: string; variables: QxValueType; output: QxValueType; watch: boolean }
|
||||||
| { kind: "state"; id: string; valueType: QxValueType; primitives: string[] }
|
| { kind: "state"; id: string; valueType: QxValueType; primitives: string[] }
|
||||||
| { kind: "edge"; id: string; primitives: string[] }
|
| { kind: "edge"; id: string; primitives: string[] }
|
||||||
| { kind: "interface"; id: string; operations: Record<string, QxOperationSpec> }
|
| { kind: "interface"; id: string; interfaceRevisionId: string; operations: Record<string, QxOperationSpec> }
|
||||||
| { kind: "constructor"; id: string; inputType: QxValueType };
|
| { kind: "constructor"; id: string; inputType: QxValueType };
|
||||||
export type QxHandlerSpec = {
|
export type QxHandlerSpec = {
|
||||||
inputType: QxValueType; outputType: QxValueType; eventType?: QxValueType;
|
receiver?: "none";
|
||||||
|
inputType: QxValueType;
|
||||||
|
outputType: QxValueType;
|
||||||
|
eventType?: QxValueType;
|
||||||
ports: Record<string, QxPortSpec>;
|
ports: Record<string, QxPortSpec>;
|
||||||
};
|
};
|
||||||
export type QxMessages = Record<string, MessageBinding<any>>;
|
export type QxMessages = Record<string, MessageBinding<any>>;
|
||||||
|
|
||||||
|
const bindInterfaceView = (
|
||||||
|
target: import("./index.js").InterfacePort,
|
||||||
|
contract: QxInterfaceContract<unknown>,
|
||||||
|
messages: QxMessages,
|
||||||
|
) => ({
|
||||||
|
objectId: target.objectId,
|
||||||
|
contract,
|
||||||
|
live: Object.fromEntries(
|
||||||
|
Object.entries(contract.operations).map(([name, operation]) => [
|
||||||
|
name,
|
||||||
|
(input: unknown) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(contract.operations).map(([name, operation]) => [
|
||||||
|
name,
|
||||||
|
async (input: unknown) =>
|
||||||
|
decodeQxValue(
|
||||||
|
operation.outputType,
|
||||||
|
(await target.live(operation.id, inputFields(operation.inputType, input, messages))).$quixosValue,
|
||||||
|
messages,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
|
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
|
||||||
export const decodeQxValue = (type: QxValueType, value: Value | undefined, messages: QxMessages): any => {
|
export const decodeQxValue = (type: QxValueType, value: Value | undefined, messages: QxMessages): any => {
|
||||||
if (type.kind === "builtin" && type.name === "unit") return null;
|
if (type.kind === "builtin" && type.name === "unit") return null;
|
||||||
@@ -57,18 +99,22 @@ export const decodeQxValue = (type: QxValueType, value: Value | undefined, messa
|
|||||||
if (type.kind === "record") {
|
if (type.kind === "record") {
|
||||||
if (value.kind.case !== "objectValue") throw new Error("Expected QX record");
|
if (value.kind.case !== "objectValue") throw new Error("Expected QX record");
|
||||||
const fields = value.kind.value.fields;
|
const fields = value.kind.value.fields;
|
||||||
if (Object.keys(fields).some((name) => !Object.hasOwn(type.fields, name))) throw new Error("Unexpected QX record field");
|
if (Object.keys(fields).some((name) => !Object.hasOwn(type.fields, name)))
|
||||||
return Object.fromEntries(Object.entries(type.fields).map(([name, field]) => [name, decodeQxValue(field, fields[name], messages)]));
|
throw new Error("Unexpected QX record field");
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(type.fields).map(([name, field]) => [name, decodeQxValue(field, fields[name], messages)]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (type.kind === "optional") return value.kind.case === "nullValue" ? null : decodeQxValue(type.value, value, messages);
|
if (type.kind === "optional")
|
||||||
|
return value.kind.case === "nullValue" ? null : decodeQxValue(type.value, value, messages);
|
||||||
if (type.kind === "list") {
|
if (type.kind === "list") {
|
||||||
if (value.kind.case !== "listValue") throw new Error("Expected QX list");
|
if (value.kind.case !== "listValue") throw new Error("Expected QX list");
|
||||||
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
|
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
|
||||||
}
|
}
|
||||||
if (type.kind === "message") {
|
if (type.kind === "message") {
|
||||||
if (type.descriptorId !== reactPropsDescriptor) assertReferenceFree(protoValueToJs(value));
|
assertReferenceFree(protoValueToJs(value));
|
||||||
const decoded = requireMessage(messages, type.descriptorId).decode(value);
|
const decoded = requireMessage(messages, type.descriptorId).decode(value);
|
||||||
if (type.descriptorId !== reactPropsDescriptor) assertReferenceFree(decoded);
|
assertReferenceFree(decoded);
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
if (type.kind === "object-ref") {
|
if (type.kind === "object-ref") {
|
||||||
@@ -98,28 +144,39 @@ export const encodeQxValue = (type: QxValueType, value: any, messages: QxMessage
|
|||||||
if (type.kind === "builtin" && type.name === "unit") return jsToProtoValue(null);
|
if (type.kind === "builtin" && type.name === "unit") return jsToProtoValue(null);
|
||||||
if (type.kind === "record") {
|
if (type.kind === "record") {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected QX record");
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected QX record");
|
||||||
if (Object.keys(value).some((name) => !Object.hasOwn(type.fields, name))) throw new Error("Unexpected QX record field");
|
if (Object.keys(value).some((name) => !Object.hasOwn(type.fields, name)))
|
||||||
const fields = Object.fromEntries(Object.entries(type.fields).map(([name, field]) => {
|
throw new Error("Unexpected QX record field");
|
||||||
if (!Object.hasOwn(value, name) && field.kind !== "optional") throw new Error(`Missing QX record field ${name}`);
|
const fields = Object.fromEntries(
|
||||||
|
Object.entries(type.fields).map(([name, field]) => {
|
||||||
|
if (!Object.hasOwn(value, name) && field.kind !== "optional")
|
||||||
|
throw new Error(`Missing QX record field ${name}`);
|
||||||
return [name, encodeQxValue(field, value[name] ?? (field.kind === "optional" ? null : value[name]), messages)];
|
return [name, encodeQxValue(field, value[name] ?? (field.kind === "optional" ? null : value[name]), messages)];
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
return create(ValueSchema, { kind: { case: "objectValue", value: create(ObjectValueSchema, { fields }) } });
|
return create(ValueSchema, { kind: { case: "objectValue", value: create(ObjectValueSchema, { fields }) } });
|
||||||
}
|
}
|
||||||
if (type.kind === "optional") return value === null ? jsToProtoValue(null) : encodeQxValue(type.value, value, messages);
|
if (type.kind === "optional")
|
||||||
if (type.kind === "list") return jsToProtoValue(value.map((entry: unknown) => liveValue(encodeQxValue(type.value, entry, messages))));
|
return value === null ? jsToProtoValue(null) : encodeQxValue(type.value, value, messages);
|
||||||
if (type.kind === "object-ref") { referenceToWire(value); return jsToProtoValue(value); }
|
if (type.kind === "list")
|
||||||
if (type.kind !== "message" || type.descriptorId !== reactPropsDescriptor) assertReferenceFree(value);
|
return jsToProtoValue(value.map((entry: unknown) => liveValue(encodeQxValue(type.value, entry, messages))));
|
||||||
|
if (type.kind === "object-ref") {
|
||||||
|
referenceToWire(value);
|
||||||
|
return jsToProtoValue(value);
|
||||||
|
}
|
||||||
|
assertReferenceFree(value);
|
||||||
if (type.kind === "message") {
|
if (type.kind === "message") {
|
||||||
const encoded = requireMessage(messages, type.descriptorId).encode(value);
|
const encoded = requireMessage(messages, type.descriptorId).encode(value);
|
||||||
if (type.descriptorId !== reactPropsDescriptor) assertReferenceFree(protoValueToJs(encoded));
|
assertReferenceFree(protoValueToJs(encoded));
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
return jsToProtoValue(value);
|
return jsToProtoValue(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputValue = (context: RuntimeContext, type: QxValueType) => {
|
const inputValue = (context: RuntimeContext, type: QxValueType) => {
|
||||||
if (type.kind === "message" || type.kind === "record") return create(ValueSchema, { kind: { case: "objectValue",
|
if (type.kind === "message" || type.kind === "record")
|
||||||
value: create(ObjectValueSchema, { fields: context.inputProto }) } });
|
return create(ValueSchema, {
|
||||||
|
kind: { case: "objectValue", value: create(ObjectValueSchema, { fields: context.inputProto }) },
|
||||||
|
});
|
||||||
return context.inputProto.value;
|
return context.inputProto.value;
|
||||||
};
|
};
|
||||||
const inputFields = (type: QxValueType, value: unknown, messages: QxMessages): Record<string, unknown> => {
|
const inputFields = (type: QxValueType, value: unknown, messages: QxMessages): Record<string, unknown> => {
|
||||||
@@ -134,45 +191,130 @@ const inputFields = (type: QxValueType, value: unknown, messages: QxMessages): R
|
|||||||
|
|
||||||
/** The sole unchecked cast connects generated contracts to the dynamic RPC runtime. */
|
/** The sole unchecked cast connects generated contracts to the dynamic RPC runtime. */
|
||||||
export const bindQxHandler = <C, O>(
|
export const bindQxHandler = <C, O>(
|
||||||
spec: QxHandlerSpec, handler: QxHandler<C, O> | QxDerived<C, O>, messages: QxMessages,
|
spec: QxHandlerSpec,
|
||||||
|
handler: QxHandler<C, O> | QxDerived<C, O>,
|
||||||
|
messages: QxMessages,
|
||||||
): RuntimeHandler | DerivedHandler => {
|
): RuntimeHandler | DerivedHandler => {
|
||||||
const bindContext = (raw: RuntimeContext): C => {
|
const bindContext = (raw: RuntimeContext): C => {
|
||||||
const ports = Object.fromEntries(Object.entries(spec.ports).map(([name, port]) => {
|
const ports = Object.fromEntries(
|
||||||
|
Object.entries(spec.ports).map(([name, port]) => {
|
||||||
switch (port.kind) {
|
switch (port.kind) {
|
||||||
case "state": {
|
case "state": {
|
||||||
const state = raw.state(port.id);
|
const state = raw.state(port.id);
|
||||||
return [name, {
|
return [
|
||||||
...(port.primitives.includes("read") ? { get: async () => decodeQxValue(port.valueType, (await state.live()).$quixosValue, messages), live: () => state.live() } : {}),
|
name,
|
||||||
...(port.primitives.includes("write") ? { set: async (value: unknown) => state.set(liveValue(encodeQxValue(port.valueType, value, 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: unknown) =>
|
||||||
|
state.set(liveValue(encodeQxValue(port.valueType, value, messages))),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
case "edge": {
|
case "edge": {
|
||||||
const edge = raw.edge(port.id);
|
const edge = raw.edge(port.id);
|
||||||
return [name, {...Object.fromEntries(port.primitives.map((primitive) => [primitive, edge[primitive as "resolve" | "connect" | "disconnect"]])),
|
return [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
...Object.fromEntries(
|
||||||
|
port.primitives.map((primitive) => [
|
||||||
|
primitive,
|
||||||
|
edge[primitive as "resolve" | "connect" | "disconnect"],
|
||||||
|
]),
|
||||||
|
),
|
||||||
...(port.primitives.includes("resolve") ? { collection: edge.collection } : {}),
|
...(port.primitives.includes("resolve") ? { collection: edge.collection } : {}),
|
||||||
...(port.primitives.includes("resolve") && port.primitives.includes("connect") && port.primitives.includes("disconnect") ? {replace: edge.replace} : {})}];
|
...(port.primitives.includes("resolve") &&
|
||||||
|
port.primitives.includes("connect") &&
|
||||||
|
port.primitives.includes("disconnect")
|
||||||
|
? { replace: edge.replace }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
case "interface": {
|
case "interface": {
|
||||||
const target = raw.interface(port.id);
|
const target = raw.interface(port.id);
|
||||||
return [name, {objectId: target.objectId,
|
return [
|
||||||
live: Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
|
name,
|
||||||
(input: unknown) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
|
bindInterfaceView(target, defineQxInterfaceContract(port.interfaceRevisionId, port.operations), messages),
|
||||||
])),
|
];
|
||||||
...Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
|
|
||||||
async (input: unknown) => decodeQxValue(operation.outputType,
|
|
||||||
(await target.live(operation.id, inputFields(operation.inputType, input, messages))).$quixosValue, messages),
|
|
||||||
]))}];
|
|
||||||
}
|
}
|
||||||
case "constructor": return [name, { construct: (input: unknown) => raw.constructor(port.id).construct(inputFields(port.inputType, input, messages)) }];
|
case "query": {
|
||||||
|
const query = raw.query(port.id);
|
||||||
|
const variablesToWire = (variables: unknown) => {
|
||||||
|
const value = encodeQxValue(port.variables, variables, messages);
|
||||||
|
if (value.kind.case !== "objectValue") throw new Error("QUERY_VARIABLE_INVALID");
|
||||||
|
return value.kind.value.fields;
|
||||||
|
};
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
async execute(variables: unknown) {
|
||||||
|
const response = await query.execute(variablesToWire(variables), port.definitionDigest);
|
||||||
|
if (response.pending.length || response.errors.length) throw new Error("QUERY_INCOMPLETE");
|
||||||
|
return decodeQxValue(port.output, response.value, messages);
|
||||||
|
},
|
||||||
|
...(port.watch
|
||||||
|
? {
|
||||||
|
async *watch(variables: unknown, signal: AbortSignal) {
|
||||||
|
for await (const event of query.watch(
|
||||||
|
variablesToWire(variables),
|
||||||
|
signal,
|
||||||
|
port.definitionDigest,
|
||||||
|
)) {
|
||||||
|
if (!event.snapshot) throw new Error("QUERY_SNAPSHOT_MISSING");
|
||||||
|
yield decodeQuerySnapshot(event.snapshot, port.output, event.runId, event.sequence);
|
||||||
}
|
}
|
||||||
}));
|
},
|
||||||
return { objectId: raw.objectId, signal: raw.signal,
|
}
|
||||||
...(raw.openSession ? {openSession: async () => {
|
: {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "constructor":
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
construct: (input: unknown) =>
|
||||||
|
raw.constructor(port.id).construct(inputFields(port.inputType, input, messages)),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
conform: {
|
||||||
|
async tryConform<View>(object: import("./references.js").QxObjectRef, contract: QxInterfaceContract<View>) {
|
||||||
|
const target = await raw.tryConform(object, contract.interfaceRevisionId);
|
||||||
|
return target
|
||||||
|
? (bindInterfaceView(target, contract as QxInterfaceContract<unknown>, messages) as View)
|
||||||
|
: undefined;
|
||||||
|
},
|
||||||
|
} satisfies QxConformer,
|
||||||
|
...(spec.receiver === "none" ? {} : { objectId: raw.objectId }),
|
||||||
|
signal: raw.signal,
|
||||||
|
...(spec.receiver !== "none" && raw.openSession
|
||||||
|
? {
|
||||||
|
openSession: async () => {
|
||||||
const session = await raw.openSession!();
|
const session = await raw.openSession!();
|
||||||
return {id: session.id, close: () => session.close(),
|
return {
|
||||||
run: <T>(work: (context: C) => Promise<T>) => session.run((next) => work(bindContext(next)))};
|
id: session.id,
|
||||||
}} : {}),
|
close: () => session.close(),
|
||||||
input: decodeQxValue(spec.inputType, inputValue(raw, spec.inputType), messages), ports } as C;
|
run: <T>(work: (context: C) => Promise<T>) => session.run((next) => work(bindContext(next))),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
input: decodeQxValue(spec.inputType, inputValue(raw, spec.inputType), messages),
|
||||||
|
ports,
|
||||||
|
} as C;
|
||||||
};
|
};
|
||||||
const execute = async (raw: RuntimeContext) => {
|
const execute = async (raw: RuntimeContext) => {
|
||||||
const context = bindContext(raw);
|
const context = bindContext(raw);
|
||||||
|
|||||||
+594
-2
File diff suppressed because one or more lines are too long
+1217
-1
File diff suppressed because one or more lines are too long
+314
-121
@@ -1,12 +1,27 @@
|
|||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { createInvocationRegistry } from "./invocations.js";
|
import { createInvocationRegistry } from "./invocations.js";
|
||||||
import { isObjectReference, referenceFromWire, referenceToWire, assertReferenceFree, type QxObjectRef } from "./references.js";
|
import {
|
||||||
|
isObjectReference,
|
||||||
|
referenceFromWire,
|
||||||
|
referenceToWire,
|
||||||
|
assertReferenceFree,
|
||||||
|
type QxObjectRef,
|
||||||
|
} from "./references.js";
|
||||||
export * from "./bindings.js";
|
export * from "./bindings.js";
|
||||||
|
export * from "./queries.js";
|
||||||
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
||||||
import { AsyncLocalStorage } from "node:async_hooks";
|
import { AsyncLocalStorage } from "node:async_hooks";
|
||||||
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
||||||
export {createMigrationContext, migrationObjectId, serveMigration, type MigrationContext, type MigrationInput, type MigrationOutput, type MigrationEdge} from "./migration.js";
|
export {
|
||||||
|
createMigrationContext,
|
||||||
|
migrationObjectId,
|
||||||
|
serveMigration,
|
||||||
|
type MigrationContext,
|
||||||
|
type MigrationInput,
|
||||||
|
type MigrationOutput,
|
||||||
|
type MigrationEdge,
|
||||||
|
} from "./migration.js";
|
||||||
import { create, equals } from "@bufbuild/protobuf";
|
import { create, equals } from "@bufbuild/protobuf";
|
||||||
import { Code, ConnectError, createClient, type Client, type ConnectRouter } from "@connectrpc/connect";
|
import { Code, ConnectError, createClient, type Client, type ConnectRouter } from "@connectrpc/connect";
|
||||||
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
|
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
|
||||||
@@ -19,6 +34,7 @@ import {
|
|||||||
RefValueSchema,
|
RefValueSchema,
|
||||||
ValueSchema,
|
ValueSchema,
|
||||||
type Value,
|
type Value,
|
||||||
|
type QueryResponse,
|
||||||
} from "./camino/api_pb.js";
|
} from "./camino/api_pb.js";
|
||||||
import { OrchestratorRuntime } from "./quixos/orch_pb.js";
|
import { OrchestratorRuntime } from "./quixos/orch_pb.js";
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +44,7 @@ import {
|
|||||||
PackageRuntime,
|
PackageRuntime,
|
||||||
WatchEventSchema,
|
WatchEventSchema,
|
||||||
} from "./quixos/runtime_pb.js";
|
} from "./quixos/runtime_pb.js";
|
||||||
import { CapabilityRefSchema } from "./quixos/refs_pb.js";
|
import { CapabilityRefSchema, type ConformanceWitness } from "./quixos/refs_pb.js";
|
||||||
|
|
||||||
export type CaminoClient = Client<typeof CaminoService>;
|
export type CaminoClient = Client<typeof CaminoService>;
|
||||||
export type OrchClient = Client<typeof OrchestratorRuntime>;
|
export type OrchClient = Client<typeof OrchestratorRuntime>;
|
||||||
@@ -63,14 +79,22 @@ const base64ToBytes = (value: string) => Buffer.from(value, "base64");
|
|||||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||||
const isWrappedValue = (value: unknown): value is { $quixosValue: Value } =>
|
const isWrappedValue = (value: unknown): value is { $quixosValue: Value } =>
|
||||||
isRecord(value) && "$quixosValue" in value &&
|
isRecord(value) &&
|
||||||
isRecord(value.$quixosValue) && value.$quixosValue.$typeName === "camino.Value";
|
"$quixosValue" in value &&
|
||||||
|
isRecord(value.$quixosValue) &&
|
||||||
|
value.$quixosValue.$typeName === "camino.Value";
|
||||||
|
|
||||||
export const objectRef = (reference: QxObjectRef) => { referenceToWire(reference); return reference; };
|
export const objectRef = (reference: QxObjectRef) => {
|
||||||
|
referenceToWire(reference);
|
||||||
|
return reference;
|
||||||
|
};
|
||||||
export const liveValue = (value: Value) => ({ $quixosValue: value });
|
export const liveValue = (value: Value) => ({ $quixosValue: value });
|
||||||
|
|
||||||
export const jsToProtoValue = (value: unknown): Value => {
|
export const jsToProtoValue = (value: unknown): Value => {
|
||||||
if (isObjectReference(value)) return create(ValueSchema, {kind: {case: "refValue", value: create(RefValueSchema, {objectId: referenceToWire(value)})}});
|
if (isObjectReference(value))
|
||||||
|
return create(ValueSchema, {
|
||||||
|
kind: { case: "refValue", value: create(RefValueSchema, { objectId: referenceToWire(value) }) },
|
||||||
|
});
|
||||||
if (isWrappedValue(value)) return value.$quixosValue;
|
if (isWrappedValue(value)) return value.$quixosValue;
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return create(ValueSchema, { kind: { case: "nullValue", value: create(NullValueSchema, {}) } });
|
return create(ValueSchema, { kind: { case: "nullValue", value: create(NullValueSchema, {}) } });
|
||||||
@@ -86,39 +110,51 @@ export const jsToProtoValue = (value: unknown): Value => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isRecord(value) && "$quixosRef" in value) throw new Error("Raw ID wrappers are not object references");
|
if (isRecord(value) && "$quixosRef" in value) throw new Error("Raw ID wrappers are not object references");
|
||||||
if (isRecord(value) && typeof value.$quixosCrdtType === "string" &&
|
if (isRecord(value) && typeof value.$quixosCrdtType === "string" && typeof value.$quixosCrdtPayload === "string") {
|
||||||
typeof value.$quixosCrdtPayload === "string") {
|
|
||||||
return create(ValueSchema, {
|
return create(ValueSchema, {
|
||||||
kind: { case: "crdtValue", value: create(CrdtValueSchema, {
|
kind: {
|
||||||
|
case: "crdtValue",
|
||||||
|
value: create(CrdtValueSchema, {
|
||||||
type: value.$quixosCrdtType,
|
type: value.$quixosCrdtType,
|
||||||
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
|
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
|
||||||
payload: base64ToBytes(value.$quixosCrdtPayload),
|
payload: base64ToBytes(value.$quixosCrdtPayload),
|
||||||
}) },
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!isRecord(value)) throw new Error(`Unsupported runtime value ${typeof value}`);
|
if (!isRecord(value)) throw new Error(`Unsupported runtime value ${typeof value}`);
|
||||||
return create(ValueSchema, {
|
return create(ValueSchema, {
|
||||||
kind: { case: "objectValue", value: create(ObjectValueSchema, {
|
kind: {
|
||||||
|
case: "objectValue",
|
||||||
|
value: create(ObjectValueSchema, {
|
||||||
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
|
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
|
||||||
}) },
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const protoValueToJs = (value: Value | undefined): unknown => {
|
export const protoValueToJs = (value: Value | undefined): unknown => {
|
||||||
switch (value?.kind.case) {
|
switch (value?.kind.case) {
|
||||||
case "nullValue":
|
case "nullValue":
|
||||||
case undefined: return null;
|
case undefined:
|
||||||
|
return null;
|
||||||
case "boolValue":
|
case "boolValue":
|
||||||
case "numberValue":
|
case "numberValue":
|
||||||
case "stringValue":
|
case "stringValue":
|
||||||
case "integerValue": return value.kind.value;
|
case "integerValue":
|
||||||
case "bytesValue": return bytesToBase64(value.kind.value);
|
return value.kind.value;
|
||||||
case "refValue": return referenceFromWire(value.kind.value.objectId);
|
case "bytesValue":
|
||||||
case "listValue": return value.kind.value.values.map(protoValueToJs);
|
return bytesToBase64(value.kind.value);
|
||||||
case "objectValue": return Object.fromEntries(
|
case "refValue":
|
||||||
|
return referenceFromWire(value.kind.value.objectId);
|
||||||
|
case "listValue":
|
||||||
|
return value.kind.value.values.map(protoValueToJs);
|
||||||
|
case "objectValue":
|
||||||
|
return Object.fromEntries(
|
||||||
Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]),
|
Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]),
|
||||||
);
|
);
|
||||||
case "crdtValue": return {
|
case "crdtValue":
|
||||||
|
return {
|
||||||
$quixosCrdtType: value.kind.value.type,
|
$quixosCrdtType: value.kind.value.type,
|
||||||
$quixosCrdtEncoding: value.kind.value.encoding,
|
$quixosCrdtEncoding: value.kind.value.encoding,
|
||||||
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
|
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
|
||||||
@@ -144,8 +180,15 @@ export type EdgePort = {
|
|||||||
collection(): Promise<RelationshipCollection>;
|
collection(): Promise<RelationshipCollection>;
|
||||||
replace(entries: RelationshipEntry[], expectedRevision: bigint): Promise<RelationshipCollection>;
|
replace(entries: RelationshipEntry[], expectedRevision: bigint): Promise<RelationshipCollection>;
|
||||||
};
|
};
|
||||||
export type RelationshipEntry<T extends QxObjectRef = QxObjectRef> = {edgeId?: string; target: T; key?: string | boolean | bigint};
|
export type RelationshipEntry<T extends QxObjectRef = QxObjectRef> = {
|
||||||
export type RelationshipCollection<T extends QxObjectRef = QxObjectRef> = {revision: bigint; entries: RelationshipEntry<T>[]};
|
edgeId?: string;
|
||||||
|
target: T;
|
||||||
|
key?: string | boolean | bigint;
|
||||||
|
};
|
||||||
|
export type RelationshipCollection<T extends QxObjectRef = QxObjectRef> = {
|
||||||
|
revision: bigint;
|
||||||
|
entries: RelationshipEntry<T>[];
|
||||||
|
};
|
||||||
export type InterfacePort = {
|
export type InterfacePort = {
|
||||||
objectId: QxObjectRef;
|
objectId: QxObjectRef;
|
||||||
interfaceRevisionId: string;
|
interfaceRevisionId: string;
|
||||||
@@ -156,9 +199,19 @@ export type ConstructorPort = {
|
|||||||
atomId: string;
|
atomId: string;
|
||||||
construct(input?: Record<string, unknown>): Promise<QxObjectRef>;
|
construct(input?: Record<string, unknown>): Promise<QxObjectRef>;
|
||||||
};
|
};
|
||||||
export type RuntimePort = StatePort | EdgePort | InterfacePort | ConstructorPort;
|
export type QueryPort = {
|
||||||
|
queryId: string;
|
||||||
|
execute(variables: Record<string, Value>, expectedDefinitionDigest?: string): Promise<QueryResponse>;
|
||||||
|
watch(
|
||||||
|
variables: Record<string, Value>,
|
||||||
|
signal: AbortSignal,
|
||||||
|
expectedDefinitionDigest?: string,
|
||||||
|
): AsyncIterable<import("./quixos/orch_pb.js").QueryEvent>;
|
||||||
|
};
|
||||||
|
export type RuntimePort = StatePort | EdgePort | InterfacePort | ConstructorPort | QueryPort;
|
||||||
|
|
||||||
export type RuntimeContext = {
|
export type RuntimeContext = {
|
||||||
|
tryConform(object: QxObjectRef, interfaceRevisionId: string): Promise<InterfacePort | undefined>;
|
||||||
/** Cooperative cancellation. Completion is acknowledged only after the handler returns. */
|
/** Cooperative cancellation. Completion is acknowledged only after the handler returns. */
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
openSession?: () => Promise<RuntimeSession>;
|
openSession?: () => Promise<RuntimeSession>;
|
||||||
@@ -170,6 +223,7 @@ export type RuntimeContext = {
|
|||||||
edge(portId: string): EdgePort;
|
edge(portId: string): EdgePort;
|
||||||
interface(portId: string): InterfacePort;
|
interface(portId: string): InterfacePort;
|
||||||
constructor(portId: string): ConstructorPort;
|
constructor(portId: string): ConstructorPort;
|
||||||
|
query(portId: string): QueryPort;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RuntimeSession = {
|
export type RuntimeSession = {
|
||||||
@@ -180,22 +234,69 @@ export type RuntimeSession = {
|
|||||||
};
|
};
|
||||||
export class RuntimeAuthorityError extends Error {
|
export class RuntimeAuthorityError extends Error {
|
||||||
readonly retryable: boolean;
|
readonly retryable: boolean;
|
||||||
constructor(message: string) { super(message); this.name = "RuntimeAuthorityError"; this.retryable = /WORKSPACE_FENCED|STALE_EPOCH/.test(message); }
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "RuntimeAuthorityError";
|
||||||
|
this.retryable = /WORKSPACE_FENCED|STALE_EPOCH/.test(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetForEdge = (
|
const targetForEdge = (
|
||||||
edge: { firstObjectId: string; secondObjectId: string; firstProjectionId: string },
|
edge: { firstObjectId: string; secondObjectId: string; firstProjectionId: string },
|
||||||
projectionId: string,
|
projectionId: string,
|
||||||
) => edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId;
|
) => (edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId);
|
||||||
|
|
||||||
export const createRuntimeContext = (
|
export const createRuntimeContext = (
|
||||||
camino: CaminoClient,
|
camino: CaminoClient,
|
||||||
orch: OrchClient,
|
orch: OrchClient,
|
||||||
request: RuntimeRequest,
|
request: RuntimeRequest,
|
||||||
): RuntimeContext => {
|
): RuntimeContext => {
|
||||||
|
const acquiredPort = (
|
||||||
|
objectId: string,
|
||||||
|
interfaceRevisionId: string,
|
||||||
|
conformance?: ConformanceWitness,
|
||||||
|
): InterfacePort => {
|
||||||
|
const invoke = async (operationId: string, input: Record<string, unknown> = {}) => {
|
||||||
|
const response = await orch.invokeCapability({
|
||||||
|
objectId,
|
||||||
|
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId, conformance }),
|
||||||
|
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error(response.error || "Capability invocation failed");
|
||||||
|
for (const dependency of response.dependencies) {
|
||||||
|
if (dependency.kind === "state" || dependency.kind === "edge")
|
||||||
|
await recordDependency({
|
||||||
|
kind: dependency.kind,
|
||||||
|
objectId: dependency.objectId,
|
||||||
|
attachmentId: dependency.attachmentId,
|
||||||
|
...(dependency.kind === "edge" ? { projectionId: dependency.projectionId } : {}),
|
||||||
|
} as RuntimeDependency);
|
||||||
|
}
|
||||||
|
if (!response.result) throw new Error("Capability returned no value");
|
||||||
|
return response.result;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
objectId: referenceFromWire(objectId),
|
||||||
|
interfaceRevisionId,
|
||||||
|
invoke: async (operation, input) => protoValueToJs(await invoke(operation, input)),
|
||||||
|
live: async (operation, input) => liveValue(await invoke(operation, input)),
|
||||||
|
};
|
||||||
|
};
|
||||||
const ports = new Map<string, RuntimePort>();
|
const ports = new Map<string, RuntimePort>();
|
||||||
for (const dependency of request.dependencies) {
|
for (const dependency of request.dependencies) {
|
||||||
switch (dependency.binding.case) {
|
switch (dependency.binding.case) {
|
||||||
|
case "queryId": {
|
||||||
|
const queryId = dependency.binding.value,
|
||||||
|
objectId = dependency.objectId || request.objectId;
|
||||||
|
ports.set(dependency.portId, {
|
||||||
|
queryId,
|
||||||
|
execute: (variables, expectedDefinitionDigest) =>
|
||||||
|
orch.executeQuery({ queryId, objectId, variables, expectedDefinitionDigest }),
|
||||||
|
watch: (variables, signal, expectedDefinitionDigest) =>
|
||||||
|
orch.watchQuery({ queryId, objectId, variables, expectedDefinitionDigest }, { signal }),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "stateSlotId": {
|
case "stateSlotId": {
|
||||||
const slotId = dependency.binding.value;
|
const slotId = dependency.binding.value;
|
||||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||||
@@ -222,25 +323,63 @@ export const createRuntimeContext = (
|
|||||||
case "edge": {
|
case "edge": {
|
||||||
const { edgeTypeId, projectionId } = dependency.binding.value;
|
const { edgeTypeId, projectionId } = dependency.binding.value;
|
||||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||||
const collectionResult = (response: {revision: bigint; entries: {edgeId: string; targetObjectId: string; key?: Value}[]}): RelationshipCollection => ({revision: response.revision,
|
const collectionResult = (response: {
|
||||||
entries: response.entries.map((entry) => ({edgeId: entry.edgeId, target: referenceFromWire(entry.targetObjectId),
|
revision: bigint;
|
||||||
...(entry.key ? {key: entry.key.kind.case === "integerValue" ? BigInt(entry.key.kind.value) : protoValueToJs(entry.key) as string | boolean} : {})}))});
|
entries: { edgeId: string; targetObjectId: string; key?: Value }[];
|
||||||
|
}): RelationshipCollection => ({
|
||||||
|
revision: response.revision,
|
||||||
|
entries: response.entries.map((entry) => ({
|
||||||
|
edgeId: entry.edgeId,
|
||||||
|
target: referenceFromWire(entry.targetObjectId),
|
||||||
|
...(entry.key
|
||||||
|
? {
|
||||||
|
key:
|
||||||
|
entry.key.kind.case === "integerValue"
|
||||||
|
? BigInt(entry.key.kind.value)
|
||||||
|
: (protoValueToJs(entry.key) as string | boolean),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})),
|
||||||
|
});
|
||||||
const edge: EdgePort = {
|
const edge: EdgePort = {
|
||||||
edgeTypeId,
|
edgeTypeId,
|
||||||
projectionId,
|
projectionId,
|
||||||
async collection() {
|
async collection() {
|
||||||
await recordDependency({kind: "edge", objectId: dependencyObjectId, attachmentId: edgeTypeId, projectionId});
|
await recordDependency({
|
||||||
return collectionResult(await camino.readCollection({objectId: dependencyObjectId, edgeTypeId, projectionId}));
|
kind: "edge",
|
||||||
|
objectId: dependencyObjectId,
|
||||||
|
attachmentId: edgeTypeId,
|
||||||
|
projectionId,
|
||||||
|
});
|
||||||
|
return collectionResult(
|
||||||
|
await camino.readCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId }),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
async replace(entries, expectedRevision) {
|
async replace(entries, expectedRevision) {
|
||||||
return collectionResult(await camino.replaceCollection({objectId: dependencyObjectId, edgeTypeId, projectionId, expectedRevision,
|
return collectionResult(
|
||||||
|
await camino.replaceCollection({
|
||||||
|
objectId: dependencyObjectId,
|
||||||
|
edgeTypeId,
|
||||||
|
projectionId,
|
||||||
|
expectedRevision,
|
||||||
entries: entries.map((entry) => {
|
entries: entries.map((entry) => {
|
||||||
assertReferenceFree(entry.key);
|
assertReferenceFree(entry.key);
|
||||||
return {edgeId: entry.edgeId ?? "", targetObjectId: referenceToWire(entry.target), key: entry.key === undefined ? undefined : jsToProtoValue(entry.key)};
|
return {
|
||||||
})}));
|
edgeId: entry.edgeId ?? "",
|
||||||
|
targetObjectId: referenceToWire(entry.target),
|
||||||
|
key: entry.key === undefined ? undefined : jsToProtoValue(entry.key),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
async resolve() {
|
async resolve() {
|
||||||
await recordDependency({ kind: "edge", objectId: dependencyObjectId, attachmentId: edgeTypeId, projectionId });
|
await recordDependency({
|
||||||
|
kind: "edge",
|
||||||
|
objectId: dependencyObjectId,
|
||||||
|
attachmentId: edgeTypeId,
|
||||||
|
projectionId,
|
||||||
|
});
|
||||||
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
||||||
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
|
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
|
||||||
},
|
},
|
||||||
@@ -252,7 +391,8 @@ export const createRuntimeContext = (
|
|||||||
const targetObjectId = referenceToWire(target);
|
const targetObjectId = referenceToWire(target);
|
||||||
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
||||||
for (const entry of result.edges) {
|
for (const entry of result.edges) {
|
||||||
if (targetForEdge(entry, projectionId) === targetObjectId) await camino.disconnectEdge({ edgeId: entry.id });
|
if (targetForEdge(entry, projectionId) === targetObjectId)
|
||||||
|
await camino.disconnectEdge({ edgeId: entry.id });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -262,44 +402,7 @@ export const createRuntimeContext = (
|
|||||||
case "interfaceRevisionId": {
|
case "interfaceRevisionId": {
|
||||||
const interfaceRevisionId = dependency.binding.value;
|
const interfaceRevisionId = dependency.binding.value;
|
||||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||||
const invoke = async (operationId: string, input: Record<string, unknown>) => {
|
ports.set(dependency.portId, acquiredPort(dependencyObjectId, interfaceRevisionId));
|
||||||
const response = await orch.invokeCapability({
|
|
||||||
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId }),
|
|
||||||
objectId: dependencyObjectId,
|
|
||||||
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
|
|
||||||
});
|
|
||||||
if (!response.ok) throw new Error(response.error || `Capability ${operationId} failed`);
|
|
||||||
for (const dependency of response.dependencies) {
|
|
||||||
if (dependency.kind === "state") {
|
|
||||||
await recordDependency({
|
|
||||||
kind: "state",
|
|
||||||
objectId: dependency.objectId,
|
|
||||||
attachmentId: dependency.attachmentId,
|
|
||||||
});
|
|
||||||
} else if (dependency.kind === "edge") {
|
|
||||||
await recordDependency({
|
|
||||||
kind: "edge",
|
|
||||||
objectId: dependency.objectId,
|
|
||||||
attachmentId: dependency.attachmentId,
|
|
||||||
projectionId: dependency.projectionId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response.result;
|
|
||||||
};
|
|
||||||
const capability: InterfacePort = {
|
|
||||||
objectId: referenceFromWire(dependencyObjectId),
|
|
||||||
interfaceRevisionId,
|
|
||||||
async invoke(operationId, input = {}) {
|
|
||||||
return protoValueToJs(await invoke(operationId, input));
|
|
||||||
},
|
|
||||||
async live(operationId, input = {}) {
|
|
||||||
const value = await invoke(operationId, input);
|
|
||||||
if (!value) throw new Error(`Capability ${operationId} returned no value`);
|
|
||||||
return liveValue(value);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ports.set(dependency.portId, capability);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "constructorAtomId": {
|
case "constructorAtomId": {
|
||||||
@@ -325,7 +428,16 @@ export const createRuntimeContext = (
|
|||||||
return port as T;
|
return port as T;
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
objectId: referenceFromWire(request.objectId),
|
async tryConform(object, interfaceRevisionId) {
|
||||||
|
const objectId = referenceToWire(object);
|
||||||
|
const { conformance } = await orch.tryConform({ objectId, interfaceRevisionId });
|
||||||
|
if (conformance && (conformance.objectId !== objectId || conformance.interfaceRevisionId !== interfaceRevisionId))
|
||||||
|
throw new Error("Conformance response does not match the requested view");
|
||||||
|
return conformance ? acquiredPort(objectId, interfaceRevisionId, conformance) : undefined;
|
||||||
|
},
|
||||||
|
get objectId() {
|
||||||
|
return referenceFromWire(request.objectId);
|
||||||
|
},
|
||||||
input: protoFieldsToJs(request.input),
|
input: protoFieldsToJs(request.input),
|
||||||
inputProto: request.input,
|
inputProto: request.input,
|
||||||
ports,
|
ports,
|
||||||
@@ -333,6 +445,7 @@ export const createRuntimeContext = (
|
|||||||
edge: (portId: string) => requirePort<EdgePort>(portId, "edgeTypeId"),
|
edge: (portId: string) => requirePort<EdgePort>(portId, "edgeTypeId"),
|
||||||
interface: (portId: string) => requirePort<InterfacePort>(portId, "interfaceRevisionId"),
|
interface: (portId: string) => requirePort<InterfacePort>(portId, "interfaceRevisionId"),
|
||||||
constructor: (portId: string) => requirePort<ConstructorPort>(portId, "atomId"),
|
constructor: (portId: string) => requirePort<ConstructorPort>(portId, "atomId"),
|
||||||
|
query: (portId: string) => requirePort<QueryPort>(portId, "queryId"),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -348,20 +461,21 @@ const evaluate = async (
|
|||||||
observe?: (dependency: RuntimeDependency) => Promise<void>,
|
observe?: (dependency: RuntimeDependency) => Promise<void>,
|
||||||
) => {
|
) => {
|
||||||
const dependencies = new Map<string, RuntimeDependency>();
|
const dependencies = new Map<string, RuntimeDependency>();
|
||||||
const result = await dependencyScope.run(
|
const result = await dependencyScope.run({ dependencies, observe }, () =>
|
||||||
{ dependencies, observe },
|
isDerived(handler) ? handler.get(context) : handler(context),
|
||||||
() => isDerived(handler) ? handler.get(context) : handler(context),
|
|
||||||
);
|
);
|
||||||
return { value: jsToProtoValue(result), dependencies: [...dependencies.values()] };
|
return { value: jsToProtoValue(result), dependencies: [...dependencies.values()] };
|
||||||
};
|
};
|
||||||
|
|
||||||
const protoDependencies = (dependencies: RuntimeDependency[]) => dependencies.map((entry) =>
|
const protoDependencies = (dependencies: RuntimeDependency[]) =>
|
||||||
|
dependencies.map((entry) =>
|
||||||
create(DerivedDependencySchema, {
|
create(DerivedDependencySchema, {
|
||||||
kind: entry.kind,
|
kind: entry.kind,
|
||||||
objectId: entry.objectId,
|
objectId: entry.objectId,
|
||||||
attachmentId: entry.attachmentId,
|
attachmentId: entry.attachmentId,
|
||||||
projectionId: entry.kind === "edge" ? entry.projectionId : "",
|
projectionId: entry.kind === "edge" ? entry.projectionId : "",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export const createPackageRuntimeRoutes = (config: {
|
export const createPackageRuntimeRoutes = (config: {
|
||||||
packageRevisionId: string;
|
packageRevisionId: string;
|
||||||
@@ -371,27 +485,38 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
}) => {
|
}) => {
|
||||||
const invocations = createInvocationRegistry();
|
const invocations = createInvocationRegistry();
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
const processToken = process.env.CAMINO_RUNTIME_AUTH_TOKEN ?? (process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
|
const processToken =
|
||||||
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim() : "");
|
process.env.CAMINO_RUNTIME_AUTH_TOKEN ??
|
||||||
|
(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
|
||||||
|
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim()
|
||||||
|
: "");
|
||||||
if (processToken) {
|
if (processToken) {
|
||||||
headers["x-camino-runtime-token"] = processToken;
|
headers["x-camino-runtime-token"] = processToken;
|
||||||
} else if (process.env.CAMINO_RUNTIME_AUTH_REQUIRED === "1") {
|
} else if (process.env.CAMINO_RUNTIME_AUTH_REQUIRED === "1") {
|
||||||
throw new Error("CAMINO_RUNTIME_AUTH_TOKEN is required");
|
throw new Error("CAMINO_RUNTIME_AUTH_TOKEN is required");
|
||||||
}
|
}
|
||||||
const camino = createClient(CaminoService, createConnectTransport({
|
const camino = createClient(
|
||||||
|
CaminoService,
|
||||||
|
createConnectTransport({
|
||||||
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
|
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
interceptors: headers["x-camino-runtime-token"] ? [
|
interceptors: headers["x-camino-runtime-token"]
|
||||||
|
? [
|
||||||
(next) => async (request) => {
|
(next) => async (request) => {
|
||||||
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]!);
|
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]!);
|
||||||
return await next(request);
|
return await next(request);
|
||||||
},
|
},
|
||||||
] : [],
|
]
|
||||||
}));
|
: [],
|
||||||
const orch = createClient(OrchestratorRuntime, createConnectTransport({
|
}),
|
||||||
|
);
|
||||||
|
const orch = createClient(
|
||||||
|
OrchestratorRuntime,
|
||||||
|
createConnectTransport({
|
||||||
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
|
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const authenticateInstance = (header: Headers) => {
|
const authenticateInstance = (header: Headers) => {
|
||||||
if (!process.env.QUIXOS_RUNTIME_INSTANCE_ID) return; // standalone development ABI
|
if (!process.env.QUIXOS_RUNTIME_INSTANCE_ID) return; // standalone development ABI
|
||||||
@@ -403,34 +528,66 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
};
|
};
|
||||||
const clientsFor = (request: { context?: { grant: string; instanceId: string; workspaceEpoch: string } }) => {
|
const clientsFor = (request: { context?: { grant: string; instanceId: string; workspaceEpoch: string } }) => {
|
||||||
const context = request.context;
|
const context = request.context;
|
||||||
if (process.env.QUIXOS_RUNTIME_INSTANCE_ID && (!context?.grant || context.instanceId !== process.env.QUIXOS_RUNTIME_INSTANCE_ID || !context.workspaceEpoch)) {
|
if (
|
||||||
|
process.env.QUIXOS_RUNTIME_INSTANCE_ID &&
|
||||||
|
(!context?.grant || context.instanceId !== process.env.QUIXOS_RUNTIME_INSTANCE_ID || !context.workspaceEpoch)
|
||||||
|
) {
|
||||||
throw new ConnectError("Managed invocation requires an exact instance and epoch grant", Code.Unauthenticated);
|
throw new ConnectError("Managed invocation requires an exact instance and epoch grant", Code.Unauthenticated);
|
||||||
}
|
}
|
||||||
if (!context?.grant) return { camino, orch };
|
if (!context?.grant) return { camino, orch };
|
||||||
const transport = (url: string) => createConnectTransport({ baseUrl: url, httpVersion: "1.1", interceptors: [(next) => async (call) => {
|
const transport = (url: string) =>
|
||||||
|
createConnectTransport({
|
||||||
|
baseUrl: url,
|
||||||
|
httpVersion: "1.1",
|
||||||
|
interceptors: [
|
||||||
|
(next) => async (call) => {
|
||||||
call.header.set("x-quixos-invocation-grant", context.grant);
|
call.header.set("x-quixos-invocation-grant", context.grant);
|
||||||
call.header.set("x-camino-runtime-token", processToken);
|
call.header.set("x-camino-runtime-token", processToken);
|
||||||
return next(call);
|
return next(call);
|
||||||
}] });
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
camino: createClient(CaminoService, transport(config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310")),
|
camino: createClient(
|
||||||
orch: createClient(OrchestratorRuntime, transport(config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311")),
|
CaminoService,
|
||||||
|
transport(config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"),
|
||||||
|
),
|
||||||
|
orch: createClient(
|
||||||
|
OrchestratorRuntime,
|
||||||
|
transport(config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311"),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const runtimeControl = async <T>(operation: string, input: unknown): Promise<T> => {
|
const runtimeControl = async <T>(operation: string, input: unknown): Promise<T> => {
|
||||||
const response = await fetch(`${config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"}/__runtime/${operation}`, {
|
const response = await fetch(
|
||||||
method: "POST", headers: { "content-type": "application/json", "x-camino-runtime-token": processToken }, body: JSON.stringify(input), signal: AbortSignal.timeout(10_000),
|
`${config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"}/__runtime/${operation}`,
|
||||||
});
|
{
|
||||||
const value = await response.json() as T & {error?: string};
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json", "x-camino-runtime-token": processToken },
|
||||||
|
body: JSON.stringify(input),
|
||||||
|
signal: AbortSignal.timeout(10_000),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const value = (await response.json()) as T & { error?: string };
|
||||||
if (!response.ok) throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
|
if (!response.ok) throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
const attachSessions = (runtimeContext: RuntimeContext, request: RuntimeRequest & {context?: {grant: string; instanceId: string; workspaceEpoch: string; ownerConformanceId?: string}}) => {
|
const attachSessions = (
|
||||||
|
runtimeContext: RuntimeContext,
|
||||||
|
request: RuntimeRequest & {
|
||||||
|
context?: { grant: string; instanceId: string; workspaceEpoch: string; ownerConformanceId?: string };
|
||||||
|
},
|
||||||
|
) => {
|
||||||
if (!request.context?.grant || !request.context.ownerConformanceId) return;
|
if (!request.context?.grant || !request.context.ownerConformanceId) return;
|
||||||
runtimeContext.openSession = async () => {
|
runtimeContext.openSession = async () => {
|
||||||
const ownerId = request.context!.ownerConformanceId!;
|
const ownerId = request.context!.ownerConformanceId!;
|
||||||
const registration = { grant: request.context!.grant, objectId: request.objectId, ownerId,
|
const registration = {
|
||||||
sessionId: `session:${randomBytes(16).toString("hex")}`, token: randomBytes(32).toString("base64url") };
|
grant: request.context!.grant,
|
||||||
|
objectId: request.objectId,
|
||||||
|
ownerId,
|
||||||
|
sessionId: `session:${randomBytes(16).toString("hex")}`,
|
||||||
|
token: randomBytes(32).toString("base64url"),
|
||||||
|
};
|
||||||
const register = () => runtimeControl<{ sessionId: string; token: string }>("register-session", registration);
|
const register = () => runtimeControl<{ sessionId: string; token: string }>("register-session", registration);
|
||||||
const registered = await register().catch((error) => {
|
const registered = await register().catch((error) => {
|
||||||
// Retry a transport/lost-response failure with exactly the same identity.
|
// Retry a transport/lost-response failure with exactly the same identity.
|
||||||
@@ -445,33 +602,59 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
if (closed) throw new RuntimeAuthorityError("SESSION_CLOSED");
|
if (closed) throw new RuntimeAuthorityError("SESSION_CLOSED");
|
||||||
// Acquisition happens before user code. A fence failure can be retried
|
// Acquisition happens before user code. A fence failure can be retried
|
||||||
// by the caller without replaying a side-effecting callback.
|
// by the caller without replaying a side-effecting callback.
|
||||||
const grant = await runtimeControl<{grant: string; epoch: string; instanceId: string; invocationId: string; bindingDigest: string}>("acquire-session", registered);
|
const grant = await runtimeControl<{
|
||||||
|
grant: string;
|
||||||
|
epoch: string;
|
||||||
|
instanceId: string;
|
||||||
|
invocationId: string;
|
||||||
|
bindingDigest: string;
|
||||||
|
}>("acquire-session", registered);
|
||||||
const execution = invocations.begin(grant.invocationId);
|
const execution = invocations.begin(grant.invocationId);
|
||||||
const sessionRequest = { ...request, context: { grant: grant.grant, instanceId: grant.instanceId, workspaceEpoch: grant.epoch } };
|
const sessionRequest = {
|
||||||
|
...request,
|
||||||
|
context: { grant: grant.grant, instanceId: grant.instanceId, workspaceEpoch: grant.epoch },
|
||||||
|
};
|
||||||
const clients = clientsFor(sessionRequest);
|
const clients = clientsFor(sessionRequest);
|
||||||
const context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
|
const context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
|
||||||
context.signal = execution.signal;
|
context.signal = execution.signal;
|
||||||
try { return await work(context); }
|
try {
|
||||||
finally {
|
return await work(context);
|
||||||
|
} finally {
|
||||||
execution.finish();
|
execution.finish();
|
||||||
await runtimeControl("complete-invocation", { invocationId: grant.invocationId }).catch((error) => console.error("Session completion will be reconciled by the host", error));
|
await runtimeControl("complete-invocation", { invocationId: grant.invocationId }).catch((error) =>
|
||||||
|
console.error("Session completion will be reconciled by the host", error),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async close() { await runtimeControl("close-session", registered); closed = true; },
|
async close() {
|
||||||
|
await runtimeControl("close-session", registered);
|
||||||
|
closed = true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return (router: ConnectRouter) => router.service(PackageRuntime, {
|
return (router: ConnectRouter) =>
|
||||||
handshake: (request) => create(HandshakeResponseSchema, {
|
router.service(PackageRuntime, {
|
||||||
|
handshake: (request) =>
|
||||||
|
create(HandshakeResponseSchema, {
|
||||||
packageRevisionId: config.packageRevisionId,
|
packageRevisionId: config.packageRevisionId,
|
||||||
runtimeProtocolVersion: "quixos-capabilities-v1",
|
runtimeProtocolVersion: "quixos-capabilities-v1",
|
||||||
exportIds: Object.keys(config.exports),
|
exportIds: Object.keys(config.exports),
|
||||||
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
|
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
|
||||||
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||||
authenticationProof: request.nonce && processToken ? createHmac("sha256", processToken)
|
authenticationProof:
|
||||||
.update(JSON.stringify([request.nonce, process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "", config.packageRevisionId]))
|
request.nonce && processToken
|
||||||
.digest("hex") : "",
|
? createHmac("sha256", processToken)
|
||||||
|
.update(
|
||||||
|
JSON.stringify([
|
||||||
|
request.nonce,
|
||||||
|
process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||||
|
config.packageRevisionId,
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.digest("hex")
|
||||||
|
: "",
|
||||||
}),
|
}),
|
||||||
getInvocationStatus: (request, context) => {
|
getInvocationStatus: (request, context) => {
|
||||||
authenticateInstance(context.requestHeader);
|
authenticateInstance(context.requestHeader);
|
||||||
@@ -487,7 +670,9 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
const exportId = request.export?.exportId;
|
const exportId = request.export?.exportId;
|
||||||
const handler = exportId ? config.exports[exportId] : undefined;
|
const handler = exportId ? config.exports[exportId] : undefined;
|
||||||
if (!handler) throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
|
if (!handler) throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
|
||||||
const execution = invocations.begin(request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()));
|
const execution = invocations.begin(
|
||||||
|
request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const runtimeContext = createRuntimeContext(camino, orch, request);
|
const runtimeContext = createRuntimeContext(camino, orch, request);
|
||||||
attachSessions(runtimeContext, request);
|
attachSessions(runtimeContext, request);
|
||||||
@@ -515,7 +700,9 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
if (!handler || !isDerived(handler)) {
|
if (!handler || !isDerived(handler)) {
|
||||||
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
|
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
|
||||||
}
|
}
|
||||||
const execution = invocations.begin(request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()));
|
const execution = invocations.begin(
|
||||||
|
request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()),
|
||||||
|
);
|
||||||
const signal = AbortSignal.any([context.signal, execution.signal]);
|
const signal = AbortSignal.any([context.signal, execution.signal]);
|
||||||
try {
|
try {
|
||||||
const watchId = `watch:${randomUUID()}`;
|
const watchId = `watch:${randomUUID()}`;
|
||||||
@@ -540,16 +727,23 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
if (pending) return await pending;
|
if (pending) return await pending;
|
||||||
const establish = (async () => {
|
const establish = (async () => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const stream = camino.watchObject(
|
const stream = camino
|
||||||
{ objectId: dependency.objectId, includeSnapshot: true, attachmentIds: request.context?.grant ? [dependency.attachmentId] : [] },
|
.watchObject(
|
||||||
|
{
|
||||||
|
objectId: dependency.objectId,
|
||||||
|
includeSnapshot: true,
|
||||||
|
attachmentIds: request.context?.grant ? [dependency.attachmentId] : [],
|
||||||
|
},
|
||||||
{ signal: controller.signal },
|
{ signal: controller.signal },
|
||||||
)[Symbol.asyncIterator]();
|
)
|
||||||
|
[Symbol.asyncIterator]();
|
||||||
try {
|
try {
|
||||||
// Camino subscribes before producing the snapshot, so once this
|
// Camino subscribes before producing the snapshot, so once this
|
||||||
// resolves the following state/edge read cannot race the stream.
|
// resolves the following state/edge read cannot race the stream.
|
||||||
const snapshot = await stream.next();
|
const snapshot = await stream.next();
|
||||||
if (snapshot.done) throw new Error(`Dependency stream ${key} ended during setup`);
|
if (snapshot.done) throw new Error(`Dependency stream ${key} ended during setup`);
|
||||||
const waitNext = () => stream.next().then(
|
const waitNext = () =>
|
||||||
|
stream.next().then(
|
||||||
(result) => ({ key, done: Boolean(result.done) }),
|
(result) => ({ key, done: Boolean(result.done) }),
|
||||||
(error: unknown) => ({ key, done: true, error }),
|
(error: unknown) => ({ key, done: true, error }),
|
||||||
);
|
);
|
||||||
@@ -614,10 +808,7 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
await abort;
|
await abort;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const outcome = await Promise.race([
|
const outcome = await Promise.race([...[...subscriptions.values()].map((entry) => entry.next), abort]);
|
||||||
...[...subscriptions.values()].map((entry) => entry.next),
|
|
||||||
abort,
|
|
||||||
]);
|
|
||||||
if (outcome === "abort") break;
|
if (outcome === "abort") break;
|
||||||
const subscription = subscriptions.get(outcome.key);
|
const subscription = subscriptions.get(outcome.key);
|
||||||
if (!subscription) continue;
|
if (!subscription) continue;
|
||||||
@@ -646,7 +837,9 @@ export const createPackageRuntimeRoutes = (config: {
|
|||||||
signal.removeEventListener("abort", abortAll);
|
signal.removeEventListener("abort", abortAll);
|
||||||
abortAll();
|
abortAll();
|
||||||
}
|
}
|
||||||
} finally { execution.finish(); }
|
} finally {
|
||||||
|
execution.finish();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-3
@@ -3,15 +3,23 @@ export const createInvocationRegistry = () => {
|
|||||||
const entries = new Map<string, { state: string; controller: AbortController }>();
|
const entries = new Map<string, { state: string; controller: AbortController }>();
|
||||||
return {
|
return {
|
||||||
begin(id: string) {
|
begin(id: string) {
|
||||||
if (!id || entries.has(id)) throw new Error("Invocation ID is missing or already used; invocations are never replayed implicitly");
|
if (!id || entries.has(id))
|
||||||
|
throw new Error("Invocation ID is missing or already used; invocations are never replayed implicitly");
|
||||||
// Completed identities remain until process retirement. A bounded process
|
// Completed identities remain until process retirement. A bounded process
|
||||||
// may reject new work; it must not evict and accidentally replay a call.
|
// may reject new work; it must not evict and accidentally replay a call.
|
||||||
if (entries.size >= 100_000) throw new Error("Invocation registry full; explicit runtime retirement required");
|
if (entries.size >= 100_000) throw new Error("Invocation registry full; explicit runtime retirement required");
|
||||||
const entry = { state: "running", controller: new AbortController() };
|
const entry = { state: "running", controller: new AbortController() };
|
||||||
entries.set(id, entry);
|
entries.set(id, entry);
|
||||||
return { signal: entry.controller.signal, finish(failed = false) { entry.state = failed ? "failed" : "completed"; } };
|
return {
|
||||||
|
signal: entry.controller.signal,
|
||||||
|
finish(failed = false) {
|
||||||
|
entry.state = failed ? "failed" : "completed";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
status(id: string) {
|
||||||
|
return { invocationId: id, state: entries.get(id)?.state ?? "unknown" };
|
||||||
},
|
},
|
||||||
status(id: string) { return { invocationId: id, state: entries.get(id)?.state ?? "unknown" }; },
|
|
||||||
cancel(id: string) {
|
cancel(id: string) {
|
||||||
const entry = entries.get(id);
|
const entry = entries.get(id);
|
||||||
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
|
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
|
||||||
|
|||||||
+84
-25
@@ -1,16 +1,40 @@
|
|||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
|
||||||
export type MigrationInput = {
|
export type MigrationInput = {
|
||||||
schemaVersion: 1; executionId: string; exportId: string;
|
schemaVersion: 1;
|
||||||
ports: {name: string; binding: string; view: "old" | "new"; access: ("read" | "write" | "create" | "edge")[]; atomId?: string;
|
executionId: string;
|
||||||
attachedAtomId?: string; defaultValue?: unknown;
|
exportId: string;
|
||||||
states?: {objectId: string; value: unknown}[]; edges?: MigrationEdge[]}[];
|
ports: {
|
||||||
|
name: string;
|
||||||
|
binding: string;
|
||||||
|
view: "old" | "new";
|
||||||
|
access: ("read" | "write" | "create" | "edge")[];
|
||||||
|
atomId?: string;
|
||||||
|
attachedAtomId?: string;
|
||||||
|
defaultValue?: unknown;
|
||||||
|
states?: { objectId: string; value: unknown }[];
|
||||||
|
edges?: MigrationEdge[];
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
export type MigrationEdge = {id: string; edgeTypeId: string; firstObjectId: string; secondObjectId: string; firstProjectionId: string; secondProjectionId: string; firstOrdinal?: number; secondOrdinal?: number; firstKeyJson?: string; secondKeyJson?: string};
|
export type MigrationEdge = {
|
||||||
export type MigrationOutput = {schemaVersion: 1; executionId: string;
|
id: string;
|
||||||
|
edgeTypeId: string;
|
||||||
|
firstObjectId: string;
|
||||||
|
secondObjectId: string;
|
||||||
|
firstProjectionId: string;
|
||||||
|
secondProjectionId: string;
|
||||||
|
firstOrdinal?: number;
|
||||||
|
secondOrdinal?: number;
|
||||||
|
firstKeyJson?: string;
|
||||||
|
secondKeyJson?: string;
|
||||||
|
};
|
||||||
|
export type MigrationOutput = {
|
||||||
|
schemaVersion: 1;
|
||||||
|
executionId: string;
|
||||||
writes: { port: string; objectId: string; value: unknown }[];
|
writes: { port: string; objectId: string; value: unknown }[];
|
||||||
creates: { port: string; logicalKey: string; objectId: string }[];
|
creates: { port: string; logicalKey: string; objectId: string }[];
|
||||||
edgeReplacements: {port: string; edges: MigrationEdge[]}[]};
|
edgeReplacements: { port: string; edges: MigrationEdge[] }[];
|
||||||
|
};
|
||||||
export type MigrationContext = {
|
export type MigrationContext = {
|
||||||
enumerate(port: string): { objectId: string; value: unknown }[];
|
enumerate(port: string): { objectId: string; value: unknown }[];
|
||||||
read(port: string, objectId: string): unknown;
|
read(port: string, objectId: string): unknown;
|
||||||
@@ -20,55 +44,89 @@ export type MigrationContext = {
|
|||||||
replaceEdges(port: string, edges: MigrationEdge[]): void;
|
replaceEdges(port: string, edges: MigrationEdge[]): void;
|
||||||
};
|
};
|
||||||
export const migrationObjectId = (executionId: string, port: string, logicalKey: string) =>
|
export const migrationObjectId = (executionId: string, port: string, logicalKey: string) =>
|
||||||
`obj:migration:${createHash("sha256").update(JSON.stringify([executionId, port, logicalKey])).digest("hex")}`;
|
`obj:migration:${createHash("sha256")
|
||||||
|
.update(JSON.stringify([executionId, port, logicalKey]))
|
||||||
|
.digest("hex")}`;
|
||||||
|
|
||||||
/** No ordinary RuntimeContext or network/database clients are supplied here.
|
/** No ordinary RuntimeContext or network/database clients are supplied here.
|
||||||
* Process isolation belongs to the host, not this convenience API. */
|
* Process isolation belongs to the host, not this convenience API. */
|
||||||
export const createMigrationContext = (input: MigrationInput) => {
|
export const createMigrationContext = (input: MigrationInput) => {
|
||||||
if (input.schemaVersion !== 1 || !input.executionId || new Set(input.ports.map((entry) => entry.name)).size !== input.ports.length) throw new Error("Invalid migration input");
|
if (
|
||||||
const output: MigrationOutput = {schemaVersion: 1, executionId: input.executionId, writes: [], creates: [], edgeReplacements: []};
|
input.schemaVersion !== 1 ||
|
||||||
|
!input.executionId ||
|
||||||
|
new Set(input.ports.map((entry) => entry.name)).size !== input.ports.length
|
||||||
|
)
|
||||||
|
throw new Error("Invalid migration input");
|
||||||
|
const output: MigrationOutput = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
executionId: input.executionId,
|
||||||
|
writes: [],
|
||||||
|
creates: [],
|
||||||
|
edgeReplacements: [],
|
||||||
|
};
|
||||||
const port = (name: string, access: string) => {
|
const port = (name: string, access: string) => {
|
||||||
const selected = input.ports.find((entry) => entry.name === name);
|
const selected = input.ports.find((entry) => entry.name === name);
|
||||||
if (!selected?.access.includes(access as "read") || (selected.view === "old" && access !== "read")) throw new Error(`Migration port ${name} does not grant ${access}`);
|
if (!selected?.access.includes(access as "read") || (selected.view === "old" && access !== "read"))
|
||||||
|
throw new Error(`Migration port ${name} does not grant ${access}`);
|
||||||
return selected;
|
return selected;
|
||||||
};
|
};
|
||||||
const context: MigrationContext = {
|
const context: MigrationContext = {
|
||||||
enumerate(name) {
|
enumerate(name) {
|
||||||
const selected = port(name, "read"), states = structuredClone(selected.states ?? []);
|
const selected = port(name, "read"),
|
||||||
if (selected.view === "new" && Object.hasOwn(selected, "defaultValue")) for (const helper of output.creates) {
|
states = structuredClone(selected.states ?? []);
|
||||||
if (input.ports.find((entry) => entry.name === helper.port)?.atomId === selected.attachedAtomId && !states.some((entry) => entry.objectId === helper.objectId)) states.push({objectId: helper.objectId, value: structuredClone(selected.defaultValue)});
|
if (selected.view === "new" && Object.hasOwn(selected, "defaultValue"))
|
||||||
|
for (const helper of output.creates) {
|
||||||
|
if (
|
||||||
|
input.ports.find((entry) => entry.name === helper.port)?.atomId === selected.attachedAtomId &&
|
||||||
|
!states.some((entry) => entry.objectId === helper.objectId)
|
||||||
|
)
|
||||||
|
states.push({ objectId: helper.objectId, value: structuredClone(selected.defaultValue) });
|
||||||
}
|
}
|
||||||
if (selected.view === "new") for (const write of output.writes) {
|
if (selected.view === "new")
|
||||||
|
for (const write of output.writes) {
|
||||||
if (input.ports.find((entry) => entry.name === write.port)?.binding !== selected.binding) continue;
|
if (input.ports.find((entry) => entry.name === write.port)?.binding !== selected.binding) continue;
|
||||||
const existing = states.findIndex((entry) => entry.objectId === write.objectId), entry = {objectId: write.objectId, value: structuredClone(write.value)};
|
const existing = states.findIndex((entry) => entry.objectId === write.objectId),
|
||||||
if (existing < 0) states.push(entry); else states[existing] = entry;
|
entry = { objectId: write.objectId, value: structuredClone(write.value) };
|
||||||
|
if (existing < 0) states.push(entry);
|
||||||
|
else states[existing] = entry;
|
||||||
}
|
}
|
||||||
return states.sort((a, b) => a.objectId < b.objectId ? -1 : a.objectId > b.objectId ? 1 : 0);
|
return states.sort((a, b) => (a.objectId < b.objectId ? -1 : a.objectId > b.objectId ? 1 : 0));
|
||||||
|
},
|
||||||
|
read(name, objectId) {
|
||||||
|
return context.enumerate(name).find((entry) => entry.objectId === objectId)?.value;
|
||||||
},
|
},
|
||||||
read(name, objectId) {return context.enumerate(name).find((entry) => entry.objectId === objectId)?.value;},
|
|
||||||
write(name, objectId, value) {
|
write(name, objectId, value) {
|
||||||
port(name, "write");
|
port(name, "write");
|
||||||
const previous = output.writes.findIndex((entry) => entry.port === name && entry.objectId === objectId);
|
const previous = output.writes.findIndex((entry) => entry.port === name && entry.objectId === objectId);
|
||||||
const entry = { port: name, objectId, value: structuredClone(value) };
|
const entry = { port: name, objectId, value: structuredClone(value) };
|
||||||
if (previous < 0) output.writes.push(entry); else output.writes[previous] = entry;
|
if (previous < 0) output.writes.push(entry);
|
||||||
|
else output.writes[previous] = entry;
|
||||||
},
|
},
|
||||||
create(name, logicalKey) {
|
create(name, logicalKey) {
|
||||||
port(name, "create");
|
port(name, "create");
|
||||||
if (!logicalKey || logicalKey.length > 1024) throw new Error("Migration creation requires a bounded stable logical key");
|
if (!logicalKey || logicalKey.length > 1024)
|
||||||
|
throw new Error("Migration creation requires a bounded stable logical key");
|
||||||
const objectId = migrationObjectId(input.executionId, name, logicalKey);
|
const objectId = migrationObjectId(input.executionId, name, logicalKey);
|
||||||
if (!output.creates.some((entry) => entry.objectId === objectId)) output.creates.push({port: name, logicalKey, objectId});
|
if (!output.creates.some((entry) => entry.objectId === objectId))
|
||||||
|
output.creates.push({ port: name, logicalKey, objectId });
|
||||||
return objectId;
|
return objectId;
|
||||||
},
|
},
|
||||||
edges(name) {
|
edges(name) {
|
||||||
const selected = port(name, "read");
|
const selected = port(name, "read");
|
||||||
const replacement = selected.view === "new" ? output.edgeReplacements.find((entry) => input.ports.find((candidate) => candidate.name === entry.port)?.binding === selected.binding) : undefined;
|
const replacement =
|
||||||
|
selected.view === "new"
|
||||||
|
? output.edgeReplacements.find(
|
||||||
|
(entry) => input.ports.find((candidate) => candidate.name === entry.port)?.binding === selected.binding,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
return structuredClone(replacement?.edges ?? selected.edges ?? []);
|
return structuredClone(replacement?.edges ?? selected.edges ?? []);
|
||||||
},
|
},
|
||||||
replaceEdges(name, edges) {
|
replaceEdges(name, edges) {
|
||||||
port(name, "edge");
|
port(name, "edge");
|
||||||
const previous = output.edgeReplacements.findIndex((entry) => entry.port === name);
|
const previous = output.edgeReplacements.findIndex((entry) => entry.port === name);
|
||||||
const entry = { port: name, edges: structuredClone(edges) };
|
const entry = { port: name, edges: structuredClone(edges) };
|
||||||
if (previous < 0) output.edgeReplacements.push(entry); else output.edgeReplacements[previous] = entry;
|
if (previous < 0) output.edgeReplacements.push(entry);
|
||||||
|
else output.edgeReplacements[previous] = entry;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return { context, result: () => structuredClone(output) };
|
return { context, result: () => structuredClone(output) };
|
||||||
@@ -78,7 +136,8 @@ export const createMigrationContext = (input: MigrationInput) => {
|
|||||||
* stdout is protocol-only; send diagnostics to stderr. The host independently
|
* stdout is protocol-only; send diagnostics to stderr. The host independently
|
||||||
* validates every write, helper identity, contract, and completion receipt. */
|
* validates every write, helper identity, contract, and completion receipt. */
|
||||||
export const serveMigration = async (exports: Record<string, (context: MigrationContext) => void | Promise<void>>) => {
|
export const serveMigration = async (exports: Record<string, (context: MigrationContext) => void | Promise<void>>) => {
|
||||||
const chunks: Buffer[] = []; let bytes = 0;
|
const chunks: Buffer[] = [];
|
||||||
|
let bytes = 0;
|
||||||
for await (const chunk of process.stdin) {
|
for await (const chunk of process.stdin) {
|
||||||
bytes += chunk.length;
|
bytes += chunk.length;
|
||||||
if (bytes > 16 * 1024 * 1024) throw new Error("Migration input exceeds 16 MiB");
|
if (bytes > 16 * 1024 * 1024) throw new Error("Migration input exceeds 16 MiB");
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { decodeQxValue, type QxValueType } from "./bindings.js";
|
||||||
|
import type { QueryResponse } from "./camino/api_pb.js";
|
||||||
|
import type { QxObjectRef } from "./references.js";
|
||||||
|
|
||||||
|
export type QxQueryPartial<T> = T extends QxObjectRef
|
||||||
|
? T
|
||||||
|
: T extends readonly (infer Item)[]
|
||||||
|
? QxQueryPartial<Item>[]
|
||||||
|
: T extends object
|
||||||
|
? { [Key in keyof T]?: QxQueryPartial<T[Key]> }
|
||||||
|
: T;
|
||||||
|
export type QxQuerySnapshot<T> = {
|
||||||
|
runId: string;
|
||||||
|
sequence: bigint;
|
||||||
|
dataVersion: string;
|
||||||
|
bindingDigest: string;
|
||||||
|
consistency: string;
|
||||||
|
fields: { path: readonly (string | number)[]; status: "pending" | "error"; error?: string }[];
|
||||||
|
} & ({ status: "ready"; data: T } | { status: "partial"; data: QxQueryPartial<T> });
|
||||||
|
declare const queryTypes: unique symbol;
|
||||||
|
/** Generated shape evidence, not permission to run a query. */
|
||||||
|
export interface QxQueryDescriptor<Variables, Result, Root extends string = string> {
|
||||||
|
readonly id: string;
|
||||||
|
readonly definitionDigest: string;
|
||||||
|
readonly rootInterfaceRevisionId: Root;
|
||||||
|
readonly variables: QxValueType;
|
||||||
|
readonly output: QxValueType;
|
||||||
|
readonly watch: boolean;
|
||||||
|
readonly [queryTypes]?: (variables: Variables, result: Result) => [Variables, Result];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeQuerySnapshot<T>(
|
||||||
|
response: QueryResponse,
|
||||||
|
output: QxValueType,
|
||||||
|
runId: string,
|
||||||
|
sequence: bigint,
|
||||||
|
): QxQuerySnapshot<T> {
|
||||||
|
if (response.preparationToken || response.residualWindows.length || response.relationalCaptures.length)
|
||||||
|
throw new Error("QUERY_RESULT_UNFINISHED: private preparation is not a query result");
|
||||||
|
const fields = [
|
||||||
|
...response.pending.map((field) => ({ path: field.path, status: "pending" as const })),
|
||||||
|
...response.errors.map((field) => ({ path: field.path, status: "error" as const, error: field.error })),
|
||||||
|
].map((field) => ({
|
||||||
|
...field,
|
||||||
|
path: field.path.map((part) => {
|
||||||
|
if (part.part.case !== "field" && part.part.case !== "index") throw new Error("QUERY_PATCH_INVALID");
|
||||||
|
return part.part.value;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
// Pending/error values are absent, not successful nulls of a scalar type.
|
||||||
|
const shape = structuredClone(output);
|
||||||
|
for (const field of fields) {
|
||||||
|
let cursor = shape;
|
||||||
|
for (const [index, part] of field.path.entries()) {
|
||||||
|
while (cursor.kind === "optional") cursor = cursor.value;
|
||||||
|
const last = index === field.path.length - 1;
|
||||||
|
if (typeof part === "string" && cursor.kind === "record" && cursor.fields[part]) {
|
||||||
|
if (last) cursor.fields[part] = { kind: "optional", value: cursor.fields[part]! };
|
||||||
|
else cursor = cursor.fields[part]!;
|
||||||
|
} else if (typeof part === "number" && cursor.kind === "list") cursor = cursor.value;
|
||||||
|
else throw new Error("QUERY_PATCH_INVALID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = decodeQxValue(shape, response.value, {}) as Record<string, unknown>;
|
||||||
|
for (const field of fields) {
|
||||||
|
let cursor: unknown = data;
|
||||||
|
for (const [index, part] of field.path.entries()) {
|
||||||
|
if (!cursor || typeof cursor !== "object") throw new Error("QUERY_PATCH_INVALID");
|
||||||
|
if (index === field.path.length - 1) delete (cursor as Record<string | number, unknown>)[part];
|
||||||
|
else cursor = (cursor as Record<string | number, unknown>)[part];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
runId,
|
||||||
|
sequence,
|
||||||
|
dataVersion: response.dataVersion,
|
||||||
|
bindingDigest: response.bindingDigest,
|
||||||
|
consistency: response.consistency,
|
||||||
|
fields,
|
||||||
|
...(fields.length ? { status: "partial", data } : { status: "ready", data }),
|
||||||
|
} as QxQuerySnapshot<T>;
|
||||||
|
}
|
||||||
+469
-23
File diff suppressed because one or more lines are too long
+54
-4
@@ -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("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3MilgEKEkNvbmZvcm1hbmNlV2l0bmVzcxIRCglvYmplY3RfaWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgEIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBSABKAkiQgoQUGFja2FnZUV4cG9ydFJlZhIbChNwYWNrYWdlX3JldmlzaW9uX2lkGAEgASgJEhEKCWV4cG9ydF9pZBgCIAEoCSLYAQoSSW5qZWN0ZWREZXBlbmRlbmN5Eg8KB3BvcnRfaWQYASABKAkSFwoNc3RhdGVfc2xvdF9pZBgCIAEoCUgAEiYKBGVkZ2UYAyABKAsyFi5xdWl4b3MuRWRnZURlcGVuZGVuY3lIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAlIABIdChNjb25zdHJ1Y3Rvcl9hdG9tX2lkGAUgASgJSAASEgoIcXVlcnlfaWQYByABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
@@ -92,6 +136,12 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
|
|||||||
*/
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
case: "constructorAtomId";
|
case: "constructorAtomId";
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* @generated from field: string query_id = 7;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
case: "queryId";
|
||||||
} | { case: undefined; value?: undefined };
|
} | { case: undefined; value?: undefined };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,7 +158,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 +180,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);
|
||||||
|
|
||||||
|
|||||||
+20
-6
@@ -7,11 +7,22 @@ export interface QxObjectRef<Identity extends string = string> {
|
|||||||
equals(other: QxObjectRef<string>): boolean;
|
equals(other: QxObjectRef<string>): boolean;
|
||||||
}
|
}
|
||||||
class Reference {
|
class Reference {
|
||||||
constructor(id: string) { identities.set(this, id); Object.freeze(this); }
|
constructor(id: string) {
|
||||||
equals(other: unknown) { return isObjectReference(other) && identities.get(this) === identities.get(other); }
|
identities.set(this, id);
|
||||||
toJSON(): never { throw new Error("Object references cannot be serialized into ordinary data"); }
|
Object.freeze(this);
|
||||||
toString(): never { throw new Error("Object references cannot be coerced to strings"); }
|
}
|
||||||
[Symbol.toPrimitive](): never { throw new Error("Object references cannot be coerced to scalar values"); }
|
equals(other: unknown) {
|
||||||
|
return isObjectReference(other) && identities.get(this) === identities.get(other);
|
||||||
|
}
|
||||||
|
toJSON(): never {
|
||||||
|
throw new Error("Object references cannot be serialized into ordinary data");
|
||||||
|
}
|
||||||
|
toString(): never {
|
||||||
|
throw new Error("Object references cannot be coerced to strings");
|
||||||
|
}
|
||||||
|
[Symbol.toPrimitive](): never {
|
||||||
|
throw new Error("Object references cannot be coerced to scalar values");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export const isObjectReference = (value: unknown): value is QxObjectRef =>
|
export const isObjectReference = (value: unknown): value is QxObjectRef =>
|
||||||
typeof value === "object" && value !== null && identities.has(value);
|
typeof value === "object" && value !== null && identities.has(value);
|
||||||
@@ -27,7 +38,10 @@ export const referenceToWire = (value: unknown): string => {
|
|||||||
};
|
};
|
||||||
export const assertReferenceFree = (value: unknown, seen = new Set<object>()): void => {
|
export const assertReferenceFree = (value: unknown, seen = new Set<object>()): void => {
|
||||||
if (!value || typeof value !== "object") return;
|
if (!value || typeof value !== "object") return;
|
||||||
if (isObjectReference(value)) throw new Error("Managed references belong in declared RPC references or graph relationships, not ordinary state/messages");
|
if (isObjectReference(value))
|
||||||
|
throw new Error(
|
||||||
|
"Managed references belong in declared RPC references or graph relationships, not ordinary state/messages",
|
||||||
|
);
|
||||||
if (seen.has(value)) throw new Error("Cyclic ordinary data");
|
if (seen.has(value)) throw new Error("Cyclic ordinary data");
|
||||||
seen.add(value);
|
seen.add(value);
|
||||||
if (!(value instanceof Uint8Array)) for (const child of Object.values(value)) assertReferenceFree(child, seen);
|
if (!(value instanceof Uint8Array)) for (const child of Object.values(value)) assertReferenceFree(child, seen);
|
||||||
|
|||||||
+24
-7
@@ -1,7 +1,10 @@
|
|||||||
import type { QxObjectRef } from "./references.js";
|
import type { QxObjectRef } from "./references.js";
|
||||||
import type { RelationshipCollection, RelationshipEntry } from "./index.js";
|
import type { RelationshipCollection, RelationshipEntry } from "./index.js";
|
||||||
type Key = string | boolean | bigint;
|
type Key = string | boolean | bigint;
|
||||||
type Port<T extends QxObjectRef> = {collection(): Promise<RelationshipCollection<T>>; replace(entries: RelationshipEntry<T>[], expectedRevision: bigint): Promise<RelationshipCollection<T>>};
|
type Port<T extends QxObjectRef> = {
|
||||||
|
collection(): Promise<RelationshipCollection<T>>;
|
||||||
|
replace(entries: RelationshipEntry<T>[], expectedRevision: bigint): Promise<RelationshipCollection<T>>;
|
||||||
|
};
|
||||||
const checked = async <T extends QxObjectRef>(port: Port<T>, revision: bigint) => {
|
const checked = async <T extends QxObjectRef>(port: Port<T>, revision: bigint) => {
|
||||||
const snapshot = await port.collection();
|
const snapshot = await port.collection();
|
||||||
if (snapshot.revision !== revision) throw new Error("STALE_COLLECTION_REVISION");
|
if (snapshot.revision !== revision) throw new Error("STALE_COLLECTION_REVISION");
|
||||||
@@ -10,7 +13,10 @@ const checked = async <T extends QxObjectRef>(port: Port<T>, revision: bigint) =
|
|||||||
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
|
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
|
||||||
export const relationshipMap = <T extends QxObjectRef, K extends Key = Key>(port: Port<T>) => ({
|
export const relationshipMap = <T extends QxObjectRef, K extends Key = Key>(port: Port<T>) => ({
|
||||||
read: () => port.collection(),
|
read: () => port.collection(),
|
||||||
async get(key: K) {const snapshot = await port.collection(); return {revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target};},
|
async get(key: K) {
|
||||||
|
const snapshot = await port.collection();
|
||||||
|
return { revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target };
|
||||||
|
},
|
||||||
async set(key: K, target: T, expectedRevision: bigint) {
|
async set(key: K, target: T, expectedRevision: bigint) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
const entries = snapshot.entries.filter((entry) => entry.key !== key);
|
const entries = snapshot.entries.filter((entry) => entry.key !== key);
|
||||||
@@ -20,21 +26,26 @@ export const relationshipMap = <T extends QxObjectRef, K extends Key = Key>(port
|
|||||||
},
|
},
|
||||||
async delete(key: K, expectedRevision: bigint) {
|
async delete(key: K, expectedRevision: bigint) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
return port.replace(snapshot.entries.filter((entry) => entry.key !== key), expectedRevision);
|
return port.replace(
|
||||||
|
snapshot.entries.filter((entry) => entry.key !== key),
|
||||||
|
expectedRevision,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const relationshipList = <T extends QxObjectRef>(port: Port<T>) => ({
|
export const relationshipList = <T extends QxObjectRef>(port: Port<T>) => ({
|
||||||
read: () => port.collection(),
|
read: () => port.collection(),
|
||||||
async insert(index: number, target: T, expectedRevision: bigint) {
|
async insert(index: number, target: T, expectedRevision: bigint) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
if (!Number.isSafeInteger(index) || index < 0 || index > snapshot.entries.length) throw new Error("List index out of bounds");
|
if (!Number.isSafeInteger(index) || index < 0 || index > snapshot.entries.length)
|
||||||
|
throw new Error("List index out of bounds");
|
||||||
snapshot.entries.splice(index, 0, { target });
|
snapshot.entries.splice(index, 0, { target });
|
||||||
return port.replace(snapshot.entries, expectedRevision);
|
return port.replace(snapshot.entries, expectedRevision);
|
||||||
},
|
},
|
||||||
async move(edgeId: string, index: number, expectedRevision: bigint) {
|
async move(edgeId: string, index: number, expectedRevision: bigint) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
const prior = snapshot.entries.findIndex((entry) => entry.edgeId === edgeId);
|
const prior = snapshot.entries.findIndex((entry) => entry.edgeId === edgeId);
|
||||||
if (prior < 0 || !Number.isSafeInteger(index) || index < 0 || index >= snapshot.entries.length) throw new Error("Unknown list entry or invalid index");
|
if (prior < 0 || !Number.isSafeInteger(index) || index < 0 || index >= snapshot.entries.length)
|
||||||
|
throw new Error("Unknown list entry or invalid index");
|
||||||
const [entry] = snapshot.entries.splice(prior, 1);
|
const [entry] = snapshot.entries.splice(prior, 1);
|
||||||
snapshot.entries.splice(index, 0, entry);
|
snapshot.entries.splice(index, 0, entry);
|
||||||
return port.replace(snapshot.entries, expectedRevision);
|
return port.replace(snapshot.entries, expectedRevision);
|
||||||
@@ -42,7 +53,10 @@ export const relationshipList = <T extends QxObjectRef>(port: Port<T>) => ({
|
|||||||
async delete(edgeId: string, expectedRevision: bigint) {
|
async delete(edgeId: string, expectedRevision: bigint) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
if (!snapshot.entries.some((entry) => entry.edgeId === edgeId)) throw new Error("Unknown list entry");
|
if (!snapshot.entries.some((entry) => entry.edgeId === edgeId)) throw new Error("Unknown list entry");
|
||||||
return port.replace(snapshot.entries.filter((entry) => entry.edgeId !== edgeId), expectedRevision);
|
return port.replace(
|
||||||
|
snapshot.entries.filter((entry) => entry.edgeId !== edgeId),
|
||||||
|
expectedRevision,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const relationshipSet = <T extends QxObjectRef>(port: Port<T>) => ({
|
export const relationshipSet = <T extends QxObjectRef>(port: Port<T>) => ({
|
||||||
@@ -54,6 +68,9 @@ export const relationshipSet = <T extends QxObjectRef>(port: Port<T>) => ({
|
|||||||
},
|
},
|
||||||
async delete(target: T, expectedRevision: bigint) {
|
async delete(target: T, expectedRevision: bigint) {
|
||||||
const snapshot = await checked(port, expectedRevision);
|
const snapshot = await checked(port, expectedRevision);
|
||||||
return port.replace(snapshot.entries.filter((entry) => !entry.target.equals(target)), expectedRevision);
|
return port.replace(
|
||||||
|
snapshot.entries.filter((entry) => !entry.target.equals(target)),
|
||||||
|
expectedRevision,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+134
-41
@@ -1,28 +1,53 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import { referenceFromWire } from "../dist/references.js";
|
import { referenceFromWire } from "../dist/references.js";
|
||||||
import { bindQxHandler, decodeQxValue, encodeQxValue, jsToProtoValue, liveValue, protoValueToJs, opaqueReactPropsBinding } from "../dist/index.js";
|
import {
|
||||||
|
bindQxHandler,
|
||||||
|
decodeQxValue,
|
||||||
|
encodeQxValue,
|
||||||
|
jsToProtoValue,
|
||||||
|
liveValue,
|
||||||
|
protoValueToJs,
|
||||||
|
} from "../dist/index.js";
|
||||||
const scalar = (name) => ({ kind: "scalar", name });
|
const scalar = (name) => ({ kind: "scalar", name });
|
||||||
const unit = { kind: "builtin", name: "unit" };
|
const unit = { kind: "builtin", name: "unit" };
|
||||||
test("opaque React props retain managed references while ordinary messages remain closed", () => {
|
test("free function handlers never acquire a receiver or object session", async () => {
|
||||||
|
const handler = bindQxHandler(
|
||||||
|
{ receiver: "none", inputType: unit, outputType: unit, ports: {} },
|
||||||
|
async (context) => {
|
||||||
|
assert.equal("objectId" in context, false);
|
||||||
|
assert.equal("openSession" in context, false);
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
await handler({
|
||||||
|
inputProto: {},
|
||||||
|
get objectId() {
|
||||||
|
throw new Error("no receiver exists");
|
||||||
|
},
|
||||||
|
openSession() {
|
||||||
|
assert.fail("no object session");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("React props no longer have an opaque-message reference escape hatch", () => {
|
||||||
const descriptorId = "org.quixos.web-studio.ReactProps";
|
const descriptorId = "org.quixos.web-studio.ReactProps";
|
||||||
const reference = referenceFromWire("obj:task");
|
const reference = referenceFromWire("obj:task");
|
||||||
const type = { kind: "message", descriptorId };
|
const type = { kind: "message", descriptorId };
|
||||||
const messages = {[descriptorId]: opaqueReactPropsBinding};
|
const messages = { [descriptorId]: { encode: jsToProtoValue, decode: protoValueToJs } };
|
||||||
const decoded = decodeQxValue(type, encodeQxValue(type, {subject: reference, title: "Task"}, messages), messages);
|
assert.throws(() => encodeQxValue(type, { subject: reference }, messages), /Managed references/);
|
||||||
assert.ok(decoded.subject.equals(reference));
|
assert.throws(() => decodeQxValue(type, jsToProtoValue({ subject: reference }), messages), /Managed references/);
|
||||||
assert.equal(decoded.title, "Task");
|
|
||||||
assert.throws(() => opaqueReactPropsBinding.encode(null), /object/);
|
|
||||||
assert.throws(() => opaqueReactPropsBinding.encode([]), /object/);
|
|
||||||
assert.throws(() => encodeQxValue({kind: "message", descriptorId: "Ordinary"}, {subject: reference},
|
|
||||||
{Ordinary: opaqueReactPropsBinding}), /Managed references/);
|
|
||||||
});
|
});
|
||||||
test("declared record inputs preserve opaque references without opening message codecs", async () => {
|
test("declared record inputs preserve opaque references without opening message codecs", async () => {
|
||||||
const target = referenceFromWire("obj:board");
|
const target = referenceFromWire("obj:board");
|
||||||
const type = {kind: "record", fields: {
|
const type = {
|
||||||
|
kind: "record",
|
||||||
|
fields: {
|
||||||
target: { kind: "object-ref", expectation: { kind: "atom", atomId: "board" } },
|
target: { kind: "object-ref", expectation: { kind: "atom", atomId: "board" } },
|
||||||
x: scalar("double"), note: {kind: "optional", value: scalar("string")},
|
x: scalar("double"),
|
||||||
}};
|
note: { kind: "optional", value: scalar("string") },
|
||||||
|
},
|
||||||
|
};
|
||||||
const encoded = encodeQxValue(type, { target, x: 12 }, {});
|
const encoded = encodeQxValue(type, { target, x: 12 }, {});
|
||||||
const decoded = decodeQxValue(type, encoded, {});
|
const decoded = decodeQxValue(type, encoded, {});
|
||||||
assert.ok(decoded.target.equals(target));
|
assert.ok(decoded.target.equals(target));
|
||||||
@@ -32,20 +57,43 @@ test("declared record inputs preserve opaque references without opening message
|
|||||||
assert.throws(() => encodeQxValue(type, { target, x: 12, hidden: target }, {}), /Unexpected/);
|
assert.throws(() => encodeQxValue(type, { target, x: 12, hidden: target }, {}), /Unexpected/);
|
||||||
assert.throws(() => encodeQxValue(type, { x: 12 }, {}), /Missing/);
|
assert.throws(() => encodeQxValue(type, { x: 12 }, {}), /Missing/);
|
||||||
assert.throws(() => encodeQxValue(type, { target: "obj:board", x: 12 }, {}), /opaque object reference/);
|
assert.throws(() => encodeQxValue(type, { target: "obj:board", x: 12 }, {}), /opaque object reference/);
|
||||||
const handler = bindQxHandler({inputType: type, outputType: unit, ports: {}}, async (context) => {
|
const handler = bindQxHandler(
|
||||||
|
{ inputType: type, outputType: unit, ports: {} },
|
||||||
|
async (context) => {
|
||||||
assert.ok(context.input.target.equals(target));
|
assert.ok(context.input.target.equals(target));
|
||||||
assert.equal(context.input.x, 12);
|
assert.equal(context.input.x, 12);
|
||||||
}, {});
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
await handler({ objectId: target, inputProto: encoded.kind.value.fields });
|
await handler({ objectId: target, inputProto: encoded.kind.value.fields });
|
||||||
});
|
});
|
||||||
test("typed sessions rebind ports and cancellation to each acquired invocation", async () => {
|
test("typed sessions rebind ports and cancellation to each acquired invocation", async () => {
|
||||||
const first = new AbortController(), second = new AbortController();
|
const first = new AbortController(),
|
||||||
|
second = new AbortController();
|
||||||
let closed = false;
|
let closed = false;
|
||||||
const context = (value, signal) => ({objectId: referenceFromWire("obj:owner"), inputProto: {}, signal,
|
const context = (value, signal) => ({
|
||||||
state: () => ({live: async () => liveValue(jsToProtoValue(value))})});
|
objectId: referenceFromWire("obj:owner"),
|
||||||
const raw = {...context(1n, first.signal), openSession: async () => ({id: "session:test",
|
inputProto: {},
|
||||||
run: (work) => work(context(2n, second.signal)), close: async () => {closed = true;}})};
|
signal,
|
||||||
const handler = bindQxHandler({inputType: unit, outputType: unit, ports: {counter: {kind: "state", id: "counter", valueType: scalar("int64"), primitives: ["read"]}}}, async (bound) => {
|
state: () => ({ live: async () => liveValue(jsToProtoValue(value)) }),
|
||||||
|
});
|
||||||
|
const raw = {
|
||||||
|
...context(1n, first.signal),
|
||||||
|
openSession: async () => ({
|
||||||
|
id: "session:test",
|
||||||
|
run: (work) => work(context(2n, second.signal)),
|
||||||
|
close: async () => {
|
||||||
|
closed = true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const handler = bindQxHandler(
|
||||||
|
{
|
||||||
|
inputType: unit,
|
||||||
|
outputType: unit,
|
||||||
|
ports: { counter: { kind: "state", id: "counter", valueType: scalar("int64"), primitives: ["read"] } },
|
||||||
|
},
|
||||||
|
async (bound) => {
|
||||||
assert.equal(bound.signal, first.signal);
|
assert.equal(bound.signal, first.signal);
|
||||||
const session = await bound.openSession();
|
const session = await bound.openSession();
|
||||||
await session.run(async (next) => {
|
await session.run(async (next) => {
|
||||||
@@ -54,15 +102,20 @@ test("typed sessions rebind ports and cancellation to each acquired invocation",
|
|||||||
assert.equal(next.openSession, undefined);
|
assert.equal(next.openSession, undefined);
|
||||||
});
|
});
|
||||||
await session.close();
|
await session.close();
|
||||||
}, {});
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
await handler(raw);
|
await handler(raw);
|
||||||
assert.equal(closed, true);
|
assert.equal(closed, true);
|
||||||
});
|
});
|
||||||
test("binding codecs round trip nested bytes, 64-bit integers, nulls, and references", () => {
|
test("binding codecs round trip nested bytes, 64-bit integers, nulls, and references", () => {
|
||||||
const values = [[scalar("int64"), -(2n ** 63n)], [scalar("uint64"), 2n ** 64n - 1n],
|
const values = [
|
||||||
|
[scalar("int64"), -(2n ** 63n)],
|
||||||
|
[scalar("uint64"), 2n ** 64n - 1n],
|
||||||
[scalar("bytes"), new Uint8Array([0, 255])],
|
[scalar("bytes"), new Uint8Array([0, 255])],
|
||||||
[{ kind: "list", value: { kind: "optional", value: scalar("int64") } }, [null, 2n ** 60n]],
|
[{ kind: "list", value: { kind: "optional", value: scalar("int64") } }, [null, 2n ** 60n]],
|
||||||
[{ kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } }, referenceFromWire("obj:thing")]];
|
[{ kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } }, referenceFromWire("obj:thing")],
|
||||||
|
];
|
||||||
for (const [type, value] of values) assert.deepEqual(decodeQxValue(type, encodeQxValue(type, value, {}), {}), value);
|
for (const [type, value] of values) assert.deepEqual(decodeQxValue(type, encodeQxValue(type, value, {}), {}), value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,17 +130,36 @@ test("opaque references pass declared RPC boundaries but cannot enter ordinary d
|
|||||||
assert.throws(() => encodeQxValue(type, "obj:thing", {}), /opaque/);
|
assert.throws(() => encodeQxValue(type, "obj:thing", {}), /opaque/);
|
||||||
assert.throws(() => encodeQxValue(scalar("string"), reference, {}), /Managed references/);
|
assert.throws(() => encodeQxValue(scalar("string"), reference, {}), /Managed references/);
|
||||||
const message = { kind: "message", descriptorId: "Payload" };
|
const message = { kind: "message", descriptorId: "Payload" };
|
||||||
assert.throws(() => encodeQxValue(message, {nested: reference}, {Payload: {encode: jsToProtoValue}}), /Managed references/);
|
assert.throws(
|
||||||
assert.throws(() => encodeQxValue(message, {}, {Payload: {encode: () => jsToProtoValue(reference)}}), /Managed references/);
|
() => encodeQxValue(message, { nested: reference }, { Payload: { encode: jsToProtoValue } }),
|
||||||
assert.throws(() => decodeQxValue(message, jsToProtoValue(reference), {Payload: {decode: () => ({})}}), /Managed references/);
|
/Managed references/,
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => encodeQxValue(message, {}, { Payload: { encode: () => jsToProtoValue(reference) } }),
|
||||||
|
/Managed references/,
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => decodeQxValue(message, jsToProtoValue(reference), { Payload: { decode: () => ({}) } }),
|
||||||
|
/Managed references/,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
test("typed state and interface ports preserve declared values and exact operation IDs", async () => {
|
test("typed state and interface ports preserve declared values and exact operation IDs", async () => {
|
||||||
const spec = { inputType: scalar("int64"), outputType: scalar("bytes"), ports: {
|
const spec = {
|
||||||
|
inputType: scalar("int64"),
|
||||||
|
outputType: scalar("bytes"),
|
||||||
|
ports: {
|
||||||
data: { kind: "state", id: "state-id", valueType: scalar("int64"), primitives: ["read", "write"] },
|
data: { kind: "state", id: "state-id", valueType: scalar("int64"), primitives: ["read", "write"] },
|
||||||
reader: { kind: "interface", id: "interface-id", operations: { "payload.get": { id: "get-id", inputType: unit, outputType: scalar("bytes") } } },
|
reader: {
|
||||||
} };
|
kind: "interface",
|
||||||
|
id: "interface-id",
|
||||||
|
operations: { "payload.get": { id: "get-id", inputType: unit, outputType: scalar("bytes") } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
let written;
|
let written;
|
||||||
const handler = bindQxHandler(spec, async ({ input, ports }) => {
|
const handler = bindQxHandler(
|
||||||
|
spec,
|
||||||
|
async ({ input, ports }) => {
|
||||||
assert.equal(input, 2n ** 60n);
|
assert.equal(input, 2n ** 60n);
|
||||||
assert.equal(await ports.data.get(), 9n);
|
assert.equal(await ports.data.get(), 9n);
|
||||||
assert.deepEqual((await ports.data.live()).$quixosValue, jsToProtoValue(9n));
|
assert.deepEqual((await ports.data.live()).$quixosValue, jsToProtoValue(9n));
|
||||||
@@ -95,14 +167,32 @@ test("typed state and interface ports preserve declared values and exact operati
|
|||||||
assert.deepEqual((await ports.reader.live["payload.get"]()).$quixosValue, jsToProtoValue(new Uint8Array([7])));
|
assert.deepEqual((await ports.reader.live["payload.get"]()).$quixosValue, jsToProtoValue(new Uint8Array([7])));
|
||||||
await ports.data.set(input);
|
await ports.data.set(input);
|
||||||
return ports.reader["payload.get"]();
|
return ports.reader["payload.get"]();
|
||||||
}, {});
|
},
|
||||||
const result = await handler({ inputProto: { value: jsToProtoValue(2n ** 60n) }, objectId: "obj",
|
{},
|
||||||
state: (id) => { assert.equal(id, "state-id"); return {
|
);
|
||||||
live: async () => liveValue(jsToProtoValue(9n)), set: async (value) => { written = value; },
|
const result = await handler({
|
||||||
}; },
|
inputProto: { value: jsToProtoValue(2n ** 60n) },
|
||||||
interface: (id) => { assert.equal(id, "interface-id"); return { objectId: referenceFromWire("obj:reader"), live: async (operation, input) => {
|
objectId: "obj",
|
||||||
assert.equal(operation, "get-id"); assert.deepEqual(input, {}); return liveValue(jsToProtoValue(new Uint8Array([7])));
|
state: (id) => {
|
||||||
} }; },
|
assert.equal(id, "state-id");
|
||||||
|
return {
|
||||||
|
live: async () => liveValue(jsToProtoValue(9n)),
|
||||||
|
set: async (value) => {
|
||||||
|
written = value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
interface: (id) => {
|
||||||
|
assert.equal(id, "interface-id");
|
||||||
|
return {
|
||||||
|
objectId: referenceFromWire("obj:reader"),
|
||||||
|
live: async (operation, input) => {
|
||||||
|
assert.equal(operation, "get-id");
|
||||||
|
assert.deepEqual(input, {});
|
||||||
|
return liveValue(jsToProtoValue(new Uint8Array([7])));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
assert.equal(written.$quixosValue.kind.value, String(2n ** 60n));
|
assert.equal(written.$quixosValue.kind.value, String(2n ** 60n));
|
||||||
assert.deepEqual(result.$quixosValue.kind.value, new Uint8Array([7]));
|
assert.deepEqual(result.$quixosValue.kind.value, new Uint8Array([7]));
|
||||||
@@ -110,8 +200,11 @@ test("typed state and interface ports preserve declared values and exact operati
|
|||||||
test("external message bindings and derived event types are used at the boundary", async () => {
|
test("external message bindings and derived event types are used at the boundary", async () => {
|
||||||
const message = { kind: "message", descriptorId: "Payload" };
|
const message = { kind: "message", descriptorId: "Payload" };
|
||||||
const messages = { Payload: { encode: jsToProtoValue, decode: protoValueToJs } };
|
const messages = { Payload: { encode: jsToProtoValue, decode: protoValueToJs } };
|
||||||
const handler = bindQxHandler({ inputType: message, outputType: { kind: "builtin", name: "watch-handle" }, eventType: message, ports: {} },
|
const handler = bindQxHandler(
|
||||||
{ kind: "derived", get: ({ input }) => ({ value: input.title }) }, messages);
|
{ inputType: message, outputType: { kind: "builtin", name: "watch-handle" }, eventType: message, ports: {} },
|
||||||
|
{ kind: "derived", get: ({ input }) => ({ value: input.title }) },
|
||||||
|
messages,
|
||||||
|
);
|
||||||
assert.equal(handler.kind, "derived");
|
assert.equal(handler.kind, "derived");
|
||||||
const result = await handler.get({ objectId: "obj", inputProto: { title: jsToProtoValue("hello") } });
|
const result = await handler.get({ objectId: "obj", inputProto: { title: jsToProtoValue("hello") } });
|
||||||
assert.deepEqual(protoValueToJs(result.$quixosValue), { value: "hello" });
|
assert.deepEqual(protoValueToJs(result.$quixosValue), { value: "hello" });
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { bindQxHandler, createRuntimeContext, defineQxInterfaceContract, jsToProtoValue } from "../dist/index.js";
|
||||||
|
import { referenceFromWire } from "../dist/references.js";
|
||||||
|
|
||||||
|
const unit = { kind: "builtin", name: "unit" };
|
||||||
|
test("generated conformance views use checked codecs and retain their fence on invocation", async () => {
|
||||||
|
const contract = defineQxInterfaceContract("Named", {
|
||||||
|
"name.get": { id: "get", inputType: unit, outputType: { kind: "scalar", name: "string" } },
|
||||||
|
});
|
||||||
|
const object = referenceFromWire("object");
|
||||||
|
const witness = {
|
||||||
|
objectId: "object",
|
||||||
|
interfaceRevisionId: "Named",
|
||||||
|
workspaceEpoch: "1",
|
||||||
|
workspaceRevisionId: "w",
|
||||||
|
conformanceId: "named",
|
||||||
|
};
|
||||||
|
const requests = [];
|
||||||
|
const raw = createRuntimeContext(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
async tryConform(request) {
|
||||||
|
requests.push(request);
|
||||||
|
return { conformance: witness };
|
||||||
|
},
|
||||||
|
async invokeCapability(request) {
|
||||||
|
for (const key of Object.keys(witness)) assert.equal(request.capability.conformance[key], witness[key]);
|
||||||
|
assert.deepEqual(request.input, {});
|
||||||
|
return { ok: true, result: jsToProtoValue("Hello"), dependencies: [] };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ objectId: "object", input: {}, dependencies: [] },
|
||||||
|
);
|
||||||
|
const handler = bindQxHandler(
|
||||||
|
{ inputType: unit, outputType: unit, ports: {} },
|
||||||
|
async (context) => {
|
||||||
|
const view = await context.conform.tryConform(object, contract);
|
||||||
|
assert.ok(view.objectId.equals(object));
|
||||||
|
assert.equal(view.contract, contract);
|
||||||
|
assert.equal(await view["name.get"](), "Hello");
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
await handler(raw);
|
||||||
|
assert.deepEqual(requests, [{ objectId: "object", interfaceRevisionId: "Named" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("package discovery preserves absence and denied errors and refuses raw IDs", async () => {
|
||||||
|
const request = { objectId: "object", input: {}, dependencies: [] };
|
||||||
|
const raw = createRuntimeContext(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
async tryConform() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
assert.equal(await raw.tryConform(referenceFromWire("object"), "Missing"), undefined);
|
||||||
|
await assert.rejects(raw.tryConform("object", "Named"), /opaque object reference/);
|
||||||
|
const denied = createRuntimeContext(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
async tryConform() {
|
||||||
|
throw Error("permission denied");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
await assert.rejects(denied.tryConform(referenceFromWire("object"), "Named"), /permission denied/);
|
||||||
|
});
|
||||||
@@ -1,30 +1,47 @@
|
|||||||
import test from 'node:test';
|
import test from "node:test";
|
||||||
import assert from 'node:assert/strict';
|
import assert from "node:assert/strict";
|
||||||
import fs from 'node:fs/promises';
|
import fs from "node:fs/promises";
|
||||||
import os from 'node:os';
|
import os from "node:os";
|
||||||
import path from 'node:path';
|
import path from "node:path";
|
||||||
import crypto from 'node:crypto';
|
import crypto from "node:crypto";
|
||||||
import {spawnSync} from 'node:child_process';
|
import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
test('supervised SDK reads a credential file and proves instance identity without an environment token', async t => {
|
test("supervised SDK reads a credential file and proves instance identity without an environment token", async (t) => {
|
||||||
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'qx-sdk-credential-'));
|
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-sdk-credential-"));
|
||||||
t.after(() => fs.rm(directory, { recursive: true, force: true }));
|
t.after(() => fs.rm(directory, { recursive: true, force: true }));
|
||||||
const token = crypto.randomBytes(32).toString('hex');
|
const token = crypto.randomBytes(32).toString("hex");
|
||||||
const filename = path.join(directory, 'process-token');
|
const filename = path.join(directory, "process-token");
|
||||||
await fs.writeFile(filename, token, { mode: 0o600 });
|
await fs.writeFile(filename, token, { mode: 0o600 });
|
||||||
const env = {...process.env, CAMINO_RUNTIME_AUTH_TOKEN_FILE: filename,
|
const env = {
|
||||||
CAMINO_RUNTIME_AUTH_REQUIRED:'1', QUIXOS_RUNTIME_INSTANCE_ID:'instance:test'};
|
...process.env,
|
||||||
|
CAMINO_RUNTIME_AUTH_TOKEN_FILE: filename,
|
||||||
|
CAMINO_RUNTIME_AUTH_REQUIRED: "1",
|
||||||
|
QUIXOS_RUNTIME_INSTANCE_ID: "instance:test",
|
||||||
|
};
|
||||||
delete env.CAMINO_RUNTIME_AUTH_TOKEN;
|
delete env.CAMINO_RUNTIME_AUTH_TOKEN;
|
||||||
const result = spawnSync(process.execPath, ['--input-type=module', '-e', `
|
const result = spawnSync(
|
||||||
import {createPackageRuntimeRoutes} from ${JSON.stringify(new URL('../dist/index.js', import.meta.url).href)};
|
process.execPath,
|
||||||
|
[
|
||||||
|
"--input-type=module",
|
||||||
|
"-e",
|
||||||
|
`
|
||||||
|
import {createPackageRuntimeRoutes} from ${JSON.stringify(new URL("../dist/index.js", import.meta.url).href)};
|
||||||
createPackageRuntimeRoutes({packageRevisionId:'package:test',exports:{}})({
|
createPackageRuntimeRoutes({packageRevisionId:'package:test',exports:{}})({
|
||||||
service(_type, implementation) { console.log(JSON.stringify(implementation.handshake({nonce:'challenge'}))); }
|
service(_type, implementation) { console.log(JSON.stringify(implementation.handshake({nonce:'challenge'}))); }
|
||||||
});
|
});
|
||||||
`], {env, encoding:'utf8'});
|
`,
|
||||||
|
],
|
||||||
|
{ env, encoding: "utf8" },
|
||||||
|
);
|
||||||
assert.equal(result.status, 0, result.stderr);
|
assert.equal(result.status, 0, result.stderr);
|
||||||
const handshake = JSON.parse(result.stdout);
|
const handshake = JSON.parse(result.stdout);
|
||||||
assert.equal(handshake.authenticationProof, crypto.createHmac('sha256', token)
|
assert.equal(
|
||||||
.update(JSON.stringify(['challenge','instance:test','package:test'])).digest('hex'));
|
handshake.authenticationProof,
|
||||||
assert.ok(handshake.capabilities.includes('epoch-grants-v1'));
|
crypto
|
||||||
|
.createHmac("sha256", token)
|
||||||
|
.update(JSON.stringify(["challenge", "instance:test", "package:test"]))
|
||||||
|
.digest("hex"),
|
||||||
|
);
|
||||||
|
assert.ok(handshake.capabilities.includes("epoch-grants-v1"));
|
||||||
assert.ok(!result.stdout.includes(token));
|
assert.ok(!result.stdout.includes(token));
|
||||||
});
|
});
|
||||||
|
|||||||
+16
-4
@@ -2,11 +2,23 @@ import test from "node:test";
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { createMigrationContext, migrationObjectId } from "../dist/migration.js";
|
import { createMigrationContext, migrationObjectId } from "../dist/migration.js";
|
||||||
test("migration context offers restricted ports and deterministic helper identities", () => {
|
test("migration context offers restricted ports and deterministic helper identities", () => {
|
||||||
const input = {schemaVersion: 1, executionId: "operation:transition", exportId: "migrate", ports: [
|
const input = {
|
||||||
{name: "old", binding: "slot:name", view: "old", access: ["read"], states: [{objectId: "obj:one", value: "Alice"}]},
|
schemaVersion: 1,
|
||||||
{name: "new", binding: "slot:name", view: "new", access: ["write"]}, {name: "newRead", binding: "slot:name", view: "new", access: ["read"]},
|
executionId: "operation:transition",
|
||||||
|
exportId: "migrate",
|
||||||
|
ports: [
|
||||||
|
{
|
||||||
|
name: "old",
|
||||||
|
binding: "slot:name",
|
||||||
|
view: "old",
|
||||||
|
access: ["read"],
|
||||||
|
states: [{ objectId: "obj:one", value: "Alice" }],
|
||||||
|
},
|
||||||
|
{ name: "new", binding: "slot:name", view: "new", access: ["write"] },
|
||||||
|
{ name: "newRead", binding: "slot:name", view: "new", access: ["read"] },
|
||||||
{ name: "helpers", binding: "atom:helper", view: "new", access: ["create"] },
|
{ name: "helpers", binding: "atom:helper", view: "new", access: ["create"] },
|
||||||
]};
|
],
|
||||||
|
};
|
||||||
const { context, result } = createMigrationContext(input);
|
const { context, result } = createMigrationContext(input);
|
||||||
assert.equal(context.read("old", "obj:one"), "Alice");
|
assert.equal(context.read("old", "obj:one"), "Alice");
|
||||||
assert.throws(() => context.write("old", "obj:one", "Bob"), /does not grant/);
|
assert.throws(() => context.write("old", "obj:one", "Bob"), /does not grant/);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { create } from "@bufbuild/protobuf";
|
||||||
|
import { QueryResponseSchema } from "../dist/camino/api_pb.js";
|
||||||
|
import { InjectedDependencySchema } from "../dist/quixos/refs_pb.js";
|
||||||
|
import { createRuntimeContext, decodeQuerySnapshot, jsToProtoValue, protoValueToJs } from "../dist/index.js";
|
||||||
|
|
||||||
|
test("private relational captures cannot be decoded as a completed public query", () => {
|
||||||
|
const response = create(QueryResponseSchema, { relationalCaptures: [{ rootObjectId: "obj:private" }] });
|
||||||
|
assert.throws(
|
||||||
|
() => decodeQuerySnapshot(response, { kind: "record", fields: {} }, "run", 1n),
|
||||||
|
/QUERY_RESULT_UNFINISHED/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("query ports carry only an injected query identity and root", async () => {
|
||||||
|
let seen;
|
||||||
|
const context = createRuntimeContext(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
executeQuery: async (request) => {
|
||||||
|
seen = request;
|
||||||
|
return create(QueryResponseSchema);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objectId: "obj:receiver",
|
||||||
|
input: {},
|
||||||
|
dependencies: [
|
||||||
|
create(InjectedDependencySchema, {
|
||||||
|
portId: "list",
|
||||||
|
objectId: "obj:collection",
|
||||||
|
binding: { case: "queryId", value: "pkg@1:upcoming" },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await context.query("list").execute({ first: jsToProtoValue(30) }, "checked-definition");
|
||||||
|
assert.equal(seen.expectedDefinitionDigest, "checked-definition");
|
||||||
|
assert.equal(seen.objectId, "obj:collection");
|
||||||
|
assert.equal(seen.queryId, "pkg@1:upcoming");
|
||||||
|
assert.equal(protoValueToJs(seen.variables.first), 30);
|
||||||
|
assert.throws(() => context.query("not-injected"), /Missing/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("query snapshots distinguish an unavailable scalar from a successful null", () => {
|
||||||
|
const output = {
|
||||||
|
kind: "record",
|
||||||
|
fields: { title: { kind: "scalar", name: "string" }, score: { kind: "scalar", name: "int64" } },
|
||||||
|
};
|
||||||
|
const response = create(QueryResponseSchema, {
|
||||||
|
value: jsToProtoValue({ title: "Native title", score: null }),
|
||||||
|
pending: [{ path: [{ part: { case: "field", value: "score" } }] }],
|
||||||
|
});
|
||||||
|
const snapshot = decodeQuerySnapshot(response, output, "run1", 1n);
|
||||||
|
assert.equal(snapshot.status, "partial");
|
||||||
|
assert.deepEqual(snapshot.data, { title: "Native title" });
|
||||||
|
assert.deepEqual(snapshot.fields, [{ path: ["score"], status: "pending" }]);
|
||||||
|
assert.equal(output.fields.score.kind, "scalar");
|
||||||
|
response.pending = [];
|
||||||
|
response.value = jsToProtoValue({ title: "Native title", score: 9007199254740993n });
|
||||||
|
assert.equal(decodeQuerySnapshot(response, output, "run1", 2n).data.score, 9007199254740993n);
|
||||||
|
});
|
||||||
@@ -3,14 +3,21 @@ import assert from "node:assert/strict";
|
|||||||
import { relationshipMap, relationshipList, relationshipSet } from "../dist/index.js";
|
import { relationshipMap, relationshipList, relationshipSet } from "../dist/index.js";
|
||||||
import { referenceFromWire } from "../dist/references.js";
|
import { referenceFromWire } from "../dist/references.js";
|
||||||
test("relationship helpers preserve handles and require explicit collection revisions", async () => {
|
test("relationship helpers preserve handles and require explicit collection revisions", async () => {
|
||||||
let current = {revision: 0n, entries: []}, serial = 0;
|
let current = { revision: 0n, entries: [] },
|
||||||
const port = {collection: async () => ({revision: current.revision, entries: current.entries.map((entry) => ({...entry}))}),
|
serial = 0;
|
||||||
|
const port = {
|
||||||
|
collection: async () => ({ revision: current.revision, entries: current.entries.map((entry) => ({ ...entry })) }),
|
||||||
replace: async (entries, revision) => {
|
replace: async (entries, revision) => {
|
||||||
assert.equal(revision, current.revision);
|
assert.equal(revision, current.revision);
|
||||||
current = {revision: revision + 1n, entries: entries.map((entry) => ({...entry, edgeId: entry.edgeId ?? `edge:${++serial}`}))};
|
current = {
|
||||||
|
revision: revision + 1n,
|
||||||
|
entries: entries.map((entry) => ({ ...entry, edgeId: entry.edgeId ?? `edge:${++serial}` })),
|
||||||
|
};
|
||||||
return port.collection();
|
return port.collection();
|
||||||
}};
|
},
|
||||||
const a = referenceFromWire("obj:a"), b = referenceFromWire("obj:b");
|
};
|
||||||
|
const a = referenceFromWire("obj:a"),
|
||||||
|
b = referenceFromWire("obj:b");
|
||||||
const map = relationshipMap(port);
|
const map = relationshipMap(port);
|
||||||
await map.set("a1", a, 0n);
|
await map.set("a1", a, 0n);
|
||||||
assert.equal((await map.get("a1")).value.equals(a), true);
|
assert.equal((await map.get("a1")).value.equals(a), true);
|
||||||
|
|||||||
+104
-55
@@ -21,11 +21,7 @@ import {
|
|||||||
ValueSchema,
|
ValueSchema,
|
||||||
ValueSourceSchema,
|
ValueSourceSchema,
|
||||||
} from "../dist/camino/api_pb.js";
|
} from "../dist/camino/api_pb.js";
|
||||||
import {
|
import { EdgeDependencySchema, InjectedDependencySchema, PackageExportRefSchema } from "../dist/quixos/refs_pb.js";
|
||||||
EdgeDependencySchema,
|
|
||||||
InjectedDependencySchema,
|
|
||||||
PackageExportRefSchema,
|
|
||||||
} from "../dist/quixos/refs_pb.js";
|
|
||||||
import { InvokeCapabilityResponseSchema, OrchestratorRuntime } from "../dist/quixos/orch_pb.js";
|
import { InvokeCapabilityResponseSchema, OrchestratorRuntime } from "../dist/quixos/orch_pb.js";
|
||||||
import { DerivedDependencySchema, PackageRuntime } from "../dist/quixos/runtime_pb.js";
|
import { DerivedDependencySchema, PackageRuntime } from "../dist/quixos/runtime_pb.js";
|
||||||
|
|
||||||
@@ -93,7 +89,10 @@ test("runtime context exposes only explicitly injected ports", async () => {
|
|||||||
resolveEdge: async () => ({ edges: [] }),
|
resolveEdge: async () => ({ edges: [] }),
|
||||||
connectEdge: async () => ({}),
|
connectEdge: async () => ({}),
|
||||||
};
|
};
|
||||||
const context = createRuntimeContext(camino, {}, {
|
const context = createRuntimeContext(
|
||||||
|
camino,
|
||||||
|
{},
|
||||||
|
{
|
||||||
objectId: "obj:task",
|
objectId: "obj:task",
|
||||||
input: {},
|
input: {},
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -112,7 +111,8 @@ test("runtime context exposes only explicitly injected ports", async () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(await context.state("port:title").get(), "obj:task/slot:task:title");
|
assert.equal(await context.state("port:title").get(), "obj:task/slot:task:title");
|
||||||
await context.state("port:title").set("Changed");
|
await context.state("port:title").set("Changed");
|
||||||
@@ -126,23 +126,30 @@ test("runtime context exposes only explicitly injected ports", async () => {
|
|||||||
|
|
||||||
test("injected ports preserve an explicitly traversed object through PackageRuntime RPC", async () => {
|
test("injected ports preserve an explicitly traversed object through PackageRuntime RPC", async () => {
|
||||||
const reads = [];
|
const reads = [];
|
||||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
const caminoServer = await listen((router) =>
|
||||||
|
router.service(CaminoService, {
|
||||||
readState: (request) => {
|
readState: (request) => {
|
||||||
reads.push(request);
|
reads.push(request);
|
||||||
return { value: jsToProtoValue("Project name") };
|
return { value: jsToProtoValue("Project name") };
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
);
|
||||||
|
const runtimeServer = await listen(
|
||||||
|
createPackageRuntimeRoutes({
|
||||||
packageRevisionId: "package:test@1",
|
packageRevisionId: "package:test@1",
|
||||||
caminoUrl: caminoServer.url,
|
caminoUrl: caminoServer.url,
|
||||||
exports: {
|
exports: {
|
||||||
"export:test:value": (context) => context.state("port:value").get(),
|
"export:test:value": (context) => context.state("port:value").get(),
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const client = createClient(PackageRuntime, createConnectTransport({
|
);
|
||||||
|
const client = createClient(
|
||||||
|
PackageRuntime,
|
||||||
|
createConnectTransport({
|
||||||
baseUrl: runtimeServer.url,
|
baseUrl: runtimeServer.url,
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const response = await client.invoke({
|
const response = await client.invoke({
|
||||||
export: create(PackageExportRefSchema, {
|
export: create(PackageExportRefSchema, {
|
||||||
@@ -150,11 +157,13 @@ test("injected ports preserve an explicitly traversed object through PackageRunt
|
|||||||
exportId: "export:test:value",
|
exportId: "export:test:value",
|
||||||
}),
|
}),
|
||||||
objectId: "obj:component",
|
objectId: "obj:component",
|
||||||
dependencies: [create(InjectedDependencySchema, {
|
dependencies: [
|
||||||
|
create(InjectedDependencySchema, {
|
||||||
portId: "port:value",
|
portId: "port:value",
|
||||||
objectId: "obj:project",
|
objectId: "obj:project",
|
||||||
binding: { case: "stateSlotId", value: "slot:project:name" },
|
binding: { case: "stateSlotId", value: "slot:project:name" },
|
||||||
})],
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
assert.equal(response.ok, true);
|
assert.equal(response.ok, true);
|
||||||
assert.equal(protoValueToJs(response.result), "Project name");
|
assert.equal(protoValueToJs(response.result), "Project name");
|
||||||
@@ -178,32 +187,40 @@ test("interface views invoke a related object and propagate transitive live depe
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const orchServer = await listen((router) => router.service(OrchestratorRuntime, {
|
const orchServer = await listen((router) =>
|
||||||
|
router.service(OrchestratorRuntime, {
|
||||||
invokeCapability: (request) => {
|
invokeCapability: (request) => {
|
||||||
invocations.push(request);
|
invocations.push(request);
|
||||||
return create(InvokeCapabilityResponseSchema, {
|
return create(InvokeCapabilityResponseSchema, {
|
||||||
ok: true,
|
ok: true,
|
||||||
result: sourceValue,
|
result: sourceValue,
|
||||||
dependencies: [create(DerivedDependencySchema, {
|
dependencies: [
|
||||||
|
create(DerivedDependencySchema, {
|
||||||
kind: "state",
|
kind: "state",
|
||||||
objectId: "obj:project",
|
objectId: "obj:project",
|
||||||
attachmentId: "slot:project:name",
|
attachmentId: "slot:project:name",
|
||||||
})],
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
);
|
||||||
|
const runtimeServer = await listen(
|
||||||
|
createPackageRuntimeRoutes({
|
||||||
packageRevisionId: "package:test@1",
|
packageRevisionId: "package:test@1",
|
||||||
orchUrl: orchServer.url,
|
orchUrl: orchServer.url,
|
||||||
exports: {
|
exports: {
|
||||||
"export:test:value": derived((context) =>
|
"export:test:value": derived((context) => context.interface("port:named").live("operation:named:name:get")),
|
||||||
context.interface("port:named").live("operation:named:name:get")),
|
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const client = createClient(PackageRuntime, createConnectTransport({
|
);
|
||||||
|
const client = createClient(
|
||||||
|
PackageRuntime,
|
||||||
|
createConnectTransport({
|
||||||
baseUrl: runtimeServer.url,
|
baseUrl: runtimeServer.url,
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const response = await client.invoke({
|
const response = await client.invoke({
|
||||||
export: create(PackageExportRefSchema, {
|
export: create(PackageExportRefSchema, {
|
||||||
@@ -211,11 +228,13 @@ test("interface views invoke a related object and propagate transitive live depe
|
|||||||
exportId: "export:test:value",
|
exportId: "export:test:value",
|
||||||
}),
|
}),
|
||||||
objectId: "obj:component",
|
objectId: "obj:component",
|
||||||
dependencies: [create(InjectedDependencySchema, {
|
dependencies: [
|
||||||
|
create(InjectedDependencySchema, {
|
||||||
portId: "port:named",
|
portId: "port:named",
|
||||||
objectId: "obj:project",
|
objectId: "obj:project",
|
||||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||||
})],
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
assert.equal(response.ok, true);
|
assert.equal(response.ok, true);
|
||||||
assert.equal(invocations[0]?.objectId, "obj:project");
|
assert.equal(invocations[0]?.objectId, "obj:project");
|
||||||
@@ -232,7 +251,8 @@ test("interface views invoke a related object and propagate transitive live depe
|
|||||||
test("derived interface views reread after their transitive subscriptions become live", async () => {
|
test("derived interface views reread after their transitive subscriptions become live", async () => {
|
||||||
let current = "before subscription";
|
let current = "before subscription";
|
||||||
let invocationCount = 0;
|
let invocationCount = 0;
|
||||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
const caminoServer = await listen((router) =>
|
||||||
|
router.service(CaminoService, {
|
||||||
watchObject: async function* (request, context) {
|
watchObject: async function* (request, context) {
|
||||||
// Model a write racing the first nested capability read. Camino makes
|
// Model a write racing the first nested capability read. Camino makes
|
||||||
// the subscription live before yielding this snapshot.
|
// the subscription live before yielding this snapshot.
|
||||||
@@ -245,51 +265,66 @@ test("derived interface views reread after their transitive subscriptions become
|
|||||||
workspaceRevisionId: "workspace:test@1",
|
workspaceRevisionId: "workspace:test@1",
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) => context.signal.addEventListener("abort", resolve, { once: true }));
|
||||||
context.signal.addEventListener("abort", resolve, { once: true }));
|
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const orchServer = await listen((router) => router.service(OrchestratorRuntime, {
|
);
|
||||||
|
const orchServer = await listen((router) =>
|
||||||
|
router.service(OrchestratorRuntime, {
|
||||||
invokeCapability: () => {
|
invokeCapability: () => {
|
||||||
invocationCount += 1;
|
invocationCount += 1;
|
||||||
return create(InvokeCapabilityResponseSchema, {
|
return create(InvokeCapabilityResponseSchema, {
|
||||||
ok: true,
|
ok: true,
|
||||||
result: jsToProtoValue(current),
|
result: jsToProtoValue(current),
|
||||||
dependencies: [create(DerivedDependencySchema, {
|
dependencies: [
|
||||||
|
create(DerivedDependencySchema, {
|
||||||
kind: "state",
|
kind: "state",
|
||||||
objectId: "obj:project",
|
objectId: "obj:project",
|
||||||
attachmentId: "slot:project:name",
|
attachmentId: "slot:project:name",
|
||||||
})],
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
);
|
||||||
|
const runtimeServer = await listen(
|
||||||
|
createPackageRuntimeRoutes({
|
||||||
packageRevisionId: "package:test@1",
|
packageRevisionId: "package:test@1",
|
||||||
caminoUrl: caminoServer.url,
|
caminoUrl: caminoServer.url,
|
||||||
orchUrl: orchServer.url,
|
orchUrl: orchServer.url,
|
||||||
exports: {
|
exports: {
|
||||||
"export:test:value": derived((context) =>
|
"export:test:value": derived((context) => context.interface("port:named").invoke("operation:named:name:get")),
|
||||||
context.interface("port:named").invoke("operation:named:name:get")),
|
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const client = createClient(PackageRuntime, createConnectTransport({
|
);
|
||||||
|
const client = createClient(
|
||||||
|
PackageRuntime,
|
||||||
|
createConnectTransport({
|
||||||
baseUrl: runtimeServer.url,
|
baseUrl: runtimeServer.url,
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
try {
|
try {
|
||||||
const stream = client.watch({
|
const stream = client
|
||||||
|
.watch(
|
||||||
|
{
|
||||||
export: create(PackageExportRefSchema, {
|
export: create(PackageExportRefSchema, {
|
||||||
packageRevisionId: "package:test@1",
|
packageRevisionId: "package:test@1",
|
||||||
exportId: "export:test:value",
|
exportId: "export:test:value",
|
||||||
}),
|
}),
|
||||||
objectId: "obj:component",
|
objectId: "obj:component",
|
||||||
dependencies: [create(InjectedDependencySchema, {
|
dependencies: [
|
||||||
|
create(InjectedDependencySchema, {
|
||||||
portId: "port:named",
|
portId: "port:named",
|
||||||
objectId: "obj:project",
|
objectId: "obj:project",
|
||||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||||
})],
|
}),
|
||||||
}, { signal: controller.signal })[Symbol.asyncIterator]();
|
],
|
||||||
|
},
|
||||||
|
{ signal: controller.signal },
|
||||||
|
)
|
||||||
|
[Symbol.asyncIterator]();
|
||||||
const initial = await stream.next();
|
const initial = await stream.next();
|
||||||
assert.equal(protoValueToJs(initial.value?.value), "after subscription");
|
assert.equal(protoValueToJs(initial.value?.value), "after subscription");
|
||||||
assert.equal(invocationCount, 2);
|
assert.equal(invocationCount, 2);
|
||||||
@@ -305,7 +340,8 @@ test("derived watches subscribe before reading and emit only real changes", asyn
|
|||||||
let current = "before";
|
let current = "before";
|
||||||
let subscribed = false;
|
let subscribed = false;
|
||||||
const changes = [];
|
const changes = [];
|
||||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
const caminoServer = await listen((router) =>
|
||||||
|
router.service(CaminoService, {
|
||||||
readState: () => {
|
readState: () => {
|
||||||
assert.equal(subscribed, true, "the dependency stream must be live before the state read");
|
assert.equal(subscribed, true, "the dependency stream must be live before the state read");
|
||||||
return { value: jsToProtoValue(current) };
|
return { value: jsToProtoValue(current) };
|
||||||
@@ -330,31 +366,44 @@ test("derived watches subscribe before reading and emit only real changes", asyn
|
|||||||
yield { objectId: request.objectId };
|
yield { objectId: request.objectId };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
);
|
||||||
|
const runtimeServer = await listen(
|
||||||
|
createPackageRuntimeRoutes({
|
||||||
packageRevisionId: "package:test@1",
|
packageRevisionId: "package:test@1",
|
||||||
caminoUrl: caminoServer.url,
|
caminoUrl: caminoServer.url,
|
||||||
exports: {
|
exports: {
|
||||||
"export:test:value": derived((context) => context.state("port:value").get()),
|
"export:test:value": derived((context) => context.state("port:value").get()),
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
const client = createClient(PackageRuntime, createConnectTransport({
|
);
|
||||||
|
const client = createClient(
|
||||||
|
PackageRuntime,
|
||||||
|
createConnectTransport({
|
||||||
baseUrl: runtimeServer.url,
|
baseUrl: runtimeServer.url,
|
||||||
httpVersion: "1.1",
|
httpVersion: "1.1",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
try {
|
try {
|
||||||
const stream = client.watch({
|
const stream = client
|
||||||
|
.watch(
|
||||||
|
{
|
||||||
export: create(PackageExportRefSchema, {
|
export: create(PackageExportRefSchema, {
|
||||||
packageRevisionId: "package:test@1",
|
packageRevisionId: "package:test@1",
|
||||||
exportId: "export:test:value",
|
exportId: "export:test:value",
|
||||||
}),
|
}),
|
||||||
objectId: "obj:test",
|
objectId: "obj:test",
|
||||||
dependencies: [create(InjectedDependencySchema, {
|
dependencies: [
|
||||||
|
create(InjectedDependencySchema, {
|
||||||
portId: "port:value",
|
portId: "port:value",
|
||||||
binding: { case: "stateSlotId", value: "slot:test:value" },
|
binding: { case: "stateSlotId", value: "slot:test:value" },
|
||||||
})],
|
}),
|
||||||
}, { signal: controller.signal })[Symbol.asyncIterator]();
|
],
|
||||||
|
},
|
||||||
|
{ signal: controller.signal },
|
||||||
|
)
|
||||||
|
[Symbol.asyncIterator]();
|
||||||
const initial = await stream.next();
|
const initial = await stream.next();
|
||||||
assert.equal(protoValueToJs(initial.value?.value), "before");
|
assert.equal(protoValueToJs(initial.value?.value), "before");
|
||||||
assert.equal(initial.value?.initial, true);
|
assert.equal(initial.value?.initial, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user