Skip to content

Commit b78fd8e

Browse files
committed
feat(cordis): support custom inject checker, check entry injections
1 parent 717b11d commit b78fd8e

File tree

6 files changed

+101
-31
lines changed

6 files changed

+101
-31
lines changed

packages/core/src/context.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export class Context {
5555
static readonly static: unique symbol = symbols.static as any
5656
static readonly filter: unique symbol = symbols.filter as any
5757
static readonly expose: unique symbol = symbols.expose as any
58-
static readonly inject: unique symbol = symbols.inject as any
5958
static readonly isolate: unique symbol = symbols.isolate as any
6059
static readonly internal: unique symbol = symbols.internal as any
6160
static readonly intercept: unique symbol = symbols.intercept as any
@@ -104,14 +103,8 @@ export class Context {
104103
if (name[0] === '$' || name[0] === '_') return
105104
// Case 4: access directly from root
106105
if (!ctx.runtime.plugin) return
107-
// Case 5: inject in ancestor contexts
108-
let parent = ctx
109-
while (parent.runtime.plugin) {
110-
for (const key of parent.runtime.inject) {
111-
if (name === Context.resolveInject(parent, key)[0]) return
112-
}
113-
parent = parent.scope.parent
114-
}
106+
// Case 5: custom inject checks
107+
if (ctx.bail('internal/inject', name)) return
115108
ctx.emit('internal/warning', new Error(`property ${name} is not registered, declare it as \`inject\` to suppress this warning`))
116109
}
117110

@@ -164,7 +157,6 @@ export class Context {
164157
constructor(config?: any) {
165158
const self: Context = new Proxy(this, Context.handler)
166159
config = resolveConfig(this.constructor, config)
167-
self[symbols.inject] = {}
168160
self[symbols.isolate] = Object.create(null)
169161
self[symbols.intercept] = Object.create(null)
170162
self.root = self

packages/core/src/events.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ export class Lifecycle {
9595
}
9696
}
9797
}, { global: true }), Context.static, root.scope)
98+
99+
// inject in ancestor contexts
100+
defineProperty(this.on('internal/inject', function (name) {
101+
let parent = this
102+
while (parent.runtime.plugin) {
103+
for (const key of parent.runtime.inject) {
104+
if (name === Context.resolveInject(parent, key)[0]) return true
105+
}
106+
parent = parent.scope.parent
107+
}
108+
}, { global: true }), Context.static, root.scope)
98109
}
99110

