Format authored monorepo code with pinned language formatters
This commit is contained in:
+435
-271
@@ -1,12 +1,26 @@
|
||||
import http from "node:http";
|
||||
import { readFileSync } from "node:fs";
|
||||
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 {relationshipMap, relationshipList, relationshipSet} from "./relationships.js";
|
||||
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
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 { Code, ConnectError, createClient, type Client, type ConnectRouter } from "@connectrpc/connect";
|
||||
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
|
||||
@@ -63,14 +77,22 @@ const base64ToBytes = (value: string) => Buffer.from(value, "base64");
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
const isWrappedValue = (value: unknown): value is { $quixosValue: Value } =>
|
||||
isRecord(value) && "$quixosValue" in value &&
|
||||
isRecord(value.$quixosValue) && value.$quixosValue.$typeName === "camino.Value";
|
||||
isRecord(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 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 (value === null || value === undefined) {
|
||||
return create(ValueSchema, { kind: { case: "nullValue", value: create(NullValueSchema, {}) } });
|
||||
@@ -86,43 +108,55 @@ 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) && typeof value.$quixosCrdtType === "string" &&
|
||||
typeof value.$quixosCrdtPayload === "string") {
|
||||
if (isRecord(value) && typeof value.$quixosCrdtType === "string" && typeof value.$quixosCrdtPayload === "string") {
|
||||
return create(ValueSchema, {
|
||||
kind: { case: "crdtValue", value: create(CrdtValueSchema, {
|
||||
type: value.$quixosCrdtType,
|
||||
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
|
||||
payload: base64ToBytes(value.$quixosCrdtPayload),
|
||||
}) },
|
||||
kind: {
|
||||
case: "crdtValue",
|
||||
value: create(CrdtValueSchema, {
|
||||
type: value.$quixosCrdtType,
|
||||
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
|
||||
payload: base64ToBytes(value.$quixosCrdtPayload),
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
if (!isRecord(value)) throw new Error(`Unsupported runtime value ${typeof value}`);
|
||||
return create(ValueSchema, {
|
||||
kind: { case: "objectValue", value: create(ObjectValueSchema, {
|
||||
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
|
||||
}) },
|
||||
kind: {
|
||||
case: "objectValue",
|
||||
value: create(ObjectValueSchema, {
|
||||
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const protoValueToJs = (value: Value | undefined): unknown => {
|
||||
switch (value?.kind.case) {
|
||||
case "nullValue":
|
||||
case undefined: return null;
|
||||
case undefined:
|
||||
return null;
|
||||
case "boolValue":
|
||||
case "numberValue":
|
||||
case "stringValue":
|
||||
case "integerValue": return value.kind.value;
|
||||
case "bytesValue": return bytesToBase64(value.kind.value);
|
||||
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)]),
|
||||
);
|
||||
case "crdtValue": return {
|
||||
$quixosCrdtType: value.kind.value.type,
|
||||
$quixosCrdtEncoding: value.kind.value.encoding,
|
||||
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
|
||||
};
|
||||
case "integerValue":
|
||||
return value.kind.value;
|
||||
case "bytesValue":
|
||||
return bytesToBase64(value.kind.value);
|
||||
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)]),
|
||||
);
|
||||
case "crdtValue":
|
||||
return {
|
||||
$quixosCrdtType: value.kind.value.type,
|
||||
$quixosCrdtEncoding: value.kind.value.encoding,
|
||||
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,8 +178,15 @@ export type EdgePort = {
|
||||
collection(): 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 RelationshipCollection<T extends QxObjectRef = QxObjectRef> = {revision: bigint; entries: RelationshipEntry<T>[]};
|
||||
export type RelationshipEntry<T extends QxObjectRef = QxObjectRef> = {
|
||||
edgeId?: string;
|
||||
target: T;
|
||||
key?: string | boolean | bigint;
|
||||
};
|
||||
export type RelationshipCollection<T extends QxObjectRef = QxObjectRef> = {
|
||||
revision: bigint;
|
||||
entries: RelationshipEntry<T>[];
|
||||
};
|
||||
export type InterfacePort = {
|
||||
objectId: QxObjectRef;
|
||||
interfaceRevisionId: string;
|
||||
@@ -180,13 +221,17 @@ export type RuntimeSession = {
|
||||
};
|
||||
export class RuntimeAuthorityError extends Error {
|
||||
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 = (
|
||||
edge: { firstObjectId: string; secondObjectId: string; firstProjectionId: string },
|
||||
projectionId: string,
|
||||
) => edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId;
|
||||
) => (edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId);
|
||||
|
||||
export const createRuntimeContext = (
|
||||
camino: CaminoClient,
|
||||
@@ -222,25 +267,63 @@ export const createRuntimeContext = (
|
||||
case "edge": {
|
||||
const { edgeTypeId, projectionId } = dependency.binding.value;
|
||||
const dependencyObjectId = dependency.objectId || request.objectId;
|
||||
const collectionResult = (response: {revision: bigint; 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 collectionResult = (response: {
|
||||
revision: bigint;
|
||||
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 = {
|
||||
edgeTypeId,
|
||||
projectionId,
|
||||
async collection() {
|
||||
await recordDependency({kind: "edge", objectId: dependencyObjectId, attachmentId: edgeTypeId, projectionId});
|
||||
return collectionResult(await camino.readCollection({objectId: dependencyObjectId, edgeTypeId, projectionId}));
|
||||
await recordDependency({
|
||||
kind: "edge",
|
||||
objectId: dependencyObjectId,
|
||||
attachmentId: edgeTypeId,
|
||||
projectionId,
|
||||
});
|
||||
return collectionResult(
|
||||
await camino.readCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId }),
|
||||
);
|
||||
},
|
||||
async replace(entries, expectedRevision) {
|
||||
return collectionResult(await camino.replaceCollection({objectId: dependencyObjectId, edgeTypeId, projectionId, expectedRevision,
|
||||
entries: entries.map((entry) => {
|
||||
assertReferenceFree(entry.key);
|
||||
return {edgeId: entry.edgeId ?? "", targetObjectId: referenceToWire(entry.target), key: entry.key === undefined ? undefined : jsToProtoValue(entry.key)};
|
||||
})}));
|
||||
return collectionResult(
|
||||
await camino.replaceCollection({
|
||||
objectId: dependencyObjectId,
|
||||
edgeTypeId,
|
||||
projectionId,
|
||||
expectedRevision,
|
||||
entries: entries.map((entry) => {
|
||||
assertReferenceFree(entry.key);
|
||||
return {
|
||||
edgeId: entry.edgeId ?? "",
|
||||
targetObjectId: referenceToWire(entry.target),
|
||||
key: entry.key === undefined ? undefined : jsToProtoValue(entry.key),
|
||||
};
|
||||
}),
|
||||
}),
|
||||
);
|
||||
},
|
||||
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 });
|
||||
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
|
||||
},
|
||||
@@ -252,7 +335,8 @@ export const createRuntimeContext = (
|
||||
const targetObjectId = referenceToWire(target);
|
||||
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
|
||||
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 });
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -348,20 +432,21 @@ const evaluate = async (
|
||||
observe?: (dependency: RuntimeDependency) => Promise<void>,
|
||||
) => {
|
||||
const dependencies = new Map<string, RuntimeDependency>();
|
||||
const result = await dependencyScope.run(
|
||||
{ dependencies, observe },
|
||||
() => isDerived(handler) ? handler.get(context) : handler(context),
|
||||
const result = await dependencyScope.run({ dependencies, observe }, () =>
|
||||
isDerived(handler) ? handler.get(context) : handler(context),
|
||||
);
|
||||
return { value: jsToProtoValue(result), dependencies: [...dependencies.values()] };
|
||||
};
|
||||
|
||||
const protoDependencies = (dependencies: RuntimeDependency[]) => dependencies.map((entry) =>
|
||||
create(DerivedDependencySchema, {
|
||||
kind: entry.kind,
|
||||
objectId: entry.objectId,
|
||||
attachmentId: entry.attachmentId,
|
||||
projectionId: entry.kind === "edge" ? entry.projectionId : "",
|
||||
}));
|
||||
const protoDependencies = (dependencies: RuntimeDependency[]) =>
|
||||
dependencies.map((entry) =>
|
||||
create(DerivedDependencySchema, {
|
||||
kind: entry.kind,
|
||||
objectId: entry.objectId,
|
||||
attachmentId: entry.attachmentId,
|
||||
projectionId: entry.kind === "edge" ? entry.projectionId : "",
|
||||
}),
|
||||
);
|
||||
|
||||
export const createPackageRuntimeRoutes = (config: {
|
||||
packageRevisionId: string;
|
||||
@@ -371,27 +456,38 @@ export const createPackageRuntimeRoutes = (config: {
|
||||
}) => {
|
||||
const invocations = createInvocationRegistry();
|
||||
const headers: Record<string, string> = {};
|
||||
const processToken = process.env.CAMINO_RUNTIME_AUTH_TOKEN ?? (process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
|
||||
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim() : "");
|
||||
const processToken =
|
||||
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) {
|
||||
headers["x-camino-runtime-token"] = processToken;
|
||||
} else if (process.env.CAMINO_RUNTIME_AUTH_REQUIRED === "1") {
|
||||
throw new Error("CAMINO_RUNTIME_AUTH_TOKEN is required");
|
||||
}
|
||||
const camino = createClient(CaminoService, createConnectTransport({
|
||||
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
|
||||
httpVersion: "1.1",
|
||||
interceptors: headers["x-camino-runtime-token"] ? [
|
||||
(next) => async (request) => {
|
||||
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]!);
|
||||
return await next(request);
|
||||
},
|
||||
] : [],
|
||||
}));
|
||||
const orch = createClient(OrchestratorRuntime, createConnectTransport({
|
||||
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
const camino = createClient(
|
||||
CaminoService,
|
||||
createConnectTransport({
|
||||
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
|
||||
httpVersion: "1.1",
|
||||
interceptors: headers["x-camino-runtime-token"]
|
||||
? [
|
||||
(next) => async (request) => {
|
||||
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]!);
|
||||
return await next(request);
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}),
|
||||
);
|
||||
const orch = createClient(
|
||||
OrchestratorRuntime,
|
||||
createConnectTransport({
|
||||
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
|
||||
httpVersion: "1.1",
|
||||
}),
|
||||
);
|
||||
|
||||
const authenticateInstance = (header: Headers) => {
|
||||
if (!process.env.QUIXOS_RUNTIME_INSTANCE_ID) return; // standalone development ABI
|
||||
@@ -403,35 +499,67 @@ export const createPackageRuntimeRoutes = (config: {
|
||||
};
|
||||
const clientsFor = (request: { context?: { grant: string; instanceId: string; workspaceEpoch: string } }) => {
|
||||
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);
|
||||
}
|
||||
if (!context?.grant) return { camino, orch };
|
||||
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-camino-runtime-token", processToken);
|
||||
return next(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-camino-runtime-token", processToken);
|
||||
return next(call);
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
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")),
|
||||
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"),
|
||||
),
|
||||
};
|
||||
};
|
||||
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}`, {
|
||||
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};
|
||||
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),
|
||||
},
|
||||
);
|
||||
const value = (await response.json()) as T & { error?: string };
|
||||
if (!response.ok) throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
|
||||
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;
|
||||
runtimeContext.openSession = async () => {
|
||||
const ownerId = request.context!.ownerConformanceId!;
|
||||
const registration = { 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 registration = {
|
||||
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 registered = await register().catch((error) => {
|
||||
// Retry a transport/lost-response failure with exactly the same identity.
|
||||
// Admission/authority errors are definitive and must not be retried here.
|
||||
@@ -445,210 +573,246 @@ export const createPackageRuntimeRoutes = (config: {
|
||||
if (closed) throw new RuntimeAuthorityError("SESSION_CLOSED");
|
||||
// Acquisition happens before user code. A fence failure can be retried
|
||||
// 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 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 context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
|
||||
context.signal = execution.signal;
|
||||
try { return await work(context); }
|
||||
finally {
|
||||
try {
|
||||
return await work(context);
|
||||
} finally {
|
||||
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, {
|
||||
handshake: (request) => create(HandshakeResponseSchema, {
|
||||
packageRevisionId: config.packageRevisionId,
|
||||
runtimeProtocolVersion: "quixos-capabilities-v1",
|
||||
exportIds: Object.keys(config.exports),
|
||||
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
|
||||
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||
authenticationProof: request.nonce && processToken ? createHmac("sha256", processToken)
|
||||
.update(JSON.stringify([request.nonce, process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "", config.packageRevisionId]))
|
||||
.digest("hex") : "",
|
||||
}),
|
||||
getInvocationStatus: (request, context) => {
|
||||
authenticateInstance(context.requestHeader);
|
||||
return invocations.status(request.invocationId);
|
||||
},
|
||||
cancelInvocation: (request, context) => {
|
||||
authenticateInstance(context.requestHeader);
|
||||
return invocations.cancel(request.invocationId);
|
||||
},
|
||||
invoke: async (request, context) => {
|
||||
authenticateInstance(context.requestHeader);
|
||||
const { camino, orch } = clientsFor(request);
|
||||
const exportId = request.export?.exportId;
|
||||
const handler = exportId ? config.exports[exportId] : undefined;
|
||||
if (!handler) throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
|
||||
const execution = invocations.begin(request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()));
|
||||
try {
|
||||
const runtimeContext = createRuntimeContext(camino, orch, request);
|
||||
attachSessions(runtimeContext, request);
|
||||
runtimeContext.signal = AbortSignal.any([context.signal, execution.signal]);
|
||||
const result = await evaluate(handler, runtimeContext);
|
||||
execution.finish();
|
||||
return create(InvokeResponseSchema, {
|
||||
ok: true,
|
||||
result: result.value,
|
||||
dependencies: protoDependencies(result.dependencies),
|
||||
});
|
||||
} catch (error) {
|
||||
execution.finish(true);
|
||||
return create(InvokeResponseSchema, {
|
||||
ok: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: async function* (request, context) {
|
||||
authenticateInstance(context.requestHeader);
|
||||
const { camino, orch } = clientsFor(request);
|
||||
const exportId = request.export?.exportId;
|
||||
const handler = exportId ? config.exports[exportId] : undefined;
|
||||
if (!handler || !isDerived(handler)) {
|
||||
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
|
||||
}
|
||||
const execution = invocations.begin(request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()));
|
||||
const signal = AbortSignal.any([context.signal, execution.signal]);
|
||||
try {
|
||||
const watchId = `watch:${randomUUID()}`;
|
||||
const runtimeContext = createRuntimeContext(camino, orch, request);
|
||||
attachSessions(runtimeContext, request);
|
||||
runtimeContext.signal = signal;
|
||||
type WatchOutcome = { key: string; done: boolean; error?: unknown };
|
||||
type Subscription = {
|
||||
dependency: RuntimeDependency;
|
||||
controller: AbortController;
|
||||
next: Promise<WatchOutcome>;
|
||||
waitNext: () => Promise<WatchOutcome>;
|
||||
};
|
||||
const subscriptions = new Map<string, Subscription>();
|
||||
const establishing = new Map<string, Promise<void>>();
|
||||
let subscriptionEpoch = 0;
|
||||
|
||||
const ensureSubscription = async (dependency: RuntimeDependency) => {
|
||||
const key = dependencyKey(dependency);
|
||||
if (subscriptions.has(key)) return;
|
||||
const pending = establishing.get(key);
|
||||
if (pending) return await pending;
|
||||
const establish = (async () => {
|
||||
const controller = new AbortController();
|
||||
const stream = camino.watchObject(
|
||||
{ objectId: dependency.objectId, includeSnapshot: true, attachmentIds: request.context?.grant ? [dependency.attachmentId] : [] },
|
||||
{ signal: controller.signal },
|
||||
)[Symbol.asyncIterator]();
|
||||
try {
|
||||
// Camino subscribes before producing the snapshot, so once this
|
||||
// resolves the following state/edge read cannot race the stream.
|
||||
const snapshot = await stream.next();
|
||||
if (snapshot.done) throw new Error(`Dependency stream ${key} ended during setup`);
|
||||
const waitNext = () => stream.next().then(
|
||||
(result) => ({ key, done: Boolean(result.done) }),
|
||||
(error: unknown) => ({ key, done: true, error }),
|
||||
);
|
||||
const subscription: Subscription = {
|
||||
dependency,
|
||||
controller,
|
||||
waitNext,
|
||||
next: Promise.resolve({ key, done: false }),
|
||||
};
|
||||
subscription.next = waitNext();
|
||||
subscriptions.set(key, subscription);
|
||||
subscriptionEpoch += 1;
|
||||
} catch (error) {
|
||||
controller.abort();
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
establishing.set(key, establish);
|
||||
return (router: ConnectRouter) =>
|
||||
router.service(PackageRuntime, {
|
||||
handshake: (request) =>
|
||||
create(HandshakeResponseSchema, {
|
||||
packageRevisionId: config.packageRevisionId,
|
||||
runtimeProtocolVersion: "quixos-capabilities-v1",
|
||||
exportIds: Object.keys(config.exports),
|
||||
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
|
||||
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||
authenticationProof:
|
||||
request.nonce && processToken
|
||||
? createHmac("sha256", processToken)
|
||||
.update(
|
||||
JSON.stringify([
|
||||
request.nonce,
|
||||
process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
|
||||
config.packageRevisionId,
|
||||
]),
|
||||
)
|
||||
.digest("hex")
|
||||
: "",
|
||||
}),
|
||||
getInvocationStatus: (request, context) => {
|
||||
authenticateInstance(context.requestHeader);
|
||||
return invocations.status(request.invocationId);
|
||||
},
|
||||
cancelInvocation: (request, context) => {
|
||||
authenticateInstance(context.requestHeader);
|
||||
return invocations.cancel(request.invocationId);
|
||||
},
|
||||
invoke: async (request, context) => {
|
||||
authenticateInstance(context.requestHeader);
|
||||
const { camino, orch } = clientsFor(request);
|
||||
const exportId = request.export?.exportId;
|
||||
const handler = exportId ? config.exports[exportId] : undefined;
|
||||
if (!handler) throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
|
||||
const execution = invocations.begin(
|
||||
request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()),
|
||||
);
|
||||
try {
|
||||
await establish;
|
||||
} finally {
|
||||
establishing.delete(key);
|
||||
const runtimeContext = createRuntimeContext(camino, orch, request);
|
||||
attachSessions(runtimeContext, request);
|
||||
runtimeContext.signal = AbortSignal.any([context.signal, execution.signal]);
|
||||
const result = await evaluate(handler, runtimeContext);
|
||||
execution.finish();
|
||||
return create(InvokeResponseSchema, {
|
||||
ok: true,
|
||||
result: result.value,
|
||||
dependencies: protoDependencies(result.dependencies),
|
||||
});
|
||||
} catch (error) {
|
||||
execution.finish(true);
|
||||
return create(InvokeResponseSchema, {
|
||||
ok: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const abortAll = () => {
|
||||
for (const subscription of subscriptions.values()) {
|
||||
subscription.controller.abort();
|
||||
},
|
||||
watch: async function* (request, context) {
|
||||
authenticateInstance(context.requestHeader);
|
||||
const { camino, orch } = clientsFor(request);
|
||||
const exportId = request.export?.exportId;
|
||||
const handler = exportId ? config.exports[exportId] : undefined;
|
||||
if (!handler || !isDerived(handler)) {
|
||||
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
|
||||
}
|
||||
};
|
||||
signal.addEventListener("abort", abortAll, { once: true });
|
||||
const execution = invocations.begin(
|
||||
request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()),
|
||||
);
|
||||
const signal = AbortSignal.any([context.signal, execution.signal]);
|
||||
try {
|
||||
const watchId = `watch:${randomUUID()}`;
|
||||
const runtimeContext = createRuntimeContext(camino, orch, request);
|
||||
attachSessions(runtimeContext, request);
|
||||
runtimeContext.signal = signal;
|
||||
type WatchOutcome = { key: string; done: boolean; error?: unknown };
|
||||
type Subscription = {
|
||||
dependency: RuntimeDependency;
|
||||
controller: AbortController;
|
||||
next: Promise<WatchOutcome>;
|
||||
waitNext: () => Promise<WatchOutcome>;
|
||||
};
|
||||
const subscriptions = new Map<string, Subscription>();
|
||||
const establishing = new Map<string, Promise<void>>();
|
||||
let subscriptionEpoch = 0;
|
||||
|
||||
const evaluateWithStableSubscriptions = async () => {
|
||||
// A direct state/edge port records its dependency before reading it,
|
||||
// but a nested interface invocation can only report its transitive
|
||||
// dependencies after that invocation returns. Once a new dependency
|
||||
// stream is established, evaluate again so every read contributing to
|
||||
// the emitted value happened after its stream became live.
|
||||
for (let pass = 0; pass < 32; pass += 1) {
|
||||
const before = subscriptionEpoch;
|
||||
const result = await evaluate(handler, runtimeContext, ensureSubscription);
|
||||
if (subscriptionEpoch === before) return result;
|
||||
}
|
||||
throw new Error("Derived dependency discovery did not stabilize after 32 passes");
|
||||
};
|
||||
|
||||
try {
|
||||
let current = await evaluateWithStableSubscriptions();
|
||||
yield create(WatchEventSchema, {
|
||||
watchId,
|
||||
value: current.value,
|
||||
dependencies: protoDependencies(current.dependencies),
|
||||
initial: true,
|
||||
});
|
||||
|
||||
const abort = new Promise<"abort">((resolve) => {
|
||||
if (signal.aborted) resolve("abort");
|
||||
else signal.addEventListener("abort", () => resolve("abort"), { once: true });
|
||||
});
|
||||
while (!signal.aborted) {
|
||||
if (subscriptions.size === 0) {
|
||||
await abort;
|
||||
break;
|
||||
}
|
||||
const outcome = await Promise.race([
|
||||
...[...subscriptions.values()].map((entry) => entry.next),
|
||||
abort,
|
||||
]);
|
||||
if (outcome === "abort") break;
|
||||
const subscription = subscriptions.get(outcome.key);
|
||||
if (!subscription) continue;
|
||||
if (outcome.error) throw outcome.error;
|
||||
if (outcome.done) throw new Error(`Dependency stream ${outcome.key} ended unexpectedly`);
|
||||
subscription.next = subscription.waitNext();
|
||||
|
||||
const updated = await evaluateWithStableSubscriptions();
|
||||
const active = new Set(updated.dependencies.map(dependencyKey));
|
||||
for (const [key, entry] of subscriptions) {
|
||||
if (!active.has(key)) {
|
||||
entry.controller.abort();
|
||||
subscriptions.delete(key);
|
||||
const ensureSubscription = async (dependency: RuntimeDependency) => {
|
||||
const key = dependencyKey(dependency);
|
||||
if (subscriptions.has(key)) return;
|
||||
const pending = establishing.get(key);
|
||||
if (pending) return await pending;
|
||||
const establish = (async () => {
|
||||
const controller = new AbortController();
|
||||
const stream = camino
|
||||
.watchObject(
|
||||
{
|
||||
objectId: dependency.objectId,
|
||||
includeSnapshot: true,
|
||||
attachmentIds: request.context?.grant ? [dependency.attachmentId] : [],
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
)
|
||||
[Symbol.asyncIterator]();
|
||||
try {
|
||||
// Camino subscribes before producing the snapshot, so once this
|
||||
// resolves the following state/edge read cannot race the stream.
|
||||
const snapshot = await stream.next();
|
||||
if (snapshot.done) throw new Error(`Dependency stream ${key} ended during setup`);
|
||||
const waitNext = () =>
|
||||
stream.next().then(
|
||||
(result) => ({ key, done: Boolean(result.done) }),
|
||||
(error: unknown) => ({ key, done: true, error }),
|
||||
);
|
||||
const subscription: Subscription = {
|
||||
dependency,
|
||||
controller,
|
||||
waitNext,
|
||||
next: Promise.resolve({ key, done: false }),
|
||||
};
|
||||
subscription.next = waitNext();
|
||||
subscriptions.set(key, subscription);
|
||||
subscriptionEpoch += 1;
|
||||
} catch (error) {
|
||||
controller.abort();
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
establishing.set(key, establish);
|
||||
try {
|
||||
await establish;
|
||||
} finally {
|
||||
establishing.delete(key);
|
||||
}
|
||||
}
|
||||
if (!equals(ValueSchema, current.value, updated.value)) {
|
||||
};
|
||||
|
||||
const abortAll = () => {
|
||||
for (const subscription of subscriptions.values()) {
|
||||
subscription.controller.abort();
|
||||
}
|
||||
};
|
||||
signal.addEventListener("abort", abortAll, { once: true });
|
||||
|
||||
const evaluateWithStableSubscriptions = async () => {
|
||||
// A direct state/edge port records its dependency before reading it,
|
||||
// but a nested interface invocation can only report its transitive
|
||||
// dependencies after that invocation returns. Once a new dependency
|
||||
// stream is established, evaluate again so every read contributing to
|
||||
// the emitted value happened after its stream became live.
|
||||
for (let pass = 0; pass < 32; pass += 1) {
|
||||
const before = subscriptionEpoch;
|
||||
const result = await evaluate(handler, runtimeContext, ensureSubscription);
|
||||
if (subscriptionEpoch === before) return result;
|
||||
}
|
||||
throw new Error("Derived dependency discovery did not stabilize after 32 passes");
|
||||
};
|
||||
|
||||
try {
|
||||
let current = await evaluateWithStableSubscriptions();
|
||||
yield create(WatchEventSchema, {
|
||||
watchId,
|
||||
value: updated.value,
|
||||
dependencies: protoDependencies(updated.dependencies),
|
||||
value: current.value,
|
||||
dependencies: protoDependencies(current.dependencies),
|
||||
initial: true,
|
||||
});
|
||||
|
||||
const abort = new Promise<"abort">((resolve) => {
|
||||
if (signal.aborted) resolve("abort");
|
||||
else signal.addEventListener("abort", () => resolve("abort"), { once: true });
|
||||
});
|
||||
while (!signal.aborted) {
|
||||
if (subscriptions.size === 0) {
|
||||
await abort;
|
||||
break;
|
||||
}
|
||||
const outcome = await Promise.race([...[...subscriptions.values()].map((entry) => entry.next), abort]);
|
||||
if (outcome === "abort") break;
|
||||
const subscription = subscriptions.get(outcome.key);
|
||||
if (!subscription) continue;
|
||||
if (outcome.error) throw outcome.error;
|
||||
if (outcome.done) throw new Error(`Dependency stream ${outcome.key} ended unexpectedly`);
|
||||
subscription.next = subscription.waitNext();
|
||||
|
||||
const updated = await evaluateWithStableSubscriptions();
|
||||
const active = new Set(updated.dependencies.map(dependencyKey));
|
||||
for (const [key, entry] of subscriptions) {
|
||||
if (!active.has(key)) {
|
||||
entry.controller.abort();
|
||||
subscriptions.delete(key);
|
||||
}
|
||||
}
|
||||
if (!equals(ValueSchema, current.value, updated.value)) {
|
||||
yield create(WatchEventSchema, {
|
||||
watchId,
|
||||
value: updated.value,
|
||||
dependencies: protoDependencies(updated.dependencies),
|
||||
});
|
||||
}
|
||||
current = updated;
|
||||
}
|
||||
} finally {
|
||||
signal.removeEventListener("abort", abortAll);
|
||||
abortAll();
|
||||
}
|
||||
current = updated;
|
||||
} finally {
|
||||
execution.finish();
|
||||
}
|
||||
} finally {
|
||||
signal.removeEventListener("abort", abortAll);
|
||||
abortAll();
|
||||
}
|
||||
} finally { execution.finish(); }
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const servePackageRuntime = (config: {
|
||||
|
||||
Reference in New Issue
Block a user