Skip to content

Commit

Permalink
feat(cordis): support thisArg in mixin accessors
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jun 11, 2024
1 parent 0956cb2 commit 49f0cf8
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 16 deletions.
21 changes: 9 additions & 12 deletions packages/core/src/reflect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineProperty, Dict, isNullable } from 'cosmokit'
import { Context } from './context'
import { getTraceable, isObject, isUnproxyable, symbols } from './utils'
import { createMixin, getTraceable, isObject, isUnproxyable, symbols } from './utils'

declare module './context' {
interface Context {
Expand Down Expand Up @@ -56,7 +56,7 @@ export default class ReflectService {
ReflectService.checkInject(ctx, name)
return Reflect.get(target, name, ctx)
} else if (internal.type === 'accessor') {
return internal.get.call(ctx, ctx[symbols.target])
return internal.get.call(ctx, ctx[symbols.receiver])
} else {
if (!internal.builtin) ReflectService.checkInject(ctx, name)
return ctx.reflect.get(name)
Expand All @@ -73,7 +73,7 @@ export default class ReflectService {
}
if (internal.type === 'accessor') {
if (!internal.set) return false
return internal.set.call(ctx, value, ctx[symbols.target])
return internal.set.call(ctx, value, ctx[symbols.receiver])
} else {
// ctx.emit('internal/warning', new Error(`assigning to service ${name} is not recommended, please use \`ctx.set()\` method instead`))
ctx.reflect.set(name, value)
Expand Down Expand Up @@ -168,18 +168,15 @@ export default class ReflectService {
get(receiver) {
const service = getTarget(this)
if (isNullable(service)) return service
const mixed = receiver && new Proxy(receiver, {
get: (target, prop, receiver) => {
if (prop in service) return Reflect.get(service, prop, receiver)
return Reflect.get(target, prop, receiver)
},
})
const value = Reflect.get(service, key, mixed)
const mixin = createMixin(service, receiver)
const value = Reflect.get(service, key, mixin)
if (typeof value !== 'function') return value
return value.bind(mixed ?? service)
return value.bind(mixin ?? service)
},
set(value, receiver) {
return Reflect.set(getTarget(this), key, value, receiver)
const service = getTarget(this)
const mixin = createMixin(service, receiver)
return Reflect.set(service, key, value, mixin)
},
})
}
Expand Down
15 changes: 12 additions & 3 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface Tracker {
export const symbols = {
// internal symbols
shadow: Symbol.for('cordis.shadow'),
target: Symbol.for('cordis.target'),
receiver: Symbol.for('cordis.receiver'),

// context symbols
source: Symbol.for('cordis.source') as typeof Context.source,
Expand Down Expand Up @@ -129,7 +129,7 @@ function createTraceable(ctx: Context, value: any, tracker: Tracker, noTrap?: bo
if (tracker.associate && ctx[symbols.internal][`${tracker.associate}.${prop}`]) {
return Reflect.get(ctx, `${tracker.associate}.${prop}`, new Proxy(ctx, {
get: (target2, prop2, receiver2) => {
if (prop2 === symbols.target) return receiver
if (prop2 === symbols.receiver) return receiver
return Reflect.get(target2, prop2, receiver2)
},
}))
Expand All @@ -152,7 +152,7 @@ function createTraceable(ctx: Context, value: any, tracker: Tracker, noTrap?: bo
if (tracker.associate && ctx[symbols.internal][`${tracker.associate}.${prop}`]) {
return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, new Proxy(ctx, {
get: (target2, prop2, receiver2) => {
if (prop2 === symbols.target) return receiver
if (prop2 === symbols.receiver) return receiver
return Reflect.get(target2, prop2, receiver2)
},
}))
Expand All @@ -179,3 +179,12 @@ export function createCallable(name: string, proto: {}, tracker: Tracker) {
defineProperty(self, 'name', name)
return Object.setPrototypeOf(self, proto)
}

export function createMixin(service: {}, receiver: any) {
return receiver ? new Proxy(receiver, {
get: (target, prop, receiver) => {
if (prop in service) return Reflect.get(service, prop, receiver)
return Reflect.get(target, prop, receiver)
},
}) : service
}
62 changes: 61 additions & 1 deletion packages/core/tests/associate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('Association', () => {
expect(root.foo.baz()).to.be.instanceof(Foo)
})

it('associated type', async () => {
it('associated type - service injection', async () => {
class Session {
[Service.tracker] = {
property: 'ctx',
Expand Down Expand Up @@ -111,4 +111,64 @@ describe('Association', () => {

await checkError(root)
})

it('associated type - accessor injection', async () => {
class Session {
[Service.tracker] = {
property: 'ctx',
associate: 'session',
}

constructor(public ctx: Context) {}
}

class Foo extends Service {
constructor(ctx: Context) {
super(ctx, 'foo', true)
}

session() {
return new Session(this.ctx)
}
}

interface Session {
bar: number
}

interface Bar extends Session {
secret: number
}

class Bar {
constructor(ctx: Context) {
ctx.mixin(this, {
bar: 'session.bar',
})
}

get bar() {
return this.secret
}

set bar(value: number) {
this.secret = value + 1
}
}

const root = new Context()
root.plugin(Foo)
root.plugin(Bar)

root.inject(['foo'], (ctx) => {
const session = ctx.foo.session()
expect(session).to.be.instanceof(Session)
expect(session.bar).to.be.undefined

session.bar = 100
expect(session.bar).to.equal(101)
})

await checkError(root)
})
})

0 comments on commit 49f0cf8

Please sign in to comment.