Implement query execution, scoped RPC enrichment, live collections, and scaffold integration

This commit is contained in:
Timothy J. Aveni
2026-09-17 16:49:44 -07:00
parent 7e69e675ba
commit c1330ae8e3
29 changed files with 2217 additions and 77 deletions
+34
View File
@@ -12,6 +12,7 @@ import {
export type { QxObjectRef } from "./references.js";
import { assertReferenceFree, referenceToWire } from "./references.js";
import { decodeQuerySnapshot } from "./queries.js";
declare const watchBrand: unique symbol;
export type QxWatchHandle = string & { readonly [watchBrand]: true };
export type MessageBinding<T> = { encode(value: T): Value; decode(value: Value): T };
@@ -50,6 +51,7 @@ export type QxValueType =
| { kind: "optional" | "list"; value: QxValueType };
export type QxOperationSpec = { id: string; inputType: QxValueType; outputType: QxValueType };
export type QxPortSpec =
| { kind: "query"; id: string; definitionDigest: string; variables: QxValueType; output: QxValueType; watch: boolean }
| { kind: "state"; id: string; valueType: QxValueType; primitives: string[] }
| { kind: "edge"; id: string; primitives: string[] }
| { kind: "interface"; id: string; interfaceRevisionId: string; operations: Record<string, QxOperationSpec> }
@@ -244,6 +246,38 @@ export const bindQxHandler = <C, O>(
bindInterfaceView(target, defineQxInterfaceContract(port.interfaceRevisionId, port.operations), 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);
}
},
}
: {}),
},
];
}
case "constructor":
return [
name,
+413 -2
View File
File diff suppressed because one or more lines are too long
+364 -1
View File
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file camino/schema.proto.
*/
export const file_camino_schema: GenFile = /*@__PURE__*/
fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAki7AEKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudCpoCgtDYXJkaW5hbGl0eRIbChdDQVJESU5BTElUWV9VTlNQRUNJRklFRBAAEhAKDE9QVElPTkFMX09ORRABEg8KC0VYQUNUTFlfT05FEAISCAoETUFOWRADEg8KC01BTllfVU5JUVVFEARiBnByb3RvMw");
fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAkilQIKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudBInCgdxdWVyaWVzGAcgAygLMhYuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5Ip4BCg1RdWVyeUFyZ3VtZW50EhIKCHZhcmlhYmxlGAEgASgJSAASFgoMbGl0ZXJhbF9qc29uGAIgASgJSAASKQoEbGlzdBgDIAEoCzIZLmNhbWluby5RdWVyeUFyZ3VtZW50TGlzdEgAEi0KBm9iamVjdBgEIAEoCzIbLmNhbWluby5RdWVyeUFyZ3VtZW50T2JqZWN0SABCBwoFdmFsdWUiOgoRUXVlcnlBcmd1bWVudExpc3QSJQoGdmFsdWVzGAEgAygLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQilAEKE1F1ZXJ5QXJndW1lbnRPYmplY3QSNwoGZmllbGRzGAEgAygLMicuY2FtaW5vLlF1ZXJ5QXJndW1lbnRPYmplY3QuRmllbGRzRW50cnkaRAoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiQKBXZhbHVlGAIgASgLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQ6AjgBIkcKDlF1ZXJ5Q29uZGl0aW9uEg8KB2luY2x1ZGUYASABKAgSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudCLdAgoOUXVlcnlTZWxlY3Rpb24SDAoEbmFtZRgBIAEoCRILCgNrZXkYAiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAMgASgJEhEKCW1lbWJlcl9pZBgEIAEoCRIkChx0YXJnZXRfaW50ZXJmYWNlX3JldmlzaW9uX2lkGAUgASgJEioKCmNvbmRpdGlvbnMYBiADKAsyFi5jYW1pbm8uUXVlcnlDb25kaXRpb24SOAoJYXJndW1lbnRzGAcgAygLMiUuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uLkFyZ3VtZW50c0VudHJ5EikKCXNlbGVjdGlvbhgIIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhpHCg5Bcmd1bWVudHNFbnRyeRILCgNrZXkYASABKAkSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudDoCOAEi6QIKEFF1ZXJ5UmVhZEJpbmRpbmcSDwoHYXRvbV9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSEQoJbWVtYmVyX2lkGAMgASgJEhsKE2dldHRlcl9vcGVyYXRpb25faWQYBCABKAkSDwoHc2xvdF9pZBgFIAEoCRIUCgxlZGdlX3R5cGVfaWQYBiABKAkSFQoNcHJvamVjdGlvbl9pZBgHIAEoCRILCgNycGMYCCABKAgSIAoYd2F0Y2hfc3RhcnRfb3BlcmF0aW9uX2lkGAkgASgJEh8KF3dhdGNoX3N0b3Bfb3BlcmF0aW9uX2lkGAogASgJEhIKCmZpZWxkX25hbWUYCyABKAkSFwoPdmFsdWVfdHlwZV9qc29uGAwgASgJEigKC2NhcmRpbmFsaXR5GA0gASgOMhMuY2FtaW5vLkNhcmRpbmFsaXR5EhAKCGtleV90eXBlGA4gASgJIpIBCgxRdWVyeUJ1ZGdldHMSDAoEcm93cxgBIAEoDRINCgVkZXB0aBgCIAEoDRIUCgxyZXN1bHRfYnl0ZXMYAyABKA0SEgoKY2FuZGlkYXRlcxgEIAEoDRIRCglycGNfY2FsbHMYBSABKA0SEwoLY29uY3VycmVuY3kYBiABKA0SEwoLZGVhZGxpbmVfbXMYByABKA0ijQQKDkluc3RhbGxlZFF1ZXJ5EgoKAmlkGAEgASgJEhkKEWRlZmluaXRpb25fZGlnZXN0GAIgASgJEhYKDmJpbmRpbmdfZGlnZXN0GAMgASgJEiIKGnJvb3RfaW50ZXJmYWNlX3JldmlzaW9uX2lkGAQgASgJEikKCXNlbGVjdGlvbhgFIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhIqCghiaW5kaW5ncxgGIAMoCzIYLmNhbWluby5RdWVyeVJlYWRCaW5kaW5nEiUKB2J1ZGdldHMYByABKAsyFC5jYW1pbm8uUXVlcnlCdWRnZXRzEhsKE3ZhcmlhYmxlc190eXBlX2pzb24YCCABKAkSGAoQb3V0cHV0X3R5cGVfanNvbhgJIAEoCRJHChF2YXJpYWJsZV9kZWZhdWx0cxgKIAMoCzIsLmNhbWluby5JbnN0YWxsZWRRdWVyeS5WYXJpYWJsZURlZmF1bHRzRW50cnkSDQoFd2F0Y2gYCyABKAgSGwoTcG9sbGluZ19pbnRlcnZhbF9tcxgMIAEoDRIeChZycGNfcHJlZGljYXRlX29yX29yZGVyGA0gASgIGk4KFVZhcmlhYmxlRGVmYXVsdHNFbnRyeRILCgNrZXkYASABKAkSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudDoCOAEqaAoLQ2FyZGluYWxpdHkSGwoXQ0FSRElOQUxJVFlfVU5TUEVDSUZJRUQQABIQCgxPUFRJT05BTF9PTkUQARIPCgtFWEFDVExZX09ORRACEggKBE1BTlkQAxIPCgtNQU5ZX1VOSVFVRRAEYgZwcm90bzM");
/**
* @generated from message camino.AtomDefinition
@@ -273,6 +273,11 @@ export type PersistencePlan = Message<"camino.PersistencePlan"> & {
* @generated from field: repeated camino.EdgeAttachment edges = 6;
*/
edges: EdgeAttachment[];
/**
* @generated from field: repeated camino.InstalledQuery queries = 7;
*/
queries: InstalledQuery[];
};
/**
@@ -282,6 +287,364 @@ export type PersistencePlan = Message<"camino.PersistencePlan"> & {
export const PersistencePlanSchema: GenMessage<PersistencePlan> = /*@__PURE__*/
messageDesc(file_camino_schema, 6);
/**
* Immutable checked query IR. Installed only with the checked persistence plan;
* query callers select its ID, never send or modify these definitions.
*
* @generated from message camino.QueryArgument
*/
export type QueryArgument = Message<"camino.QueryArgument"> & {
/**
* @generated from oneof camino.QueryArgument.value
*/
value: {
/**
* @generated from field: string variable = 1;
*/
value: string;
case: "variable";
} | {
/**
* @generated from field: string literal_json = 2;
*/
value: string;
case: "literalJson";
} | {
/**
* @generated from field: camino.QueryArgumentList list = 3;
*/
value: QueryArgumentList;
case: "list";
} | {
/**
* @generated from field: camino.QueryArgumentObject object = 4;
*/
value: QueryArgumentObject;
case: "object";
} | { case: undefined; value?: undefined };
};
/**
* Describes the message camino.QueryArgument.
* Use `create(QueryArgumentSchema)` to create a new message.
*/
export const QueryArgumentSchema: GenMessage<QueryArgument> = /*@__PURE__*/
messageDesc(file_camino_schema, 7);
/**
* @generated from message camino.QueryArgumentList
*/
export type QueryArgumentList = Message<"camino.QueryArgumentList"> & {
/**
* @generated from field: repeated camino.QueryArgument values = 1;
*/
values: QueryArgument[];
};
/**
* Describes the message camino.QueryArgumentList.
* Use `create(QueryArgumentListSchema)` to create a new message.
*/
export const QueryArgumentListSchema: GenMessage<QueryArgumentList> = /*@__PURE__*/
messageDesc(file_camino_schema, 8);
/**
* @generated from message camino.QueryArgumentObject
*/
export type QueryArgumentObject = Message<"camino.QueryArgumentObject"> & {
/**
* @generated from field: map<string, camino.QueryArgument> fields = 1;
*/
fields: { [key: string]: QueryArgument };
};
/**
* Describes the message camino.QueryArgumentObject.
* Use `create(QueryArgumentObjectSchema)` to create a new message.
*/
export const QueryArgumentObjectSchema: GenMessage<QueryArgumentObject> = /*@__PURE__*/
messageDesc(file_camino_schema, 9);
/**
* @generated from message camino.QueryCondition
*/
export type QueryCondition = Message<"camino.QueryCondition"> & {
/**
* @generated from field: bool include = 1;
*/
include: boolean;
/**
* @generated from field: camino.QueryArgument value = 2;
*/
value?: QueryArgument | undefined;
};
/**
* Describes the message camino.QueryCondition.
* Use `create(QueryConditionSchema)` to create a new message.
*/
export const QueryConditionSchema: GenMessage<QueryCondition> = /*@__PURE__*/
messageDesc(file_camino_schema, 10);
/**
* @generated from message camino.QuerySelection
*/
export type QuerySelection = Message<"camino.QuerySelection"> & {
/**
* @generated from field: string name = 1;
*/
name: string;
/**
* @generated from field: string key = 2;
*/
key: string;
/**
* @generated from field: string interface_revision_id = 3;
*/
interfaceRevisionId: string;
/**
* @generated from field: string member_id = 4;
*/
memberId: string;
/**
* @generated from field: string target_interface_revision_id = 5;
*/
targetInterfaceRevisionId: string;
/**
* @generated from field: repeated camino.QueryCondition conditions = 6;
*/
conditions: QueryCondition[];
/**
* @generated from field: map<string, camino.QueryArgument> arguments = 7;
*/
arguments: { [key: string]: QueryArgument };
/**
* @generated from field: repeated camino.QuerySelection selection = 8;
*/
selection: QuerySelection[];
};
/**
* Describes the message camino.QuerySelection.
* Use `create(QuerySelectionSchema)` to create a new message.
*/
export const QuerySelectionSchema: GenMessage<QuerySelection> = /*@__PURE__*/
messageDesc(file_camino_schema, 11);
/**
* @generated from message camino.QueryReadBinding
*/
export type QueryReadBinding = Message<"camino.QueryReadBinding"> & {
/**
* @generated from field: string atom_id = 1;
*/
atomId: string;
/**
* @generated from field: string interface_revision_id = 2;
*/
interfaceRevisionId: string;
/**
* @generated from field: string member_id = 3;
*/
memberId: string;
/**
* @generated from field: string getter_operation_id = 4;
*/
getterOperationId: string;
/**
* @generated from field: string slot_id = 5;
*/
slotId: string;
/**
* @generated from field: string edge_type_id = 6;
*/
edgeTypeId: string;
/**
* @generated from field: string projection_id = 7;
*/
projectionId: string;
/**
* @generated from field: bool rpc = 8;
*/
rpc: boolean;
/**
* @generated from field: string watch_start_operation_id = 9;
*/
watchStartOperationId: string;
/**
* @generated from field: string watch_stop_operation_id = 10;
*/
watchStopOperationId: string;
/**
* @generated from field: string field_name = 11;
*/
fieldName: string;
/**
* @generated from field: string value_type_json = 12;
*/
valueTypeJson: string;
/**
* @generated from field: camino.Cardinality cardinality = 13;
*/
cardinality: Cardinality;
/**
* @generated from field: string key_type = 14;
*/
keyType: string;
};
/**
* Describes the message camino.QueryReadBinding.
* Use `create(QueryReadBindingSchema)` to create a new message.
*/
export const QueryReadBindingSchema: GenMessage<QueryReadBinding> = /*@__PURE__*/
messageDesc(file_camino_schema, 12);
/**
* @generated from message camino.QueryBudgets
*/
export type QueryBudgets = Message<"camino.QueryBudgets"> & {
/**
* @generated from field: uint32 rows = 1;
*/
rows: number;
/**
* @generated from field: uint32 depth = 2;
*/
depth: number;
/**
* @generated from field: uint32 result_bytes = 3;
*/
resultBytes: number;
/**
* @generated from field: uint32 candidates = 4;
*/
candidates: number;
/**
* @generated from field: uint32 rpc_calls = 5;
*/
rpcCalls: number;
/**
* @generated from field: uint32 concurrency = 6;
*/
concurrency: number;
/**
* @generated from field: uint32 deadline_ms = 7;
*/
deadlineMs: number;
};
/**
* Describes the message camino.QueryBudgets.
* Use `create(QueryBudgetsSchema)` to create a new message.
*/
export const QueryBudgetsSchema: GenMessage<QueryBudgets> = /*@__PURE__*/
messageDesc(file_camino_schema, 13);
/**
* @generated from message camino.InstalledQuery
*/
export type InstalledQuery = Message<"camino.InstalledQuery"> & {
/**
* @generated from field: string id = 1;
*/
id: string;
/**
* @generated from field: string definition_digest = 2;
*/
definitionDigest: string;
/**
* @generated from field: string binding_digest = 3;
*/
bindingDigest: string;
/**
* @generated from field: string root_interface_revision_id = 4;
*/
rootInterfaceRevisionId: string;
/**
* @generated from field: repeated camino.QuerySelection selection = 5;
*/
selection: QuerySelection[];
/**
* @generated from field: repeated camino.QueryReadBinding bindings = 6;
*/
bindings: QueryReadBinding[];
/**
* @generated from field: camino.QueryBudgets budgets = 7;
*/
budgets?: QueryBudgets | undefined;
/**
* @generated from field: string variables_type_json = 8;
*/
variablesTypeJson: string;
/**
* @generated from field: string output_type_json = 9;
*/
outputTypeJson: string;
/**
* @generated from field: map<string, camino.QueryArgument> variable_defaults = 10;
*/
variableDefaults: { [key: string]: QueryArgument };
/**
* @generated from field: bool watch = 11;
*/
watch: boolean;
/**
* @generated from field: uint32 polling_interval_ms = 12;
*/
pollingIntervalMs: number;
/**
* @generated from field: bool rpc_predicate_or_order = 13;
*/
rpcPredicateOrOrder: boolean;
};
/**
* Describes the message camino.InstalledQuery.
* Use `create(InstalledQuerySchema)` to create a new message.
*/
export const InstalledQuerySchema: GenMessage<InstalledQuery> = /*@__PURE__*/
messageDesc(file_camino_schema, 14);
/**
* @generated from enum camino.Cardinality
*/
+26 -1
View File
@@ -9,6 +9,7 @@ import {
type QxObjectRef,
} from "./references.js";
export * from "./bindings.js";
export * from "./queries.js";
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -33,6 +34,7 @@ import {
RefValueSchema,
ValueSchema,
type Value,
type QueryResponse,
} from "./camino/api_pb.js";
import { OrchestratorRuntime } from "./quixos/orch_pb.js";
import {
@@ -197,7 +199,16 @@ export type ConstructorPort = {
atomId: string;
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 = {
tryConform(object: QxObjectRef, interfaceRevisionId: string): Promise<InterfacePort | undefined>;
@@ -212,6 +223,7 @@ export type RuntimeContext = {
edge(portId: string): EdgePort;
interface(portId: string): InterfacePort;
constructor(portId: string): ConstructorPort;
query(portId: string): QueryPort;
};
export type RuntimeSession = {
@@ -273,6 +285,18 @@ export const createRuntimeContext = (
const ports = new Map<string, RuntimePort>();
for (const dependency of request.dependencies) {
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": {
const slotId = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
@@ -421,6 +445,7 @@ export const createRuntimeContext = (
edge: (portId: string) => requirePort<EdgePort>(portId, "edgeTypeId"),
interface: (portId: string) => requirePort<InterfacePort>(portId, "interfaceRevisionId"),
constructor: (portId: string) => requirePort<ConstructorPort>(portId, "atomId"),
query: (portId: string) => requirePort<QueryPort>(portId, "queryId"),
};
};
+82
View File
@@ -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)
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>;
}
+162 -30
View File
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file quixos/refs.proto.
*/
export const file_quixos_refs: GenFile = /*@__PURE__*/
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3MilgEKEkNvbmZvcm1hbmNlV2l0bmVzcxIRCglvYmplY3RfaWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgEIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBSABKAkiQgoQUGFja2FnZUV4cG9ydFJlZhIbChNwYWNrYWdlX3JldmlzaW9uX2lkGAEgASgJEhEKCWV4cG9ydF9pZBgCIAEoCSLEAQoSSW5qZWN0ZWREZXBlbmRlbmN5Eg8KB3BvcnRfaWQYASABKAkSFwoNc3RhdGVfc2xvdF9pZBgCIAEoCUgAEiYKBGVkZ2UYAyABKAsyFi5xdWl4b3MuRWRnZURlcGVuZGVuY3lIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAlIABIdChNjb25zdHJ1Y3Rvcl9hdG9tX2lkGAUgASgJSAASEQoJb2JqZWN0X2lkGAYgASgJQgkKB2JpbmRpbmciPQoORWRnZURlcGVuZGVuY3kSFAoMZWRnZV90eXBlX2lkGAEgASgJEhUKDXByb2plY3Rpb25faWQYAiABKAliBnByb3RvMw");
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3MilgEKEkNvbmZvcm1hbmNlV2l0bmVzcxIRCglvYmplY3RfaWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgEIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBSABKAkiQgoQUGFja2FnZUV4cG9ydFJlZhIbChNwYWNrYWdlX3JldmlzaW9uX2lkGAEgASgJEhEKCWV4cG9ydF9pZBgCIAEoCSLYAQoSSW5qZWN0ZWREZXBlbmRlbmN5Eg8KB3BvcnRfaWQYASABKAkSFwoNc3RhdGVfc2xvdF9pZBgCIAEoCUgAEiYKBGVkZ2UYAyABKAsyFi5xdWl4b3MuRWRnZURlcGVuZGVuY3lIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAlIABIdChNjb25zdHJ1Y3Rvcl9hdG9tX2lkGAUgASgJSAASEgoIcXVlcnlfaWQYByABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
/**
* @generated from message quixos.CapabilityRef
@@ -136,6 +136,12 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
*/
value: string;
case: "constructorAtomId";
} | {
/**
* @generated from field: string query_id = 7;
*/
value: string;
case: "queryId";
} | { case: undefined; value?: undefined };
/**