mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Generator (all four structural gaps):
- harness service pages now render public properties/accessors, not just
methods (ctx.codeRuntime.language/isolation were missing);
- the class page merges the same-named interface half, so ctx.root/baseUrl/
events/logger/reflect/registry appear on Context (vendor root JSDoc gains
prose alongside @experimental);
- Pick<…> heritage on a Context merge resolves to the picked class members,
giving ctx.effect a documented signature on the Fiber page;
- {@link} tags normalize to code spans; merge sections get their own h2 so
reflect members no longer nest under 'Static members'.
verify-website-yaml: reject the unloadable 'group:' pseudo-name (tree.import
only special-cases 'cordis:'; no builtin is registered here) and recurse into
@cordisjs/plugin-group nested entry lists instead.
Prose corrected against loader/cordis source: service.md isolation example
uses the real group plugin + group: true + the required isolate map;
config.md documents concurrent entry startup (Promise.all; order via inject)
and the real hmr defaults (root ['.'], base/ignored/debounce); events.md
fixes emit (synchronous, not parallel), bail (null/false also delegate), and
serial (stops at the first bail value).
147 lines
6.0 KiB
TypeScript
147 lines
6.0 KiB
TypeScript
import { Dict } from 'cosmokit'
|
|
import { EventsService } from './events.ts'
|
|
import { LoggerService } from './logger.ts'
|
|
import { ReflectService } from './reflect.ts'
|
|
import { InjectKey, RegistryService } from './registry.ts'
|
|
import { getTraceable, symbols } from './utils.ts'
|
|
import { Fiber } from './fiber.ts'
|
|
|
|
/**
|
|
* Public shape of a Cordis context.
|
|
*
|
|
* The concrete `Context` class is proxied at runtime, so this interface is
|
|
* augmented by core services and plugins to describe the properties that may
|
|
* be read from `ctx`.
|
|
*/
|
|
export interface Context {
|
|
/** Isolation map: service name → scope label. Lookups for a name resolve within its label. */
|
|
[symbols.isolate]: Dict<symbol>
|
|
/** Intercept map: service name → config merged into that service's per-plugin config. */
|
|
[symbols.intercept]: Dict
|
|
/** The root context of the application (every child context shares it). @experimental */
|
|
root: this
|
|
/** Base URL used to resolve relative plugin/module specifiers, if the runtime sets one. */
|
|
baseUrl?: string
|
|
/** The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...). */
|
|
events: EventsService
|
|
/** The logging service. Call `ctx.logger(name)` for a named logger. */
|
|
logger: LoggerService
|
|
/** The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...). */
|
|
reflect: ReflectService
|
|
/** The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`). */
|
|
registry: RegistryService
|
|
}
|
|
|
|
/**
|
|
* Root and child dependency containers for Cordis plugins.
|
|
*
|
|
* A context is a proxy: normal property reads go through the service resolver,
|
|
* while `extend()`, `isolate()`, and `intercept()` create scoped child
|
|
* contexts without mutating their parent.
|
|
*/
|
|
export class Context {
|
|
/** Symbol key under which a disposer exposes its {@link EffectMeta} diagnostics tree. */
|
|
static readonly effect: unique symbol = symbols.effect
|
|
/** Symbol key for a context's listener filter, consulted on every event dispatch. */
|
|
static readonly filter: unique symbol = symbols.filter
|
|
/** Symbol key of the isolation map (see the `Context[symbols.isolate]` property). */
|
|
static readonly isolate: unique symbol = symbols.isolate
|
|
/** Symbol key of the intercept map (see the `Context[symbols.intercept]` property). */
|
|
static readonly intercept: unique symbol = symbols.intercept
|
|
|
|
/**
|
|
* Returns true for Cordis context proxies and context prototypes.
|
|
*
|
|
* Works across realms and across multiple copies of cordis, because the
|
|
* brand is keyed by a global symbol rather than by `instanceof`.
|
|
*
|
|
* @param value — the value to test.
|
|
* @returns `true` if `value` is a Cordis context, narrowing its type.
|
|
*/
|
|
static is(value: any): value is Context {
|
|
return !!value?.[Context.is as any]
|
|
}
|
|
|
|
static {
|
|
Context.is[Symbol.toPrimitive] = () => Symbol.for('cordis.is')
|
|
Context.prototype[Context.is as any] = true
|
|
}
|
|
|
|
/** Create the root context and install the built-in services. */
|
|
constructor() {
|
|
this[symbols.isolate] = Object.create(null)
|
|
this[symbols.intercept] = Object.create(null)
|
|
const self = new Proxy<this>(this, ReflectService.handler)
|
|
this.root = self
|
|
this.baseUrl = undefined
|
|
this.fiber = new Fiber(self, {}, Object.create(null), null, () => [])
|
|
this.reflect = new ReflectService(self)
|
|
this.registry = new RegistryService(self)
|
|
this.events = new EventsService(self)
|
|
this.logger = new LoggerService(self)
|
|
this.fiber._disposables.clear()
|
|
return self
|
|
}
|
|
|
|
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
return `Context <${this.fiber.name}>`
|
|
}
|
|
|
|
/**
|
|
* Create a child context with extra metadata on top of the current scope.
|
|
*
|
|
* The child prototypally inherits every property of this context; own
|
|
* properties of `meta` shadow the inherited ones. The parent is not mutated.
|
|
*
|
|
* @param meta — own properties (including symbol keys) to define on the child.
|
|
* @returns a child context inheriting from this one.
|
|
*/
|
|
extend(meta = {}): this {
|
|
const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value
|
|
const self = Object.create(getTraceable(this, this))
|
|
for (const prop of Reflect.ownKeys(meta)) {
|
|
Object.defineProperty(self, prop, Reflect.getOwnPropertyDescriptor(meta, prop)!)
|
|
}
|
|
if (!shadow) return self
|
|
return Object.assign(Object.create(self), { [symbols.shadow]: shadow })
|
|
}
|
|
|
|
/**
|
|
* Create a child context with an independent service scope for `name`.
|
|
*
|
|
* Below the returned context, reads and writes of the service `name`
|
|
* resolve against the new label instead of the parent's, so a different
|
|
* implementation can be provided without affecting the parent scope.
|
|
* Passing the same `label` to two `isolate()` calls joins their scopes.
|
|
*
|
|
* @param name — the service name to isolate.
|
|
* @param label — scope label to join; defaults to a fresh unique symbol.
|
|
* @returns a child context whose `name` service resolves in the new scope.
|
|
*/
|
|
isolate(name: string, label?: symbol) {
|
|
const shadow = Object.create(this[symbols.isolate])
|
|
shadow[name] = label ?? Symbol(name)
|
|
return this.extend({ [symbols.isolate]: shadow })
|
|
}
|
|
|
|
/**
|
|
* Add service-specific intercept config for plugins started below this
|
|
* context.
|
|
*
|
|
* Plugins loaded under the returned context see `config` merged into the
|
|
* service's resolved config (ancestor entries first; see
|
|
* `Service[symbols.resolveConfig]`). The parent context is not affected.
|
|
*
|
|
* @param name — the service name whose config to intercept.
|
|
* @param config — the intercept config to merge for that service.
|
|
* @returns a child context carrying the additional intercept entry.
|
|
*/
|
|
intercept<K extends InjectKey>(name: K, config: Context[K] extends { [symbols.config]: infer T } ? T : never): this
|
|
intercept(name: string, config: any): this
|
|
intercept(name: string, config: any) {
|
|
const intercept = Object.create(this[symbols.intercept])
|
|
intercept[name] = config
|
|
return this.extend({ [symbols.intercept]: intercept })
|
|
}
|
|
}
|