Skip to content

Commit

Permalink
feat(cordis): service method will no longer trace inject from plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jun 6, 2024
1 parent e0a0eed commit c9be0aa
Show file tree
Hide file tree
Showing 12 changed files with 72 additions and 57 deletions.
5 changes: 3 additions & 2 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ export class Context {
// Case 4: access directly from root
if (!ctx.runtime.plugin) return
// Case 5: custom inject checks
if (ctx.bail('internal/inject', name)) return
ctx.emit(ctx, 'internal/warning', new Error(`property ${name} is not registered, declare it as \`inject\` to suppress this warning`))
if (ctx.bail(ctx, 'internal/inject', name)) return
const warning = new Error(`property ${name} is not registered, declare it as \`inject\` to suppress this warning`)
ctx.emit(ctx, 'internal/warning', warning)
}

const [name, internal] = Context.resolveInject(ctx, prop)
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,15 @@ export class Lifecycle {
}, { global: true }), Context.static, ctx.scope)

// inject in ancestor contexts
defineProperty(this.on('internal/inject', function (name) {
let parent = this
while (parent.runtime.plugin) {
for (const key of parent.runtime.inject) {
if (name === Context.resolveInject(parent, key)[0]) return true
defineProperty(this.on('internal/inject', function (this: Context, name) {
let ctx = this
while (ctx !== ctx.root) {
if (Reflect.ownKeys(ctx).includes('scope')) {
for (const key of ctx.runtime.inject) {
if (name === Context.resolveInject(ctx, key)[0]) return true
}
}
parent = parent.scope.parent
ctx = ctx[symbols.trace] ?? Object.getPrototypeOf(ctx)
}
}, { global: true }), Context.static, ctx.scope)
}
Expand Down
27 changes: 17 additions & 10 deletions packages/core/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@ declare module './context.ts' {
export class Registry<C extends Context = Context> {
private _counter = 0
private _internal = new Map<Function, MainScope<C>>()
protected context: Context

constructor(private ctx: Context, config: any) {
constructor(public ctx: C, config: any) {
defineProperty(this, Context.origin, ctx)
ctx.scope = new MainScope(this, null!, config)
ctx.scope.runtime.isReactive = true
this.context = ctx
const runtime = new MainScope(ctx, null!, config)
ctx.scope = runtime
runtime.ctx = ctx
this.set(null!, runtime)
}

get counter() {
Expand Down Expand Up @@ -139,15 +143,17 @@ export class Registry<C extends Context = Context> {
// check if it's a valid plugin
this.resolve(plugin, true)

const context: Context = this.ctx
context.scope.assertActive()
// magic: this.ctx[symbols.trace] === this
// Here we ignore the reference
const ctx: C = Object.getPrototypeOf(this.ctx)
ctx.scope.assertActive()

// resolve plugin config
let error: any
try {
config = resolveConfig(plugin, config)
} catch (reason) {
context.emit('internal/error', reason)
this.context.emit(ctx, 'internal/error', reason)
error = reason
config = null
}
Expand All @@ -156,12 +162,13 @@ export class Registry<C extends Context = Context> {
let runtime = this.get(plugin)
if (runtime) {
if (!runtime.isForkable) {
context.emit('internal/warning', new Error(`duplicate plugin detected: ${plugin.name}`))
this.context.emit(ctx, 'internal/warning', new Error(`duplicate plugin detected: ${plugin.name}`))
}
return runtime.fork(context, config, error)
return runtime.fork(ctx, config, error)
}

runtime = new MainScope(this, plugin, config, error)
return runtime.fork(context, config, error)
runtime = new MainScope(ctx, plugin, config, error)
this.set(plugin, runtime)
return runtime.fork(ctx, config, error)
}
}
9 changes: 4 additions & 5 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { deepEqual, defineProperty, isNullable, remove } from 'cosmokit'
import { Context } from './context.ts'
import { Inject, Plugin, Registry } from './registry.ts'
import { Inject, Plugin } from './registry.ts'
import { isConstructor, resolveConfig } from './utils.ts'

declare module './context.ts' {
Expand Down Expand Up @@ -167,7 +167,7 @@ export abstract class EffectScope<C extends Context = Context> {
}

get ready() {
return this.runtime.using.every(name => !isNullable(this.ctx[name]))
return this.runtime.using.every(name => !isNullable(this.ctx.get(name)))
}

reset() {
Expand Down Expand Up @@ -310,9 +310,8 @@ export class MainScope<C extends Context = Context> extends EffectScope<C> {
isReusable?: boolean = false
isReactive?: boolean = false

constructor(registry: Registry<C>, public plugin: Plugin, config: any, error?: any) {
super(registry[Context.origin] as C, config)
registry.set(plugin, this)
constructor(ctx: C, public plugin: Plugin, config: any, error?: any) {
super(ctx, config)
if (!plugin) {
this.name = 'root'
this.isActive = true
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Spread } from './registry.ts'

export abstract class Service<T = unknown, C extends Context = Context> {
static readonly setup: unique symbol = symbols.setup as any
static readonly trace: unique symbol = symbols.trace as any
static readonly invoke: unique symbol = symbols.invoke as any
static readonly extend: unique symbol = symbols.extend as any
static readonly provide: unique symbol = symbols.provide as any
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const symbols = {

// service symbols
setup: Symbol.for('cordis.setup') as typeof Service.setup,
trace: Symbol.for('cordis.trace') as typeof Service.trace,
invoke: Symbol.for('cordis.invoke') as typeof Service.invoke,
extend: Symbol.for('cordis.extend') as typeof Service.extend,
provide: Symbol.for('cordis.provide') as typeof Service.provide,
Expand Down Expand Up @@ -57,7 +58,10 @@ export function joinPrototype(proto1: {}, proto2: {}) {
export function createTraceable(ctx: any, value: any) {
const proxy = new Proxy(value, {
get: (target, name, receiver) => {
if (name === symbols.origin || name === 'ctx') return ctx
if (name === symbols.origin || name === 'ctx') {
const origin = Reflect.getOwnPropertyDescriptor(target, symbols.origin)?.value
return ctx.extend({ [symbols.trace]: origin })
}
return Reflect.get(target, name, receiver)
},
apply: (target, thisArg, args) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/tests/invoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from 'chai'
import { Context, Service } from '../src'
import './utils'

describe('functional service', () => {
it('functional service', async () => {
Expand All @@ -14,7 +15,7 @@ describe('functional service', () => {
static [Service.immediate] = true

protected [Service.invoke](init?: Config) {
const caller = this[Context.origin]
const caller = this.ctx
expect(caller).to.be.instanceof(Context)
let result = { ...this.config }
let intercept = caller[Context.intercept]
Expand Down
26 changes: 13 additions & 13 deletions packages/core/tests/service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Context, Service } from '../src'
import { noop } from 'cosmokit'
import { expect } from 'chai'
import { mock } from 'node:test'
import { checkError, getHookSnapshot } from './utils'
import { checkError, Counter, getHookSnapshot } from './utils'

describe('Service', () => {
it('non-service access', async () => {
Expand Down Expand Up @@ -128,36 +128,36 @@ describe('Service', () => {
expect(callback.mock.calls).to.have.length(1)
})

it('traceable effect', async () => {
it('traceable effect (inject)', async () => {
class Foo extends Service {
size = 0
static inject = ['counter']

constructor(ctx: Context) {
super(ctx, 'foo', true)
}

increse() {
return this.ctx.effect(() => {
this.size++
return () => this.size--
})
count() {
this.ctx.counter.increse()
return this.ctx.counter.value
}
}

const root = new Context()
const warning = mock.fn()
root.on('internal/warning', warning)
root.set('counter', new Counter(root))

root.plugin(Foo)
root.foo.increse()
expect(root.foo.size).to.equal(1)
expect(root.foo.count()).to.equal(1)
expect(root.foo.count()).to.equal(2)

const fork = root.inject(['foo'], (ctx) => {
ctx.foo.increse()
expect(ctx.foo.size).to.equal(2)
expect(ctx.foo.count()).to.equal(3)
expect(ctx.foo.count()).to.equal(4)
})

fork.dispose()
expect(root.foo.size).to.equal(1)
expect(root.foo.count()).to.equal(3)
expect(warning.mock.calls).to.have.length(0)
})

Expand Down
17 changes: 0 additions & 17 deletions packages/core/tests/update.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,4 @@ describe('Update', () => {
expect(fork.disposables).to.have.length(1) // effect
expect(fork.runtime.disposables).to.have.length(1) // fork
})

it('root update', async () => {
const root = new Context()
const callback = mock.fn(noop)
const { length } = root.state.disposables

root.decline(['foo'])
root.on('dispose', callback)
expect(callback.mock.calls).to.have.length(0)

root.state.update({ maxListeners: 100 })
expect(callback.mock.calls).to.have.length(0)

root.state.update({ foo: 100 })
expect(callback.mock.calls).to.have.length(1)
expect(root.state.disposables.length).to.equal(length)
})
})
17 changes: 15 additions & 2 deletions packages/core/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ export class Filter {
export function filter(ctx: Context) {
ctx.root.filter = () => true
ctx.on('internal/runtime', (runtime) => {
// same as `!runtime.uid`, but to make coverage happy
if (!ctx.registry.has(runtime.plugin)) return
if (!runtime.uid) return
runtime.ctx.filter = (session) => {
return runtime.children.some((child) => {
return child.ctx.filter(session)
Expand All @@ -44,11 +43,25 @@ declare module '../src/events' {
}
}

export class Counter {
value = 0

constructor(public ctx: Context) {}

increse() {
return this.ctx.effect(() => {
this.value++
return () => this.value--
})
}
}

declare module '../src/context' {
interface Context {
foo: any
bar: any
baz: any
counter: Counter
filter(session: Session): boolean
}

Expand Down
2 changes: 2 additions & 0 deletions packages/loader/src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ declare module './entry.ts' {
}
}

export const name = 'inject'

export function apply(ctx: Context) {
function getRequired(entry?: Entry) {
return Array.isArray(entry?.options.inject)
Expand Down
2 changes: 2 additions & 0 deletions packages/loader/src/isolate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export class GlobalRealm extends Realm {
}
}

export const name = 'isolate'

export function apply(ctx: Context) {
const realms: Dict<GlobalRealm> = Object.create(null)

Expand Down

0 comments on commit c9be0aa

Please sign in to comment.