返回源码地图

vendor/cordis/src/context.ts

main snapshot · da00f7f5358f · 正文引用章节 02 / 03 / 03;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1import type { Dict } from '@deepseek-ai/cosmokit'
2import { EventsService } from './events.ts'
3import { LoggerService } from './logger.ts'
4import { ReflectService } from './reflect.ts'
5import { RegistryService, type InjectKey } from './registry.ts'
6import { getTraceable, symbols } from './utils.ts'
7import { Fiber } from './fiber.ts'
8
9/**
10 * Public shape of a Cordis context.
11 *
12 * The concrete `Context` class is proxied at runtime, so this interface is
13 * augmented by core services and plugins to describe the properties that may
14 * be read from `ctx`.
15 */
16export interface Context {
17 /** Isolation map: service name → scope label. Lookups for a name resolve within its label. */
18 [symbols.isolate]: Dict<symbol>
19 /** Intercept map: service name → config merged into that service's per-plugin config. */
20 [symbols.intercept]: Dict
21 /** The root context of the application (every child context shares it). @experimental */
22 root: this
23 /** Base URL used to resolve relative plugin/module specifiers, if the runtime sets one. */
24 baseUrl?: string
25 /** The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...). */
26 events: EventsService
27 /** The logging service. Call `ctx.logger(name)` for a named logger. */
28 logger: LoggerService
29 /** The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...). */
30 reflect: ReflectService
31 /** The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`). */
32 registry: RegistryService
33}
34
35/**
36 * Root and child dependency containers for Cordis plugins.
37 *
38 * A context is a proxy: normal property reads go through the service resolver,
39 * while `extend()`, `isolate()`, and `intercept()` create scoped child
40 * contexts without mutating their parent.
41 */
42export class Context {
43 /** Symbol key under which a disposer exposes its {@link EffectMeta} diagnostics tree. */
44 static readonly effect: unique symbol = symbols.effect
45 /** Symbol key for a context's listener filter, consulted on every event dispatch. */
46 static readonly filter: unique symbol = symbols.filter
47 /** Symbol key of the isolation map (see the `Context[symbols.isolate]` property). */
48 static readonly isolate: unique symbol = symbols.isolate
49 /** Symbol key of the intercept map (see the `Context[symbols.intercept]` property). */
50 static readonly intercept: unique symbol = symbols.intercept
51
52 /**
53 * Returns true for Cordis context proxies and context prototypes.
54 *
55 * Works across realms and across multiple copies of cordis, because the
56 * brand is keyed by a global symbol rather than by `instanceof`.
57 *
58 * @param value — the value to test.
59 * @returns `true` if `value` is a Cordis context, narrowing its type.
60 */
61 static is(value: any): value is Context {
62 return !!value?.[Context.is as any]
63 }
64
65 static {
66 Context.is[Symbol.toPrimitive] = () => Symbol.for('cordis.is')
67 Context.prototype[Context.is as any] = true
68 }
69
70 /** Create the root context and install the built-in services. */
71 constructor() {
72 this[symbols.isolate] = Object.create(null)
73 this[symbols.intercept] = Object.create(null)
74 const self = new Proxy<this>(this, ReflectService.handler)
75 this.root = self
76 this.baseUrl = undefined
77 this.fiber = new Fiber(self, {}, Object.create(null), null, () => [])
78 this.reflect = new ReflectService(self)
79 this.registry = new RegistryService(self)
80 this.events = new EventsService(self)
81 this.logger = new LoggerService(self)
82 this.fiber._disposables.clear()
83 return self
84 }
85
86 [Symbol.for('nodejs.util.inspect.custom')]() {
87 return `Context <${this.fiber.name}>`
88 }
89
90 /**
91 * Create a child context with extra metadata on top of the current scope.
92 *
93 * The child prototypally inherits every property of this context; own
94 * properties of `meta` shadow the inherited ones. The parent is not mutated.
95 *
96 * @param meta — own properties (including symbol keys) to define on the child.
97 * @returns a child context inheriting from this one.
98 */
99 extend(meta = {}): this {
100 const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value
101 const self = Object.create(getTraceable(this, this))
102 for (const prop of Reflect.ownKeys(meta)) {
103 Object.defineProperty(self, prop, Reflect.getOwnPropertyDescriptor(meta, prop)!)
104 }
105 if (!shadow) return self
106 return Object.assign(Object.create(self), { [symbols.shadow]: shadow })
107 }
108
109 /**
110 * Create a child context with an independent service scope for `name`.
111 *
112 * Below the returned context, reads and writes of the service `name`
113 * resolve against the new label instead of the parent's, so a different
114 * implementation can be provided without affecting the parent scope.
115 * Passing the same `label` to two `isolate()` calls joins their scopes.
116 *
117 * @param name — the service name to isolate.
118 * @param label — scope label to join; defaults to a fresh unique symbol.
119 * @returns a child context whose `name` service resolves in the new scope.
120 */
121 isolate(name: string, label?: symbol) {
122 const shadow = Object.create(this[symbols.isolate])
123 shadow[name] = label ?? Symbol(name)
124 return this.extend({ [symbols.isolate]: shadow })
125 }
126
127 /**
128 * Add service-specific intercept config for plugins started below this
129 * context.
130 *
131 * Plugins loaded under the returned context see `config` merged into the
132 * service's resolved config (ancestor entries first; see
133 * `Service[symbols.resolveConfig]`). The parent context is not affected.
134 *
135 * @param name — the service name whose config to intercept.
136 * @param config — the intercept config to merge for that service.
137 * @returns a child context carrying the additional intercept entry.
138 */
139 intercept<K extends InjectKey>(name: K, config: Context[K] extends { [symbols.config]: infer T } ? T : never): this
140 intercept(name: string, config: any): this
141 intercept(name: string, config: any) {
142 const intercept = Object.create(this[symbols.intercept])
143 intercept[name] = config
144 return this.extend({ [symbols.intercept]: intercept })
145 }
146}