Compare commits

...

2 Commits

Author SHA1 Message Date
Quixos Subtree Publisher 142bb43c5b Publish camino-package-runtime from 4a2b76ed15a52d06b422fb3d1f651be47d92ff51 2026-07-12 20:48:49 +00:00
Timothy J. Aveni d50f2bdfe0 Add derived watch lifecycle helper 2026-07-12 13:48:49 -07:00
2 changed files with 190 additions and 3 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git",
"sourceCommit": "ddccb886544038ee85e4f1fd4db1c35b24869199",
"sourceCommit": "4a2b76ed15a52d06b422fb3d1f651be47d92ff51",
"sourcePath": "quixos-instance/packages/camino-package-runtime",
"exportName": "camino-package-runtime",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
+189 -2
View File
@@ -1,5 +1,6 @@
import http from "node:http";
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import { create } from "@bufbuild/protobuf";
import { createClient, type Client } from "@connectrpc/connect";
import {
@@ -107,6 +108,48 @@ const logDerivedDependencies = (dependencies: DerivedDependency[]) => {
);
};
const logDerivedWatch = (event: string, detail: Record<string, unknown>) => {
if (process.env.QUIXOS_DERIVED_WATCH_LOG !== "1") {
return;
}
process.stderr.write(
`[derived-watch] ${JSON.stringify({ event, ...detail })}\n`,
);
};
type WatchableContext = {
camino: CaminoClient;
request: InvokeRequest;
};
type ActiveDerivedWatch<Context extends WatchableContext> = {
watchId: string;
context: Context;
get: RuntimeHandler<Context>;
controllers: Map<string, AbortController>;
running: boolean;
pending: boolean;
stopped: boolean;
lastValue: unknown;
};
const dependencyObjectIds = (dependencies: DerivedDependency[]) =>
new Set(dependencies.map((dependency) => dependency.objectId));
const stopDerivedWatch = <Context extends WatchableContext>(
watch: ActiveDerivedWatch<Context>,
) => {
if (watch.stopped) {
return;
}
watch.stopped = true;
for (const controller of watch.controllers.values()) {
controller.abort();
}
watch.controllers.clear();
logDerivedWatch("stopped", { watchId: watch.watchId });
};
export type Field<T> = {
get(): Promise<T | undefined>;
set(value: T): Promise<void>;
@@ -129,8 +172,116 @@ export const fieldOps = <Context>(
export const derivedWithDeps = <Context>(operation: {
get: RuntimeHandler<Context>;
}): FieldOperation<Context> =>
fieldOps({
}): FieldOperation<Context> => {
type DerivedContext = Context & WatchableContext;
const watches = new Map<string, ActiveDerivedWatch<DerivedContext>>();
const rerun = async (watch: ActiveDerivedWatch<DerivedContext>) => {
if (watch.stopped) {
return;
}
if (watch.running) {
watch.pending = true;
return;
}
watch.running = true;
try {
do {
watch.pending = false;
const { value, dependencies } = await runWithDerivedDependencyTracking(
() => watch.get(watch.context),
);
watch.lastValue = value;
logDerivedDependencies(dependencies);
logDerivedWatch("evaluated", {
watchId: watch.watchId,
value,
dependencies,
});
reconcileSubscriptions(watch, dependencies);
} while (watch.pending && !watch.stopped);
} catch (error) {
logDerivedWatch("error", {
watchId: watch.watchId,
error: error instanceof Error ? error.message : String(error),
});
} finally {
watch.running = false;
}
};
const scheduleRerun = (
watch: ActiveDerivedWatch<DerivedContext>,
reason: string,
) => {
if (watch.stopped) {
return;
}
logDerivedWatch("scheduled", { watchId: watch.watchId, reason });
void rerun(watch);
};
const startObjectWatch = (
watch: ActiveDerivedWatch<DerivedContext>,
objectId: string,
) => {
const controller = new AbortController();
watch.controllers.set(objectId, controller);
logDerivedWatch("subscribed", { watchId: watch.watchId, objectId });
void (async () => {
try {
for await (const event of watch.context.camino.watchObject(
{
objectId,
includeSnapshot: false,
},
{ signal: controller.signal },
)) {
if (controller.signal.aborted || watch.stopped) {
break;
}
scheduleRerun(
watch,
event.op?.id ? `op:${event.op.id}` : `object:${objectId}`,
);
}
} catch (error) {
if (!controller.signal.aborted && !watch.stopped) {
logDerivedWatch("watch-error", {
watchId: watch.watchId,
objectId,
error: error instanceof Error ? error.message : String(error),
});
}
} finally {
if (watch.controllers.get(objectId) === controller) {
watch.controllers.delete(objectId);
logDerivedWatch("unsubscribed", { watchId: watch.watchId, objectId });
}
}
})();
};
const reconcileSubscriptions = (
watch: ActiveDerivedWatch<DerivedContext>,
dependencies: DerivedDependency[],
) => {
const objectIds = dependencyObjectIds(dependencies);
for (const [objectId, controller] of watch.controllers.entries()) {
if (!objectIds.has(objectId)) {
controller.abort();
watch.controllers.delete(objectId);
logDerivedWatch("unsubscribed", { watchId: watch.watchId, objectId });
}
}
for (const objectId of objectIds) {
if (!watch.controllers.has(objectId)) {
startObjectWatch(watch, objectId);
}
}
};
return fieldOps({
async get(context) {
const { value, dependencies } = await runWithDerivedDependencyTracking(
() => operation.get(context),
@@ -138,7 +289,43 @@ export const derivedWithDeps = <Context>(operation: {
logDerivedDependencies(dependencies);
return value;
},
async watchStart(context) {
const watchId = `watch:${randomUUID()}`;
const watch: ActiveDerivedWatch<DerivedContext> = {
watchId,
context: context as DerivedContext,
get: operation.get as RuntimeHandler<DerivedContext>,
controllers: new Map(),
running: false,
pending: false,
stopped: false,
lastValue: undefined,
};
watches.set(watchId, watch);
logDerivedWatch("started", { watchId });
await rerun(watch);
return {
watch_id: watchId,
};
},
watchStop(context) {
const input = protoFieldsToJs((context as DerivedContext).request.input);
const watchId =
typeof input.watch_id === "string"
? input.watch_id
: typeof input.watchId === "string"
? input.watchId
: "";
const watch = watches.get(watchId);
if (!watch) {
throw new Error(`Unknown derived watch: ${watchId}`);
}
stopDerivedWatch(watch);
watches.delete(watchId);
return {};
},
});
};
export type RuntimeFunctionSpec<ExportName extends string = string> = {
exportName: ExportName;