返回源码地图

vendor/cordis/src/service.ts

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

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

1import { defineProperty } from '@deepseek-ai/cosmokit'
2import { Context } from './context.ts'
3import { createCallable, joinPrototype, symbols, type Tracker } from './utils.ts'
4
5/**
6 * Base class for services that expose a named API on `ctx`.
7 *
8 * Subclasses call `super(ctx, name)` from their constructor. The service is
9 * registered immediately and is automatically removed with the owning fiber.
10 */
11export abstract class Service<out T = never> {
12 /** Symbol key of an instance method run after construction (class plugins). */
13 static readonly init: unique symbol = symbols.init
14 /** Symbol key of the availability predicate passed to `ctx.provide()`. */
15 static readonly check: unique symbol = symbols.check
16 /** Symbol key of the phantom intercept-config type parameter. */
17 static readonly config: unique symbol = symbols.config
18 /** Symbol key of the call body making a service callable (e.g. `ctx.logger()`). */
19 static readonly invoke: unique symbol = symbols.invoke
20 /** Symbol key of the helper deriving an extended service instance. */
21 static readonly extend: unique symbol = symbols.extend
22 /** Symbol key of the tracker metadata used for context tracing. */
23 static readonly tracker: unique symbol = symbols.tracker
24 /** Symbol key of the intercept-config resolution helper below. */
25 static readonly resolveConfig: unique symbol = symbols.resolveConfig
26
27 declare [symbols.config]: T
28
29 /** The service name this instance is registered under. */
30 public name!: string
31
32 /**
33 * Register this instance as `name` in the current context.
34 *
35 * Calls `ctx.reflect.provide(name, this, this[Service.check])`, so the
36 * service is unregistered automatically when the owning fiber unloads.
37 * Services with a `[Service.invoke]` body return a callable instance.
38 *
39 * @param ctx — the context to register in (stored as `this.ctx`).
40 * @param name — the service name; defaults to the static `provide` field.
41 */
42 constructor(protected ctx: Context, name: string) {
43 name ??= this.constructor['provide'] as string
44
45 let self = this
46 const tracker: Tracker = {
47 associate: name,
48 property: 'ctx',
49 }
50 if (self[symbols.invoke]) {
51 self = createCallable(name, joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker)
52 }
53 self.ctx = ctx
54 self.name = name
55 defineProperty(self, symbols.tracker, tracker)
56
57 self.ctx.reflect.provide(name, self, this[symbols.check])
58 return self
59 }
60
61 protected [symbols.filter](ctx: Context) {
62 return ctx[symbols.isolate][this.name] === this.ctx[symbols.isolate][this.name]
63 }
64
65 protected [symbols.extend](props?: any) {
66 let self: any
67 if (this[Service.invoke]) {
68 self = createCallable(this.name, this, this[symbols.tracker])
69 } else {
70 self = Object.create(this)
71 }
72 return Object.assign(self, props)
73 }
74
75 /**
76 * Merge intercept config from ancestors with optional base and head values.
77 *
78 * Entries added closer to the root apply first; `base` is prepended and
79 * `head` appended. Uses `Config.merge` when the service declares one,
80 * otherwise a shallow `Object.assign`.
81 *
82 * @param base — lowest-precedence config merged before all intercepts.
83 * @param head — highest-precedence config merged after all intercepts.
84 * @returns the merged config.
85 */
86 [symbols.resolveConfig](base?: T, head?: T): T {
87 let intercept = this.ctx[Context.intercept]
88 const configs: any[] = []
89 while (this.name in intercept) {
90 if (Object.hasOwn(intercept, this.name)) {
91 configs.unshift(intercept[this.name])
92 }
93 intercept = Object.getPrototypeOf(intercept)
94 }
95 if (base) configs.unshift(base)
96 if (head) configs.push(head)
97 if (this['Config']?.merge) {
98 return this['Config'].merge(...configs)
99 } else {
100 return Object.assign({}, ...configs)
101 }
102 }
103
104 static [Symbol.hasInstance](instance: any) {
105 if (!instance) return false
106 let constructor = instance.constructor
107 while (constructor) {
108 // constructor may be a proxy
109 constructor = constructor.prototype?.constructor
110 if (constructor === this) return true
111 constructor &&= Object.getPrototypeOf(constructor)
112 }
113 return false
114 }
115}