Skip to content

Commit

Permalink
feat: support new provide
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Sep 17, 2024
1 parent d6aabd4 commit 1333ef0
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 36 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export namespace Context {
}

export interface Item<C extends Context> {
name: string
value?: any
source: C
}
Expand Down Expand Up @@ -64,7 +65,6 @@ export class Context {
static readonly events: unique symbol = symbols.events as any
static readonly static: unique symbol = symbols.static as any
static readonly filter: unique symbol = symbols.filter as any
static readonly expose: unique symbol = symbols.expose as any
static readonly isolate: unique symbol = symbols.isolate as any
static readonly internal: unique symbol = symbols.internal as any
static readonly intercept: unique symbol = symbols.intercept as any
Expand Down
31 changes: 21 additions & 10 deletions packages/core/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Lifecycle {
property: 'ctx',
})

defineProperty(this.on('internal/listener', function (this: Context, name, listener, options: EventOptions) {
ctx.scope.leak(this.on('internal/listener', function (this: Context, name, listener, options: EventOptions) {
const method = options.prepend ? 'unshift' : 'push'
if (name === 'ready') {
if (!this.lifecycle.isActive) return
Expand All @@ -69,18 +69,18 @@ class Lifecycle {
this.scope.runtime.forkables[method](listener as any)
return this.scope.collect('event <fork>', () => remove(this.scope.runtime.forkables, listener))
}
}), Context.static, ctx.scope)
}))

for (const level of ['info', 'error', 'warning']) {
defineProperty(this.on(`internal/${level}`, (format, ...param) => {
ctx.scope.leak(this.on(`internal/${level}`, (format, ...param) => {
if (this._hooks[`internal/${level}`].length > 1) return
// eslint-disable-next-line no-console
console.info(format, ...param)
}), Context.static, ctx.scope)
}))
}

// non-reusable plugin forks are not responsive to isolated service changes
defineProperty(this.on('internal/before-service', function (this: Context, name) {
ctx.scope.leak(this.on('internal/before-service', function (this: Context, name) {
for (const runtime of this.registry.values()) {
if (!runtime.inject[name]?.required) continue
const scopes = runtime.isReusable ? runtime.children : [runtime]
Expand All @@ -90,9 +90,9 @@ class Lifecycle {
scope.reset()
}
}
}, { global: true }), Context.static, ctx.scope)
}, { global: true }))

defineProperty(this.on('internal/service', function (this: Context, name) {
ctx.scope.leak(this.on('internal/service', function (this: Context, name) {
for (const runtime of this.registry.values()) {
if (!runtime.inject[name]?.required) continue
const scopes = runtime.isReusable ? runtime.children : [runtime]
Expand All @@ -101,7 +101,18 @@ class Lifecycle {
scope.start()
}
}
}, { global: true }), Context.static, ctx.scope)
}, { global: true }))

ctx.scope.leak(this.on('internal/status', function (scope: EffectScope) {
if (scope.status !== ScopeStatus.ACTIVE) return
for (const key of Reflect.ownKeys(ctx[symbols.store])) {
const item = ctx[symbols.store][key as symbol]
if (item.source.scope !== scope) continue
if (item.value) {
item.source.emit(item.source, 'internal/service', item.name, item.value)
}
}
}, { global: true }))

// inject in ancestor contexts
const checkInject = (scope: EffectScope, name: string) => {
Expand All @@ -112,9 +123,9 @@ class Lifecycle {
return checkInject(scope.parent.scope, name)
}

defineProperty(this.on('internal/inject', function (this: Context, name) {
ctx.scope.leak(this.on('internal/inject', function (this: Context, name) {
return checkInject(this.scope, name)
}, { global: true }), Context.static, ctx.scope)
}, { global: true }))
}

async flush() {
Expand Down
23 changes: 15 additions & 8 deletions packages/core/src/reflect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defineProperty, Dict, isNullable } from 'cosmokit'
import { Context } from './context'
import { getTraceable, isObject, isUnproxyable, symbols, withProps } from './utils'
import { ScopeStatus } from './scope'

declare module './context' {
interface Context {
Expand Down Expand Up @@ -105,12 +106,14 @@ class ReflectService {
this._mixin('lifecycle', ['on', 'once', 'parallel', 'emit', 'serial', 'bail', 'start', 'stop'])
}

get(name: string) {
get(name: string, strict = false) {
const internal = this.ctx[symbols.internal][name]
if (internal?.type !== 'service') return
const key = this.ctx[symbols.isolate][name]
const value = this.ctx[symbols.store][key]?.value
return getTraceable(this.ctx, value)
const item = this.ctx[symbols.store][key]
if (!item) return
if (strict && item.source.scope.status !== ScopeStatus.ACTIVE) return
return getTraceable(this.ctx, item.value)
}

set(name: string, value: any) {
Expand Down Expand Up @@ -141,9 +144,13 @@ class ReflectService {
return ctx[symbols.isolate][name] === ctx2[symbols.isolate][name]
}

ctx.emit(self, 'internal/before-service', name, value)
ctx[symbols.store][key] = { value, source: ctx }
ctx.emit(self, 'internal/service', name, oldValue)
if (ctx.scope.status === ScopeStatus.ACTIVE) {
ctx.emit(self, 'internal/before-service', name, value)
}
ctx[symbols.store][key] = { name, value, source: self }
if (ctx.scope.status === ScopeStatus.ACTIVE) {
ctx.emit(self, 'internal/service', name, oldValue)
}
return dispose
}

Expand All @@ -154,7 +161,7 @@ class ReflectService {
internal[name] = { type: 'service', builtin }
this.ctx.root[symbols.isolate][name] = key
if (!isObject(value)) return
this.ctx[symbols.store][key] = { value, source: null! }
this.ctx[symbols.store][key] = { name, value, source: null! }
defineProperty(value, symbols.tracker, {
associate: name,
property: 'ctx',
Expand All @@ -165,7 +172,7 @@ class ReflectService {
const internal = this.ctx.root[symbols.internal]
if (name in internal) return () => {}
internal[name] = { type: 'accessor', ...options }
return () => delete this.ctx.root[symbols.isolate][name]
return () => delete internal[name]
}

accessor(name: string, options: Omit<Context.Internal.Accessor, 'type'>) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export namespace Plugin {
reusable?: boolean
Config?: (config: any) => T
inject?: Inject
provide?: string | string[]
intercept?: Dict<boolean>
}

Expand Down
17 changes: 9 additions & 8 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ export abstract class EffectScope<C extends Context = Context> {
}

ensure(callback: () => Promise<void>) {
const task = callback()
const task = Promise.resolve()
.then(callback)
.catch((reason) => {
this.context.emit(this.ctx, 'internal/error', reason)
this.cancel(reason)
Expand All @@ -168,10 +169,14 @@ export abstract class EffectScope<C extends Context = Context> {

get ready() {
return Object.entries(this.runtime.inject).every(([name, inject]) => {
return !inject.required || !isNullable(this.ctx.get(name))
return !inject.required || !isNullable(this.ctx.reflect.get(name, true))
})
}

leak<T>(disposable: T) {
return defineProperty(disposable, Context.static, this)
}

reset() {
this.isActive = false
this.disposables = this.disposables.splice(0).filter((dispose) => {
Expand Down Expand Up @@ -259,7 +264,7 @@ export class ForkScope<C extends Context = Context> extends EffectScope<C> {
constructor(parent: Context, public runtime: MainScope<C>, config: C['config'], error?: any) {
super(parent as C, config)

this.dispose = defineProperty(parent.scope.collect(`fork <${parent.runtime.name}>`, () => {
this.dispose = runtime.leak(parent.scope.collect(`fork <${parent.runtime.name}>`, () => {
this.uid = null
this.reset()
this.context.emit('internal/fork', this)
Expand All @@ -268,7 +273,7 @@ export class ForkScope<C extends Context = Context> extends EffectScope<C> {
parent.registry.delete(runtime.plugin)
}
return result
}), Context.static, runtime)
}))

runtime.children.push(this)
runtime.disposables.push(this.dispose)
Expand Down Expand Up @@ -363,10 +368,6 @@ export class MainScope<C extends Context = Context> extends EffectScope<C> {
} else if (isConstructor(this.plugin)) {
// eslint-disable-next-line new-cap
const instance = new this.plugin(context, config)
const name = instance[Context.expose]
if (name) {
context.set(name, instance)
}
if (instance['fork']) {
this.forkables.push(instance['fork'].bind(instance))
}
Expand Down
10 changes: 3 additions & 7 deletions packages/core/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export abstract class Service<C extends Context = Context> {
static readonly invoke: unique symbol = symbols.invoke as any
static readonly extend: unique symbol = symbols.extend as any
static readonly tracker: unique symbol = symbols.tracker as any
static readonly provide: unique symbol = symbols.provide as any
static readonly immediate: unique symbol = symbols.immediate as any
static readonly provide = 'provide' as any

protected start(): Awaitable<void> {}
protected stop(): Awaitable<void> {}
Expand All @@ -28,21 +28,17 @@ export abstract class Service<C extends Context = Context> {
}
self.ctx = ctx
self.name = name
self.config = config
// self.config = config
defineProperty(self, symbols.tracker, tracker)

self.ctx.provide(name)
self.ctx.runtime.name = name
if (immediate) {
if (_ctx) self[symbols.expose] = name
else self.ctx.set(name, self)
}
self.ctx.set(name, self)

self.ctx.on('ready', async () => {
// await until next tick because derived class has not been initialized yet
await Promise.resolve()
await self.start()
if (!immediate) self.ctx.set(name!, self)
})

self.ctx.on('dispose', () => self.stop())
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const symbols = {
events: Symbol.for('cordis.events') as typeof Context.events,
static: Symbol.for('cordis.static') as typeof Context.static,
filter: Symbol.for('cordis.filter') as typeof Context.filter,
expose: Symbol.for('cordis.expose') as typeof Context.expose,
isolate: Symbol.for('cordis.isolate') as typeof Context.isolate,
internal: Symbol.for('cordis.internal') as typeof Context.internal,
intercept: Symbol.for('cordis.intercept') as typeof Context.intercept,
Expand All @@ -26,7 +25,6 @@ export const symbols = {
invoke: Symbol.for('cordis.invoke') as typeof Service.invoke,
extend: Symbol.for('cordis.extend') as typeof Service.extend,
tracker: Symbol.for('cordis.tracker') as typeof Service.tracker,
provide: Symbol.for('cordis.provide') as typeof Service.provide,
immediate: Symbol.for('cordis.immediate') as typeof Service.immediate,
}

Expand Down

0 comments on commit 1333ef0

Please sign in to comment.