Format authored monorepo code with pinned language formatters
This commit is contained in:
+144
-55
@@ -1,7 +1,14 @@
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { ValueSchema, ObjectValueSchema, type Value } from "./camino/api_pb.js";
|
||||
import { derived, jsToProtoValue, liveValue, protoValueToJs,
|
||||
type RuntimeContext, type RuntimeHandler, type DerivedHandler } from "./index.js";
|
||||
import {
|
||||
derived,
|
||||
jsToProtoValue,
|
||||
liveValue,
|
||||
protoValueToJs,
|
||||
type RuntimeContext,
|
||||
type RuntimeHandler,
|
||||
type DerivedHandler,
|
||||
} from "./index.js";
|
||||
|
||||
export type { QxObjectRef } from "./references.js";
|
||||
import { assertReferenceFree, referenceToWire } from "./references.js";
|
||||
@@ -25,8 +32,8 @@ export const opaqueReactPropsBinding: MessageBinding<Record<string, unknown>> =
|
||||
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
|
||||
export type QxHandler<C, O> = (context: C) => O | Promise<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 QxContextLifecycle<C> = {signal?: AbortSignal; openSession?: () => Promise<QxSession<C>>};
|
||||
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 const qxDerived = <C, O>(get: QxHandler<C, O>): QxDerived<C, O> => ({ kind: "derived", get });
|
||||
|
||||
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
|
||||
@@ -44,7 +51,9 @@ export type QxPortSpec =
|
||||
| { kind: "interface"; id: string; operations: Record<string, QxOperationSpec> }
|
||||
| { kind: "constructor"; id: string; inputType: QxValueType };
|
||||
export type QxHandlerSpec = {
|
||||
inputType: QxValueType; outputType: QxValueType; eventType?: QxValueType;
|
||||
inputType: QxValueType;
|
||||
outputType: QxValueType;
|
||||
eventType?: QxValueType;
|
||||
ports: Record<string, QxPortSpec>;
|
||||
};
|
||||
export type QxMessages = Record<string, MessageBinding<any>>;
|
||||
@@ -57,10 +66,14 @@ export const decodeQxValue = (type: QxValueType, value: Value | undefined, messa
|
||||
if (type.kind === "record") {
|
||||
if (value.kind.case !== "objectValue") throw new Error("Expected QX record");
|
||||
const fields = value.kind.value.fields;
|
||||
if (Object.keys(fields).some((name) => !Object.hasOwn(type.fields, name))) throw new Error("Unexpected QX record field");
|
||||
return Object.fromEntries(Object.entries(type.fields).map(([name, field]) => [name, decodeQxValue(field, fields[name], messages)]));
|
||||
if (Object.keys(fields).some((name) => !Object.hasOwn(type.fields, name)))
|
||||
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 (value.kind.case !== "listValue") throw new Error("Expected QX list");
|
||||
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
|
||||
@@ -98,16 +111,25 @@ export const encodeQxValue = (type: QxValueType, value: any, messages: QxMessage
|
||||
if (type.kind === "builtin" && type.name === "unit") return jsToProtoValue(null);
|
||||
if (type.kind === "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");
|
||||
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 create(ValueSchema, {kind: {case: "objectValue", value: create(ObjectValueSchema, {fields})}});
|
||||
if (Object.keys(value).some((name) => !Object.hasOwn(type.fields, name)))
|
||||
throw new Error("Unexpected QX record field");
|
||||
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 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 === "list")
|
||||
return jsToProtoValue(value.map((entry: unknown) => liveValue(encodeQxValue(type.value, entry, messages))));
|
||||
if (type.kind === "object-ref") {
|
||||
referenceToWire(value);
|
||||
return jsToProtoValue(value);
|
||||
}
|
||||
if (type.kind === "optional") return value === null ? jsToProtoValue(null) : encodeQxValue(type.value, value, messages);
|
||||
if (type.kind === "list") return jsToProtoValue(value.map((entry: unknown) => liveValue(encodeQxValue(type.value, entry, messages))));
|
||||
if (type.kind === "object-ref") { referenceToWire(value); return jsToProtoValue(value); }
|
||||
if (type.kind !== "message" || type.descriptorId !== reactPropsDescriptor) assertReferenceFree(value);
|
||||
if (type.kind === "message") {
|
||||
const encoded = requireMessage(messages, type.descriptorId).encode(value);
|
||||
@@ -118,8 +140,10 @@ export const encodeQxValue = (type: QxValueType, value: any, messages: QxMessage
|
||||
};
|
||||
|
||||
const inputValue = (context: RuntimeContext, type: QxValueType) => {
|
||||
if (type.kind === "message" || type.kind === "record") return create(ValueSchema, { kind: { case: "objectValue",
|
||||
value: create(ObjectValueSchema, { fields: context.inputProto }) } });
|
||||
if (type.kind === "message" || type.kind === "record")
|
||||
return create(ValueSchema, {
|
||||
kind: { case: "objectValue", value: create(ObjectValueSchema, { fields: context.inputProto }) },
|
||||
});
|
||||
return context.inputProto.value;
|
||||
};
|
||||
const inputFields = (type: QxValueType, value: unknown, messages: QxMessages): Record<string, unknown> => {
|
||||
@@ -134,45 +158,110 @@ const inputFields = (type: QxValueType, value: unknown, messages: QxMessages): R
|
||||
|
||||
/** The sole unchecked cast connects generated contracts to the dynamic RPC runtime. */
|
||||
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 => {
|
||||
const bindContext = (raw: RuntimeContext): C => {
|
||||
const ports = Object.fromEntries(Object.entries(spec.ports).map(([name, port]) => {
|
||||
switch (port.kind) {
|
||||
case "state": {
|
||||
const state = raw.state(port.id);
|
||||
return [name, {
|
||||
...(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))) } : {}),
|
||||
}];
|
||||
const ports = Object.fromEntries(
|
||||
Object.entries(spec.ports).map(([name, port]) => {
|
||||
switch (port.kind) {
|
||||
case "state": {
|
||||
const state = raw.state(port.id);
|
||||
return [
|
||||
name,
|
||||
{
|
||||
...(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": {
|
||||
const edge = raw.edge(port.id);
|
||||
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") &&
|
||||
port.primitives.includes("connect") &&
|
||||
port.primitives.includes("disconnect")
|
||||
? { replace: edge.replace }
|
||||
: {}),
|
||||
},
|
||||
];
|
||||
}
|
||||
case "interface": {
|
||||
const target = raw.interface(port.id);
|
||||
return [
|
||||
name,
|
||||
{
|
||||
objectId: target.objectId,
|
||||
live: Object.fromEntries(
|
||||
Object.entries(port.operations).map(([name, operation]) => [
|
||||
name,
|
||||
(input: unknown) => target.live(operation.id, inputFields(operation.inputType, input, 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 "edge": {
|
||||
const edge = raw.edge(port.id);
|
||||
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") && port.primitives.includes("connect") && port.primitives.includes("disconnect") ? {replace: edge.replace} : {})}];
|
||||
}
|
||||
case "interface": {
|
||||
const target = raw.interface(port.id);
|
||||
return [name, {objectId: target.objectId,
|
||||
live: Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [name,
|
||||
(input: unknown) => target.live(operation.id, inputFields(operation.inputType, input, 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)) }];
|
||||
}
|
||||
}));
|
||||
return { objectId: raw.objectId, signal: raw.signal,
|
||||
...(raw.openSession ? {openSession: async () => {
|
||||
const session = await raw.openSession!();
|
||||
return {id: session.id, close: () => session.close(),
|
||||
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;
|
||||
}),
|
||||
);
|
||||
return {
|
||||
objectId: raw.objectId,
|
||||
signal: raw.signal,
|
||||
...(raw.openSession
|
||||
? {
|
||||
openSession: async () => {
|
||||
const session = await raw.openSession!();
|
||||
return {
|
||||
id: session.id,
|
||||
close: () => session.close(),
|
||||
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 context = bindContext(raw);
|
||||
|
||||
Reference in New Issue
Block a user