Publish camino-package-runtime from 4a2b76ed15a52d06b422fb3d1f651be47d92ff51
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git",
|
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git",
|
||||||
"sourceCommit": "ddccb886544038ee85e4f1fd4db1c35b24869199",
|
"sourceCommit": "4a2b76ed15a52d06b422fb3d1f651be47d92ff51",
|
||||||
"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"
|
||||||
|
|||||||
+189
-2
@@ -1,5 +1,6 @@
|
|||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
import { AsyncLocalStorage } from "node:async_hooks";
|
import { AsyncLocalStorage } from "node:async_hooks";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
import { create } from "@bufbuild/protobuf";
|
import { create } from "@bufbuild/protobuf";
|
||||||
import { createClient, type Client } from "@connectrpc/connect";
|
import { createClient, type Client } from "@connectrpc/connect";
|
||||||
import {
|
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> = {
|
export type Field<T> = {
|
||||||
get(): Promise<T | undefined>;
|
get(): Promise<T | undefined>;
|
||||||
set(value: T): Promise<void>;
|
set(value: T): Promise<void>;
|
||||||
@@ -129,8 +172,116 @@ export const fieldOps = <Context>(
|
|||||||
|
|
||||||
export const derivedWithDeps = <Context>(operation: {
|
export const derivedWithDeps = <Context>(operation: {
|
||||||
get: RuntimeHandler<Context>;
|
get: RuntimeHandler<Context>;
|
||||||
}): FieldOperation<Context> =>
|
}): FieldOperation<Context> => {
|
||||||
fieldOps({
|
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) {
|
async get(context) {
|
||||||
const { value, dependencies } = await runWithDerivedDependencyTracking(
|
const { value, dependencies } = await runWithDerivedDependencyTracking(
|
||||||
() => operation.get(context),
|
() => operation.get(context),
|
||||||
@@ -138,7 +289,43 @@ export const derivedWithDeps = <Context>(operation: {
|
|||||||
logDerivedDependencies(dependencies);
|
logDerivedDependencies(dependencies);
|
||||||
return value;
|
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> = {
|
export type RuntimeFunctionSpec<ExportName extends string = string> = {
|
||||||
exportName: ExportName;
|
exportName: ExportName;
|
||||||
|
|||||||
Reference in New Issue
Block a user