Skip to content

Commit

Permalink
feat(core): support instanceof for functional services
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Feb 15, 2024
1 parent 4fbf8bf commit cab9ce1
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 25 deletions.
50 changes: 25 additions & 25 deletions packages/core/src/service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Awaitable, defineProperty } from 'cosmokit'
import { Context } from './context.ts'

const kSetup = Symbol.for('cordis.service.setup')

export namespace Service {
export interface Options {
name?: string
Expand All @@ -14,10 +12,7 @@ export namespace Service {
function makeFunctional(proto: {}) {
if (proto === Object.prototype) return Function.prototype
const result = Object.create(makeFunctional(Object.getPrototypeOf(proto)))
for (const key of Object.getOwnPropertyNames(proto)) {
Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(proto, key)!)
}
for (const key of Object.getOwnPropertySymbols(proto)) {
for (const key of Reflect.ownKeys(proto)) {
Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(proto, key)!)
}
return result
Expand All @@ -35,43 +30,48 @@ export abstract class Service<C extends Context = Context> {
protected [Context.current]!: C

constructor(ctx: C | undefined, public readonly name: string, options?: boolean | Service.Options) {
let self: any = this
let self = this
if (self[Context.invoke]) {
// functional service
self = function (...args: any[]) {
const proxy = Context.createProxy(ctx, self)
return Context.applyProxy(proxy, self, this, args)
}
} as any
defineProperty(self, 'name', name)
Object.setPrototypeOf(self, makeFunctional(Object.getPrototypeOf(this)))
}
return self[kSetup](ctx, name, options)
}

[Context.filter](ctx: Context) {
return ctx[Context.shadow][this.name] === this.ctx[Context.shadow][this.name]
}

[kSetup](ctx: C | undefined, name: string, options?: boolean | Service.Options) {
this.ctx = ctx ?? new (this.constructor as any).Context()
this.ctx.provide(name)
defineProperty(this, Context.current, ctx)
self.ctx = ctx ?? new (self.constructor as any).Context()
self.ctx.provide(name)
defineProperty(self, Context.current, ctx)

const resolved = typeof options === 'boolean' ? { immediate: options } : options ?? {}
if (!resolved.standalone && resolved.immediate) {
if (ctx) this[Context.expose] = name
else this.ctx[name] = this
if (ctx) self[Context.expose] = name
else self.ctx[name] = self
}

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

this.ctx.on('dispose', () => this.stop())
self.ctx.on('dispose', () => self.stop())
return Context.associate(self, name)
}

return Context.associate(this, name)
[Context.filter](ctx: Context) {
return ctx[Context.shadow][this.name] === this.ctx[Context.shadow][this.name]
}

static [Symbol.hasInstance](instance: any) {
let constructor = instance.constructor
while (constructor) {
if (constructor === this) return true
constructor = Object.getPrototypeOf(constructor)
}
return false
}
}
3 changes: 3 additions & 0 deletions packages/core/tests/service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,14 @@ describe('Service', () => {
const ctx1 = root.intercept('foo', { b: 2 })
expect(ctx1.foo()).to.deep.equal({ a: 1, b: 2 })
const foo1 = ctx1.foo
expect(foo1).to.be.instanceof(Foo)

// create extension
const foo2 = root.foo.extend({ c: 3 })
expect(foo2).to.be.instanceof(Foo)
expect(foo2()).to.deep.equal({ a: 1, c: 3 })
const foo3 = foo1.extend({ d: 4 })
expect(foo3).to.be.instanceof(Foo)
expect(foo3.reflect()).to.deep.equal({ a: 1, b: 2, d: 4 })

// context tracibility
Expand Down

0 comments on commit cab9ce1

Please sign in to comment.