mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
378 lines
17 KiB
TypeScript
378 lines
17 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` contract (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: 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) → exports) 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 → exports; graph row → register its dependency factories and own
|
|
* factory; registered factory → 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 load branch. Loading is async, so a requested dynamic package must have
|
|
* registered its factory before a consumer materializes.
|
|
*
|
|
* This file is the browser-safe contract face (zero node imports): the
|
|
* `__DSH_BOOT__` wire types, the boot-manifest parser, and the boundaries around
|
|
* {@link ClientModuleSystem}. The package root is the host-side service that
|
|
* composes the wire.
|
|
*/
|
|
|
|
import type {} from '@deepseek-ai/cordis'
|
|
import type { ClientModuleSystem } from './system.ts'
|
|
|
|
declare module '@deepseek-ai/cordis' {
|
|
interface Context {
|
|
/** The client module system the web shell builds at boot (provided by the `./client` wrapper plugin). */
|
|
modules: ClientModuleLoader
|
|
}
|
|
}
|
|
|
|
/**
|
|
* One composed client entry pushed by the host (a graph row). Wire
|
|
* single source: the host node half (package root) produces this same shape.
|
|
* `immediately` marks stage-one prefetch. `inject` names package rows whose
|
|
* factories must arrive before this row materializes, while Cordis separately
|
|
* uses the same package edges to compose entries. `external` carries exact
|
|
* non-inject module requests (see {@link WebBootGraph.entries}).
|
|
*/
|
|
export interface WebBootEntry {
|
|
/** Entry name == package name. */
|
|
id: string
|
|
/** Revisioned single-resource combo endpoint used by HMR. */
|
|
url: string
|
|
/** Opaque plugin-artifact revision used for HMR cache busting. */
|
|
rev: string
|
|
/** Package-name dependency edges used for factory arrival and plugin composition. */
|
|
inject?: string[]
|
|
/** Stage-one prefetch mark: load the script for factory registration during module-face boot. */
|
|
immediately?: boolean
|
|
/** Non-baseline module specifiers this row requests; omitted when it requests none. */
|
|
external?: string[]
|
|
}
|
|
|
|
/** Initial scheduling phase for one content-addressed combo script. */
|
|
export type WebBootBatchPhase = 'bootstrap' | 'application'
|
|
|
|
/** One initial combo script; a scheduling phase may span several descriptors. */
|
|
export interface WebBootBatch {
|
|
/** Parser-blocking bootstrap or preloaded application scheduling. */
|
|
phase: WebBootBatchPhase
|
|
/** Content-addressed combo script endpoint. */
|
|
url: string
|
|
/** Revision over the combined plugin script bytes and indexed source map. */
|
|
rev: string
|
|
/** Graph entry ids whose factories the script registers, in execution order. */
|
|
entries: string[]
|
|
}
|
|
|
|
/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */
|
|
export interface WebBootGraph {
|
|
/** Consistency anchor over the whole graph (content + bundle hashes). */
|
|
rev: string
|
|
/**
|
|
* Composed entries in module-graph order — a dynamic package row precedes
|
|
* rows whose `external` requests that package. Cordis activation order is
|
|
* unrelated and remains owned by fiber service waiting.
|
|
*/
|
|
entries: WebBootEntry[]
|
|
/** Initial combo descriptors; every entry belongs to exactly one descriptor. */
|
|
batches: WebBootBatch[]
|
|
}
|
|
|
|
/** The npm-package view of one boot row: what the module table needs to fetch the bundle. */
|
|
export interface BootModuleRow {
|
|
/** Entry name == package name (module-table key). */
|
|
id: string
|
|
/** Revisioned single-resource combo endpoint used after HMR invalidation. */
|
|
url: string
|
|
/** Content-addressed combo endpoint used before the first HMR invalidation. */
|
|
initialUrl: string
|
|
/** Opaque plugin-artifact revision used after HMR invalidation. */
|
|
rev: string
|
|
/** Injected package rows whose factories arrive before this row materializes. */
|
|
inject: string[]
|
|
/** Module specifiers this row requests from the module table ([] when the wire omits them). */
|
|
external: string[]
|
|
}
|
|
|
|
/** The cordis-plugin view of one boot row: what entry composition needs (optional wire fields normalized). */
|
|
export interface BootPluginRow {
|
|
/** Entry name == package name. */
|
|
id: string
|
|
/** Package-name dependency edges ([] when the wire omits them). */
|
|
inject: string[]
|
|
/** Stage-one prefetch tier (false when the wire omits it). */
|
|
immediately: boolean
|
|
}
|
|
|
|
/** The parsed boot manifest: one wire, two consumer views. */
|
|
export interface BootManifest {
|
|
/** Consistency anchor over the whole graph. */
|
|
rev: string
|
|
/** Rows as the module table consumes them. */
|
|
modules: BootModuleRow[]
|
|
/** Rows as entry composition consumes them. */
|
|
plugins: BootPluginRow[]
|
|
}
|
|
|
|
/**
|
|
* Validate an optional string-array field read from a `dsh.client` declaration
|
|
* or from the boot wire.
|
|
* @param subject - diagnostic prefix naming the package or the wire row.
|
|
* @param field - field name as it appears in the diagnostic.
|
|
* @param value - the raw field value.
|
|
* @returns the validated array, or undefined when the field is absent.
|
|
* @throws {Error} when the value is present but is not an array of strings.
|
|
*/
|
|
export function optionalStringArray(subject: string, field: string, value: unknown): string[] | undefined {
|
|
if (value === undefined) return undefined
|
|
if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {
|
|
throw new Error(`client-modules: ${subject} ${field} must be a string array`)
|
|
}
|
|
return value as string[]
|
|
}
|
|
|
|
/**
|
|
* Normalize a module specifier onto the graph row that owns it: a plugin bundle
|
|
* IS its package's client half, so `<id>/client` (the exports subpath external
|
|
* bundles emit) and the bare package name resolve to the same exports. Both the
|
|
* require path and graph composition normalize here, which is what lets each
|
|
* importing package request the subpath its own code imports.
|
|
* @param spec - module specifier as a bundle requires it or a declaration spells it.
|
|
* @returns the specifier with a trailing `/client` removed.
|
|
*/
|
|
export function stripClientSuffix(spec: string): string {
|
|
return spec.endsWith('/client') ? spec.slice(0, -'/client'.length) : spec
|
|
}
|
|
|
|
/**
|
|
* Parse `window.__DSH_BOOT__` into the two consumer views. Wire boundary:
|
|
* a missing or malformed graph throws (the shell shows the loud failure —
|
|
* a page without a valid manifest cannot boot anything).
|
|
* @param wire - the raw `window.__DSH_BOOT__` value.
|
|
* @returns the manifest with optional plugin-view fields normalized.
|
|
*/
|
|
export function parseBootManifest(wire: unknown): BootManifest {
|
|
if (typeof wire !== 'object' || wire === null) {
|
|
throw new Error('client-modules: window.__DSH_BOOT__ is missing or not an object')
|
|
}
|
|
const graph = wire as Record<string, unknown>
|
|
if (typeof graph.rev !== 'string') {
|
|
throw new Error('client-modules: boot manifest rev must be a string')
|
|
}
|
|
if (!Array.isArray(graph.entries)) {
|
|
throw new Error('client-modules: boot manifest entries must be an array')
|
|
}
|
|
if (!Array.isArray(graph.batches)) {
|
|
throw new Error('client-modules: boot manifest batches must be an array')
|
|
}
|
|
const moduleFields: Omit<BootModuleRow, 'initialUrl'>[] = []
|
|
const plugins: BootPluginRow[] = []
|
|
const seenEntryIds = new Set<string>()
|
|
for (const value of graph.entries as unknown[]) {
|
|
if (typeof value !== 'object' || value === null) {
|
|
throw new Error('client-modules: boot manifest entry is not an object')
|
|
}
|
|
const row = value as Record<string, unknown>
|
|
const where = typeof row.id === 'string' ? `"${row.id}"` : JSON.stringify(row)
|
|
if (typeof row.id !== 'string' || typeof row.url !== 'string' || typeof row.rev !== 'string') {
|
|
throw new Error(`client-modules: boot manifest entry ${where} must carry string id/url/rev`)
|
|
}
|
|
if (seenEntryIds.has(row.id)) throw new Error(`client-modules: duplicate graph entry "${row.id}"`)
|
|
seenEntryIds.add(row.id)
|
|
const subject = `boot manifest entry ${where}`
|
|
const inject = optionalStringArray(subject, 'inject', row.inject)
|
|
const external = optionalStringArray(subject, 'external', row.external)
|
|
if (row.immediately !== undefined && typeof row.immediately !== 'boolean') {
|
|
throw new Error(`client-modules: boot manifest entry ${where} immediately must be a boolean`)
|
|
}
|
|
moduleFields.push({
|
|
id: row.id,
|
|
url: row.url,
|
|
rev: row.rev,
|
|
inject: inject === undefined ? [] : [...inject],
|
|
external: external === undefined ? [] : [...external],
|
|
})
|
|
plugins.push({
|
|
id: row.id,
|
|
inject: inject === undefined ? [] : [...inject],
|
|
immediately: row.immediately === true,
|
|
})
|
|
}
|
|
|
|
const entryIds = new Set(moduleFields.map(row => row.id))
|
|
const initialUrls = new Map<string, string>()
|
|
const batchUrls = new Set<string>()
|
|
for (const value of graph.batches as unknown[]) {
|
|
if (typeof value !== 'object' || value === null) {
|
|
throw new Error('client-modules: boot manifest batch is not an object')
|
|
}
|
|
const batch = value as Record<string, unknown>
|
|
const phase = batch.phase
|
|
if (phase !== 'bootstrap' && phase !== 'application') {
|
|
throw new Error(`client-modules: boot manifest batch phase must be "bootstrap" or "application", received ${JSON.stringify(phase)}`)
|
|
}
|
|
if (typeof batch.url !== 'string' || typeof batch.rev !== 'string') {
|
|
throw new Error(`client-modules: boot manifest ${phase} batch must carry string url/rev`)
|
|
}
|
|
if (batchUrls.has(batch.url)) {
|
|
throw new Error(`client-modules: boot manifest carries duplicate batch URL ${JSON.stringify(batch.url)}`)
|
|
}
|
|
batchUrls.add(batch.url)
|
|
const entries = optionalStringArray(`boot manifest ${phase} batch`, 'entries', batch.entries)
|
|
if (entries === undefined || entries.length === 0) {
|
|
throw new Error(`client-modules: boot manifest ${phase} batch entries must be a non-empty string array`)
|
|
}
|
|
for (const id of entries) {
|
|
if (!entryIds.has(id)) {
|
|
throw new Error(`client-modules: boot manifest ${phase} batch names unknown entry "${id}"`)
|
|
}
|
|
if (initialUrls.has(id)) {
|
|
throw new Error(`client-modules: boot manifest entry "${id}" belongs to more than one batch`)
|
|
}
|
|
initialUrls.set(id, batch.url)
|
|
}
|
|
}
|
|
const modules = moduleFields.map((row): BootModuleRow => {
|
|
const initialUrl = initialUrls.get(row.id)
|
|
if (initialUrl === undefined) {
|
|
throw new Error(`client-modules: boot manifest entry "${row.id}" belongs to no initial-load batch`)
|
|
}
|
|
return { ...row, initialUrl }
|
|
})
|
|
return { rev: graph.rev, modules, plugins }
|
|
}
|
|
|
|
/** One client bundle's factory registration submitted through `window.__ModuleLoader__.load`. */
|
|
export interface ClientBundleRegistration {
|
|
/** 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 exports. Runs
|
|
* once, at materialization.
|
|
*/
|
|
factory: (require: (spec: string) => unknown) => Record<string, unknown>
|
|
}
|
|
|
|
/** Inputs passed by the web entry when it creates the client module system. */
|
|
export interface ClientModuleCreateOptions {
|
|
/** Raw Host-injected boot graph; the modules bundle owns validation and projection. */
|
|
boot: unknown
|
|
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
staticModules: Record<string, unknown>
|
|
/** Bundle-load hook. Defaults to a same-origin classic `<script src>` element. */
|
|
loadBundle?: (url: string) => Promise<void>
|
|
}
|
|
|
|
/** The modules bundle after its factory has been materialized by the HTML bootstrap facade. */
|
|
export interface ClientBootstrapModule {
|
|
/** Graph/module id carried by the modules bundle registration. */
|
|
id: string
|
|
/** Materialized exports reused when Cordis later activates the modules entry. */
|
|
exports: Record<string, unknown>
|
|
}
|
|
|
|
/** Stable page-global facade: queues early bundle registrations, then registers them live. */
|
|
export interface ClientModuleLoaderTarget {
|
|
/** Queue before {@link create}; live registration after it returns. */
|
|
mode: 'queue' | 'live'
|
|
/** Registrations submitted by parser-preloaded scripts before the module system exists. */
|
|
pendingQueue: ClientBundleRegistration[]
|
|
/** Queue or immediately register one bundle factory according to {@link mode}. */
|
|
load(registration: ClientBundleRegistration): void
|
|
/** Create the module system exactly once from the parser-preloaded modules bundle. */
|
|
create(options: ClientModuleCreateOptions): ClientModuleSystem
|
|
}
|
|
|
|
/** Window API of the web boot protocol: the host-injected graph and registration facade. */
|
|
export interface DshWindow {
|
|
/** Host-composed entry graph, injected before the shell bundle runs; wire-boundary raw until {@link parseBootManifest}. */
|
|
__DSH_BOOT__?: unknown
|
|
/** HTML-installed facade: a pending registration queue, then the live module-system target. */
|
|
__ModuleLoader__?: ClientModuleLoaderTarget
|
|
}
|
|
|
|
/** Per-module bookkeeping in {@link ClientModuleLoader.loadCache} (flat module-graph boundary). */
|
|
export interface ClientModuleRecord {
|
|
/** Module id (entry name / package name). */
|
|
id: string
|
|
/** Materialized exports (`module.exports` from a factory or bootstrap registration). */
|
|
exports: unknown
|
|
/** Owned `<style data-plugin>` tag ids (`data-plugin-css` values) injected during materialization. */
|
|
styles: string[]
|
|
/** Observed `require()` edges (module-graph boundary; only table words can appear). */
|
|
edges: Set<string>
|
|
}
|
|
|
|
/**
|
|
* The internal-contract subset the vendored Loader and the client HMR plugin
|
|
* consume. Mounted on `ctx.loader.internal` by the shell boot and provided
|
|
* as `ctx.modules`.
|
|
*/
|
|
export interface ClientModuleLoader {
|
|
/** Discriminant against Node's internal loader shapes ('v1'/'v2'). */
|
|
version: 'client'
|
|
/** Parsed Host boot graph shared with the web entry after module-system creation. */
|
|
manifest: BootManifest
|
|
/** Materialized-module registry: id → record. The governance-side read API for entry exports. */
|
|
loadCache: Map<string, ClientModuleRecord>
|
|
/**
|
|
* Internal contract 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 loader contract).
|
|
* @returns the module's exports.
|
|
*/
|
|
import(specifier: string, parentURL: string, attrs: Record<string, unknown>): Promise<unknown>
|
|
/**
|
|
* Stage-one arrival: load the entry's declared dynamic requests, then its
|
|
* own script, to register their factories (no materialization — module side
|
|
* effects wait for import).
|
|
* No-op for materialized bootstrap ids. A registered graph row still
|
|
* registers any unresolved declared requests before skipping its own script;
|
|
* concurrent arrivals share one in-flight task. To force a fresh load (HMR),
|
|
* {@link invalidate} first.
|
|
* @param id - graph entry name.
|
|
*/
|
|
prefetch(id: string): Promise<void>
|
|
/**
|
|
* Full reset of one non-bootstrap module: drop its registered factory and
|
|
* materialized record so the next prefetch/import loads its one-resource
|
|
* combo script rather than the initial multi-resource request. The bootstrap
|
|
* module remains materialized.
|
|
* @param id - entry name to invalidate.
|
|
* @param rev - New content revision from the HMR frame; omitted to reuse
|
|
* the graph revision or for page-local modules that register directly.
|
|
*/
|
|
invalidate(id: string, rev?: string): void
|
|
}
|
|
|
|
/** Internal construction inputs assembled by the modules bundle's bootstrap export. */
|
|
export interface ClientModuleSystemOptions {
|
|
/** Parsed boot graph owned by the resulting module system. */
|
|
manifest: BootManifest
|
|
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
staticModules: Record<string, unknown>
|
|
/** Stable HTML-installed registration facade to switch from queue to live mode. */
|
|
registrationTarget: ClientModuleLoaderTarget
|
|
/** Already-materialized modules bundle consumed while creating the system. */
|
|
bootstrapModule: ClientBootstrapModule
|
|
/** Bundle-load hook. Defaults to a same-origin classic `<script src>` element. */
|
|
loadBundle?: (url: string) => Promise<void>
|
|
}
|