93 lines · 3.5 KB · TypeScript
RawDownload
import { join } from "node:path"; import { Effect } from "effect"; import { makeGitArtifactStore } from "../backing/git-artifact-store";import { makeLibsqlScopeDb } from "../backing/libsql-scope-db";import { makeQuickjsToolSandbox } from "../backing/quickjs-tool-sandbox";import { makeSqliteWorkflowRunner } from "../backing/sqlite-workflow-runner";import { makeQuickjsWorkflowDriver } from "../backing/quickjs-workflow-driver";import { makeInProcessLiveChannel } from "../backing/in-process-live-channel";import type { ScopeDb } from "../seams/scope-db";import type { LiveChannel } from "../seams/live-channel";import { makeAppsRuntime, type AppsRuntime } from "./runtime";import type { AppsStore } from "./store";import type { ClientResolver } from "./bindings"; // ---------------------------------------------------------------------------// Wire the five self-hosted seam backings into one AppsRuntime, rooted at a// data directory. This is what the self-host app calls at boot. The ScopeDb's// write events are wired into the LiveChannel here (invalidation delivery), and// the store is provided by the caller (over the host's pluginStorage + blobs).// --------------------------------------------------------------------------- export interface SelfHostAppsRuntimeOptions {  /** Data dir root; `<root>/artifacts`, `<root>/scope-db`, `<root>/workflows`. */  readonly dataDir: string;  /** Store over the host's pluginStorage + blobs. */  readonly store: AppsStore;  /** Routes bound integration calls to real APIs (policy/audit). */  readonly resolver: ClientResolver;  /** In-memory backings for tests. */  readonly inMemory?: boolean;} export interface SelfHostAppsRuntime {  readonly runtime: AppsRuntime;  readonly scopeDb: ScopeDb;  readonly liveChannel: LiveChannel;  readonly close: () => Promise<void>;} export const makeSelfHostAppsRuntime = (  options: SelfHostAppsRuntimeOptions,): SelfHostAppsRuntime => {  const inMem = options.inMemory === true;  const artifactStore = makeGitArtifactStore({    root: inMem ? options.dataDir : join(options.dataDir, "artifacts"),  });  const scopeDb = makeLibsqlScopeDb({    root: inMem ? ":memory:" : join(options.dataDir, "scope-db"),  });  const sandbox = makeQuickjsToolSandbox();  // The workflow body runs inside the sandbox (Fix 3): the driver loads the  // pinned bundle from the artifact store and drives one replay in QuickJS  // behind the serializable step bridge. The runner services the bridge against  // its journal.  const workflowDriver = makeQuickjsWorkflowDriver({ artifactStore });  const workflows = makeSqliteWorkflowRunner({    path: inMem ? ":memory:" : join(options.dataDir, "workflows", "journal.db"),    driver: workflowDriver,  });  const liveChannel = makeInProcessLiveChannel();   // Wire scope-db writes -> live invalidations.  const unwire = scopeDb.onWrite((event) => {    for (const t of event.tables) {      void Effect.runPromise(        liveChannel.publish({ scope: event.scope, table: t.table, version: t.version }),      );    }  });   const runtime = makeAppsRuntime({    artifactStore,    scopeDb,    sandbox,    workflows,    liveChannel,    store: options.store,    resolver: options.resolver,  });   return {    runtime,    scopeDb,    liveChannel,    close: async () => {      unwire();      await Effect.runPromise(scopeDb.close().pipe(Effect.orElseSucceed(() => undefined)));      await Effect.runPromise(workflows.close().pipe(Effect.orElseSucceed(() => undefined)));    },  };};