100111
async flush() {
@@ -224,6 +235,7 @@ export interface Events<in C extends Context = Context> {
224235
'internal/service'(this: C, name: string, value: any): void
225236
'internal/before-update'(fork: ForkScope<C>, config: any): void
226237
'internal/update'(fork: ForkScope<C>, oldConfig: any): void
238+
'internal/inject'(this: C, name: string): boolean | undefined
227239
'internal/listener'(this: C, name: string, listener: any, prepend: boolean): void
228240
'internal/event'(type: 'emit' | 'parallel' | 'serial' | 'bail', name: string, args: any[], thisArg: any): void
229241
}

packages/core/src/scope.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export abstract class EffectScope<C extends Context = Context> {
7777
constructor(public parent: C, public config: C['config']) {
7878
this.uid = parent.registry ? parent.registry.counter : 0
7979
this.ctx = this.context = parent.extend({ scope: this })
80-
this.ctx[Context.inject] = {}
8180
this.proxy = new Proxy({}, {
8281
get: (target, key) => Reflect.get(this.config, key),
8382
})
@@ -360,7 +359,6 @@ export class MainScope<C extends Context = Context> extends EffectScope<C> {
360359
if (name && name !== 'apply') this.name = name
361360
this.schema = this.plugin['Config'] || this.plugin['schema']
362361
this.setInject(this.plugin['using'] || this.plugin['inject'])
363-
this.setInject(this.parent[Context.inject])
364362
this.isReusable = this.plugin['reusable']
365363
this.isReactive = this.plugin['reactive']
366364
this.context.emit('internal/runtime', this)

packages/core/src/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export const symbols = {
88
static: Symbol.for('cordis.static') as typeof Context.static,
99
filter: Symbol.for('cordis.filter') as typeof Context.filter,
1010
expose: Symbol.for('cordis.expose') as typeof Context.expose,
11-
inject: Symbol.for('cordis.inject') as typeof Context.inject,
1211
isolate: Symbol.for('cordis.isolate') as typeof Context.isolate,
1312
internal: Symbol.for('cordis.internal') as typeof Context.internal,
1413
intercept: Symbol.for('cordis.intercept') as typeof Context.intercept,

packages/loader/src/entry.ts

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ export class Entry {
6161
}
6262

6363
patch(ctx: Context, ref: Context = ctx) {
64-
ctx[Context.inject] = this.options.inject
65-
6664
// part 1: prepare isolate map
6765
const newMap: Dict<symbol> = Object.create(Object.getPrototypeOf(ref[Context.isolate]))
6866
for (const [key, label] of Object.entries(this.options.isolate ?? {})) {
@@ -144,11 +142,51 @@ export class Entry {
144142
})
145143
}
146144

145+
get requiredInjects() {
146+
return Array.isArray(this.options.inject)
147+
? this.options.inject
148+
: this.options.inject?.required ?? []
149+
}
150+
151+
get optionalInjects() {
152+
return Array.isArray(this.options.inject)
153+
? this.options.inject
154+
: [
155+
...this.options.inject?.required ?? [],
156+
...this.options.inject?.optional ?? [],
157+
]
158+
}
159+
160+
_check() {
161+
if (!this.loader.isTruthyLike(this.options.when)) return false
162+
if (this.options.disabled) return false
163+
for (const name of this.requiredInjects) {
164+
let key = this.parent.ctx[Context.isolate][name]
165+
const label = this.options.isolate?.[name]
166+
if (label) {
167+
const realm = this.resolveRealm(label)
168+
key = (this.loader.realms[realm] ?? Object.create(null))[name] ?? Symbol(`${name}${realm}`)
169+
}
170+
if (!key || isNullable(this.parent.ctx[key])) return false
171+
}
172+
return true
173+
}
174+
175+
async checkService(name: string) {
176+
if (!this.requiredInjects.includes(name)) return
177+
const ready = this._check()
178+
if (ready && !this.fork) {
179+
await this.start()
180+
} else if (!ready && this.fork) {
181+
await this.stop()
182+
}
183+
}
184+
147185
async update(options: Entry.Options) {
148186
const legacy = this.options
149187
this.options = sortKeys(options)
150-
if (!this.loader.isTruthyLike(options.when) || options.disabled) {
151-
this.stop()
188+
if (!this._check()) {
189+
await this.stop()
152190
} else if (this.fork) {
153191
this.suspend = true
154192
for (const [key, label] of Object.entries(legacy.isolate ?? {})) {
@@ -158,21 +196,25 @@ export class Entry {
158196
}
159197
this.patch(this.fork.parent)
160198
} else {
161-
const ctx = this.createContext()
162-
const exports = await this.loader.import(this.options.name).catch((error: any) => {
163-
ctx.emit('internal/error', new Error(`Cannot find package "${this.options.name}"`))
164-
ctx.emit('internal/error', error)
165-
})
166-
if (!exports) return
167-
const plugin = this.loader.unwrapExports(exports)
168-
this.patch(ctx)
169-
ctx[Entry.key] = this
170-
this.fork = ctx.plugin(plugin, this.options.config)
171-
ctx.emit('loader/entry', 'apply', this)
199+
await this.start()
172200
}
173201
}
174202

175-
stop() {
203+
async start() {
204+
const ctx = this.createContext()
205+
const exports = await this.loader.import(this.options.name, this.parent.url).catch((error: any) => {
206+
ctx.emit('internal/error', new Error(`Cannot find package "${this.options.name}"`))
207+
ctx.emit('internal/error', error)
208+
})
209+
if (!exports) return
210+
const plugin = this.loader.unwrapExports(exports)
211+
this.patch(ctx)
212+
ctx[Entry.key] = this
213+
this.fork = ctx.plugin(plugin, this.options.config)
214+
ctx.emit('loader/entry', 'apply', this)
215+
}
216+
217+
async stop() {
176218
this.fork?.dispose()
177219
this.fork = undefined
178220

packages/loader/src/shared.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export abstract class Loader extends BaseLoader {
6464

6565
constructor(public ctx: Context, public config: Loader.Config) {
6666
super(ctx)
67+
68+
const self = this
6769
this.ctx.set('loader', this)
6870
this.realms['#'] = ctx.root[Context.isolate]
6971

@@ -98,6 +100,31 @@ export abstract class Loader extends BaseLoader {
98100
fork.entry.stop()
99101
fork.entry.parent.write()
100102
})
103+
104+
this.ctx.on('internal/before-service', function (name) {
105+
for (const entry of Object.values(self.entries)) {
106+
entry.checkService(name)
107+
}
108+
}, { global: true })
109+
110+
this.ctx.on('internal/service', function (name) {
111+
for (const entry of Object.values(self.entries)) {
112+
entry.checkService(name)
113+
}
114+
}, { global: true })
115+
116+
const checkInject = (scope: EffectScope, name: string) => {
117+
if (!scope.runtime.plugin) return false
118+
if (scope.runtime === scope) {
119+
return scope.runtime.children.every(fork => checkInject(fork, name))
120+
}
121+
if (scope.entry?.optionalInjects.includes(name)) return true
122+
return checkInject(scope.parent.scope, name)
123+
}
124+
125+
this.ctx.on('internal/inject', function (this, name) {
126+
return checkInject(this.scope, name)
127+
})
101128
}
102129

103130
async start() {
@@ -188,11 +215,11 @@ export abstract class Loader extends BaseLoader {
188215

189216
_locate(scope: EffectScope): Entry[] {
190217
// root scope
191-
if (scope === scope.parent.scope) return []
218+
if (!scope.runtime.plugin) return []
192219

193220
// runtime scope
194221
if (scope.runtime === scope) {
195-
return ([] as Entry[]).concat(...scope.runtime.children.map(child => this._locate(child)))
222+
return scope.runtime.children.flatMap(child => this._locate(child))
196223
}
197224

198225
if (scope.entry) return [scope.entry]

0 commit comments

Comments
 (0)