mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
The module system moves out of dsh-client-runtime (./loader retired) into its own package: a lazy CJS table where executing a bundle only registers its factory and materialization happens at first require, memoized, with recursive requires self-ordering. ClientModuleSystem is a class; index.ts keeps the types and a thin factory. Boot is two-phase: phase one prefetches the immediately tier in parallel (registration only, failures deferred to phase two's loud import); phase two mounts the vendored Loader with the module system as internal, creates one entry per graph row plus the app-shell pseudo-row the kernel appends itself, and settles on an all-ACTIVE sweep. The shell kernel is self-sufficient: hand-rolled loader-status stores, no plugin value imports, platform seed list single- sourced in platform.ts.
176 lines
8.0 KiB
TypeScript
176 lines
8.0 KiB
TypeScript
/**
|
|
* Client module system: the browser peer of Node's internal ESM loader, built
|
|
* as a lazy CJS table. The vendored cordis Loader consumes this object
|
|
* through its `internal` seam (the only call site is `EntryTree.import` →
|
|
* `internal.import`), which keeps entry governance (fiber lifecycle, inject
|
|
* waiting, update/refresh) entirely on the vendored side while this package
|
|
* owns code arrival.
|
|
*
|
|
* Lazy CJS model (web2 §0): executing a plugin bundle only REGISTERS its
|
|
* factory (`window.__ModuleLoader__.load({id, factory})`); every module body
|
|
* side effect — including CSS injection — lives inside the factory closure
|
|
* and runs at materialization, not at script execution. Materialization
|
|
* (factory(require) → export surface) happens on first import/require and is
|
|
* memoized in {@link ClientModuleLoader.loadCache}; a factory that requires
|
|
* another registered-but-unmaterialized module materializes it recursively,
|
|
* so load order needs no external sequencing.
|
|
*
|
|
* Resolution branch order (import): seed word → shell instance; memoized
|
|
* record → surface; static registry (shell-own modules, e.g. app-shell) →
|
|
* module; registered factory → materialize; graph row → fetch + execute +
|
|
* materialize; anything else → throw (loud — the runtime mirror of the
|
|
* build-time bundle purity gate). The synchronous `require` handed to
|
|
* factories walks the same order minus the fetch branch: fetching is async,
|
|
* so only already-executed bundles can be required — and cross-plugin value
|
|
* imports are a build error anyway.
|
|
* @module @deepseek-ai/dsh-client-modules
|
|
*/
|
|
|
|
import { ClientModuleLoaderImpl } from './loader.ts'
|
|
|
|
export { ClientModuleLoaderImpl }
|
|
|
|
declare module 'cordis' {
|
|
interface Context {
|
|
/** The client module system the web shell provides at boot (contract C5). */
|
|
modules: ClientModuleLoader
|
|
}
|
|
}
|
|
|
|
/**
|
|
* One composed client entry pushed by the host (web2 §0 graph row).
|
|
* `immediately` marks stage-one prefetch; `inject` is informational graph
|
|
* metadata (the authoritative edges live in each package's dshClient
|
|
* declaration and reach fibers through entry creation).
|
|
*
|
|
* Wire contract, held on both sides: the producing peer lives in
|
|
* `@deepseek-ai/dsh-host-webserver` (host packages keep zero workspace
|
|
* dependencies, so neither side imports the other's shape — drift between
|
|
* the two declarations is a bug against the web2 contract).
|
|
*/
|
|
export interface WebBootEntry {
|
|
/** Entry name == package name (or a shell-owned pseudo id, e.g. app-shell). */
|
|
id: string
|
|
/**
|
|
* Bundle endpoint, '/plugins/<id>/client.js?rev=<rev>'. Absent only on
|
|
* shell-owned pseudo rows (app-shell) whose module is statically registered
|
|
* — a row that is neither fetchable nor static-registered fails loud.
|
|
*/
|
|
url?: string
|
|
/** Bundle content hash (cache-busting consistency anchor); absent with url. */
|
|
rev?: string
|
|
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
|
inject?: string[]
|
|
/** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */
|
|
immediately?: boolean
|
|
}
|
|
|
|
/** The composed client entry graph the host injects as `window.__DSH_BOOT__` (dual-held wire contract — see {@link WebBootEntry}). */
|
|
export interface WebBootGraph {
|
|
/** Consistency anchor over the whole graph (content + bundle hashes). */
|
|
rev: string
|
|
/** Composed entries; order carries no semantics (activation order is fiber inject waiting). */
|
|
entries: WebBootEntry[]
|
|
}
|
|
|
|
/** The shape a client bundle hands to `window.__ModuleLoader__.load` (registration handoff, contract C6). */
|
|
export interface ClientPluginHandoff {
|
|
/** Plugin id (package name) — the registration key; must match the graph row being executed. */
|
|
id: string
|
|
/**
|
|
* Closure factory holding the whole bundle body: receives the synchronous
|
|
* require bound to the module table and returns the bundle's export
|
|
* surface. Runs once, at materialization.
|
|
*/
|
|
factory: (require: (spec: string) => unknown) => Record<string, unknown>
|
|
}
|
|
|
|
/** Window surface this loader owns (bundle side of the handoff protocol) plus the host-injected graph. */
|
|
export interface DshWindow {
|
|
/** Host-composed entry graph, injected before the shell bundle runs. */
|
|
__DSH_BOOT__?: WebBootGraph
|
|
/** Bundle registration sink; installed once per page by {@link createClientModuleLoader} (contract C6). */
|
|
__ModuleLoader__?: { load(handoff: ClientPluginHandoff): void }
|
|
}
|
|
|
|
/** Per-module bookkeeping in {@link ClientModuleLoader.loadCache} (module-graph seam, flat today). */
|
|
export interface ClientModuleRecord {
|
|
/** Module id (entry name / package name). */
|
|
id: string
|
|
/** The materialized export surface (factory `module.exports`, or the shell module for static registrations). */
|
|
surface: unknown
|
|
/** Owned `<style data-plugin>` tag ids (`data-plugin-css` values) injected during materialization. */
|
|
styles: string[]
|
|
/** Observed `require()` edges (module-graph seam; only table words can appear today). */
|
|
edges: Set<string>
|
|
}
|
|
|
|
/**
|
|
* The internal-seam subset the vendored Loader and the client HMR plugin
|
|
* consume. Mounted on `ctx.loader.internal` by the shell boot and provided
|
|
* as `ctx.modules` (contract C5).
|
|
*/
|
|
export interface ClientModuleLoader {
|
|
/** Discriminant against Node's internal loader shapes ('v1'/'v2'). */
|
|
version: 'client'
|
|
/** Materialized-module registry: id → record. The governance-side read face for entry export surfaces. */
|
|
loadCache: Map<string, ClientModuleRecord>
|
|
/**
|
|
* Internal seam consumed by the vendored Loader's `tree.import`. Resolves
|
|
* `specifier` through the branch order documented on the module, fetching
|
|
* and executing a bundle when needed.
|
|
* @param specifier - module specifier (entry name or table word).
|
|
* @param parentURL - importer URL (unused — the client module graph is flat).
|
|
* @param attrs - import attributes (unused; interface parity with Node's seam).
|
|
* @returns the module's export surface.
|
|
*/
|
|
import(specifier: string, parentURL: string, attrs: Record<string, unknown>): Promise<unknown>
|
|
/**
|
|
* Register a shell-own module (app-shell — code that ships inside the shell
|
|
* bundle and never arrives as a plugin bundle).
|
|
* @param id - entry name (shell-owned pseudo id).
|
|
* @param module - the statically imported module namespace.
|
|
*/
|
|
registerStatic(id: string, module: unknown): void
|
|
/**
|
|
* Stage-one arrival: fetch the entry's bundle and execute it, registering
|
|
* its factory (no materialization — module side effects wait for import).
|
|
* No-op for static-registered ids and ids whose factory is already
|
|
* registered; concurrent calls share one in-flight task. To force a fresh
|
|
* fetch (HMR), {@link invalidate} first.
|
|
* @param id - graph entry name.
|
|
*/
|
|
prefetch(id: string): Promise<void>
|
|
/**
|
|
* Full reset of one module: drop its registered factory, its materialized
|
|
* record, and any consumed bundle text, so the next prefetch/import
|
|
* refetches and re-executes (the HMR invalidation hook).
|
|
* @param id - entry name to invalidate.
|
|
*/
|
|
invalidate(id: string): void
|
|
}
|
|
|
|
/** Options for {@link createClientModuleLoader} (assembled by the web shell at boot). */
|
|
export interface ClientModuleLoaderOptions {
|
|
/** Host-composed entry graph. */
|
|
graph: WebBootGraph
|
|
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
staticModules: Record<string, unknown>
|
|
/** Bundle fetch seam (parallelizable half). Defaults to same-origin fetch().text(). */
|
|
fetchBundle?: (url: string) => Promise<string>
|
|
/**
|
|
* Bundle execution seam (synchronously performs the load() registration).
|
|
* Defaults to a <script> element carrying the code.
|
|
*/
|
|
executeBundle?: (code: string, url: string) => void
|
|
}
|
|
|
|
/**
|
|
* Build the client module system.
|
|
* @param options - entry graph, module-table staticModules, fetch/execute seams.
|
|
* @returns the loader the shell mounts as `ctx.loader.internal` and provides as `ctx.modules`.
|
|
*/
|
|
export function createClientModuleLoader(options: ClientModuleLoaderOptions): ClientModuleLoader {
|
|
return new ClientModuleLoaderImpl(options)
|
|
}
|