Skip to content

Commit

Permalink
before complete refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
eddow committed Nov 3, 2024
1 parent 4bcb641 commit 889385f
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 45 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,13 @@ For instance, one cannot use :
```ts
constructor() {
super{}
super()
instances.register(this);
}
```
> Note: Indeed, this should still just work fine in most case as `Diamond` modifies the temporary object for it to become a straight "forward" to the `Diamond` object (anyway doomed to be garbage collected if not used) - it wouldn't be the same reference but the same behavior with the same data.
### When we are writing the class
When constructing a class who inherit (directly or indirectly) a `Diamond`, the function `constructedObject` helps you retrieve the actual instance being constructed - this is the one you want to refer to.
Expand Down
37 changes: 16 additions & 21 deletions src/diamond.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Ctor, HasBases, Newable } from './types'
import { allFLegs, bottomLeg, fLegs, nextInFLeg, temporaryBuiltObjects } from './utils'
import { allFLegs, bottomLeg, fLegs, forwardProxyHandler, nextInFLeg } from './utils'

type BuildingStrategy = Map<Ctor, Ctor[]>
let buildingDiamond: {
Expand Down Expand Up @@ -44,6 +44,12 @@ export function hasInstanceManager<Class extends Ctor>(cls: Class) {
}
}

function forwardTempTo(target: any, temp: any) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(temp))
for (const p of Object.getOwnPropertyNames(temp)) delete temp[p]
Object.setPrototypeOf(temp, new Proxy(target, forwardProxyHandler))
}

export default function Diamond<TBases extends Ctor[]>(
...baseClasses: TBases
): Newable<HasBases<TBases>> {
Expand All @@ -70,38 +76,27 @@ export default function Diamond<TBases extends Ctor[]>(
const myResponsibility: Ctor[] = []
class Diamond {
constructor(...args: any[]) {
const itsMe = !buildingDiamond,
responsibility = itsMe
? myResponsibility
: buildingDiamond!.strategy.get(this.constructor as Ctor)!
if (itsMe)
const responsibility = buildingDiamond
? buildingDiamond!.strategy.get(this.constructor as Ctor)!
: myResponsibility
if (!buildingDiamond)
buildingDiamond = {
built: this,
strategy: buildingStrategy
} // It will be set to `null` on purpose in the process and needs to be restored
const locallyStoredDiamond = buildingDiamond!
const locallyStoredDiamond = buildingDiamond!.built
try {
// `super()`: Builds the temporary objects and import all their properties
for (const subs of responsibility) {
const temp = new (subs as any)(...args) // `any` because declared as an abstract class
Object.defineProperties(
locallyStoredDiamond.built,
Object.getOwnPropertyDescriptors(temp)
)
// Even if `Diamond` managed: property initializers do not go through proxy
if (locallyStoredDiamond !== temp) forwardTempTo(locallyStoredDiamond, temp)
}
} finally {
if (!itsMe) {
// Feels the same
Object.setPrototypeOf(this, Object.getPrototypeOf(locallyStoredDiamond.built))
Object.defineProperties(
this,
Object.getOwnPropertyDescriptors(locallyStoredDiamond.built)
)
// Is the same (through `constructedObject`)
temporaryBuiltObjects.set(this, locallyStoredDiamond.built)
}
if (locallyStoredDiamond !== this) forwardTempTo(locallyStoredDiamond, this)
buildingDiamond = null
}
return locallyStoredDiamond
}
static [Symbol.hasInstance] = hasInstanceManager(Diamond)
}
Expand Down
12 changes: 1 addition & 11 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import { Ctor } from './types'
import { fLegs, temporaryBuiltObjects } from './utils'

/**
* As constructors build temporary objects, this function returns a reference to the real object being constructed.
* In other cases, it just returns the object itself
* @param obj `this` - the object being constructed by this constructor
* @returns
*/
export function constructedObject<Class extends object>(obj: Class): Class {
return (temporaryBuiltObjects.get(obj) as Class) || obj
}
import { fLegs } from './utils'

/**
* `instanceof` substitute for diamonds
Expand Down
12 changes: 5 additions & 7 deletions src/seclude.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Diamond, { diamondHandler, hasInstanceManager } from './diamond'
import { constructedObject } from './helpers'
import { Ctor, KeySet, Newable } from './types'
import { allFLegs, bottomLeg, fLegs, nextInLine, secludedPropertyDescriptor } from './utils'

Expand Down Expand Up @@ -34,7 +33,7 @@ export function Seclude<TBase extends Ctor, Keys extends (keyof InstanceType<TBa
/**
* In order to integrate well in diamonds, we need to be a diamond
* When we create a diamond between the Secluded and the base, the private properties of the base *have to*
* be collected before the diamond propagate them to the `constructedObject`
* be collected before the diamond propagate them to the constructed object
*/
abstract class PropertyCollector extends base {
constructor(...args: any[]) {
Expand Down Expand Up @@ -72,10 +71,9 @@ export function Seclude<TBase extends Ctor, Keys extends (keyof InstanceType<TBa
} finally {
initPropertiesBasket.shift()
}
const actThis = constructedObject(this),
// This proxy is used to write public properties in the prototype (the public object) and give
const // This proxy is used to write public properties in the prototype (the public object) and give
// access to the private instance methods. It's the one between `Secluded` and the main object
protoProxy = new Proxy(actThis, {
protoProxy = new Proxy(this, {
get(target, p, receiver) {
if (p in base.prototype) {
const pd = nextInLine(base, p)
Expand All @@ -99,7 +97,7 @@ export function Seclude<TBase extends Ctor, Keys extends (keyof InstanceType<TBa
`init.initialObject` is the instance of the secluded class who contains all its public properties
`init.privateProperties` is a pure object containing all its private properties
We need the initial object but with only the private properties
1- we remove all the public ones (they have been transferred to `constructedObject`)
1- we remove all the public ones
2- we restore the private ones
*/
// Except when we're the main class, or when the secluded is a diamond, then we create a new object
Expand All @@ -114,7 +112,7 @@ export function Seclude<TBase extends Ctor, Keys extends (keyof InstanceType<TBa
Object.defineProperties(secluded, init.privateProperties)
Object.setPrototypeOf(secluded, protoProxy)
}
privates.set(actThis, secluded)
privates.set(this, secluded)
}
}
function whoAmI(receiver: TBase) {
Expand Down
37 changes: 35 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,38 @@ export function nextInFLeg(ctor: Ctor, name: PropertyKey, diamond: Ctor) {
return rv
}

export const temporaryBuiltObjects = new WeakMap<object, object>(),
allFLegs = new WeakMap<Ctor, Ctor[]>()
export const allFLegs = new WeakMap<Ctor, Ctor[]>()

// Deflect all actions so they they apply to `target` instead of `receiver`
export const forwardProxyHandler: ProxyHandler<Ctor> = {
get(target, p) {
return Reflect.get(target, p)
},
set(target, p, v) {
return Reflect.set(target, p, v)
},
getOwnPropertyDescriptor(target, p) {
return Reflect.getOwnPropertyDescriptor(target, p)
},
getPrototypeOf(target) {
return Reflect.getPrototypeOf(target)
},
ownKeys(target) {
return Reflect.ownKeys(target)
},
has(target, p) {
return Reflect.has(target, p)
},
isExtensible(target) {
return Reflect.isExtensible(target)
},
preventExtensions(target) {
return Reflect.preventExtensions(target)
},
defineProperty(target, p, attributes) {
return Reflect.defineProperty(target, p, attributes)
},
deleteProperty(target, p) {
return Reflect.deleteProperty(target, p)
}
}
4 changes: 2 additions & 2 deletions test/construction.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import D, { constructedObject } from '../src'
import D from '../src'
import { log, logs } from './logger'

class End1 {
Expand Down Expand Up @@ -30,7 +30,7 @@ let constructedObjectFromA: any = null
class A extends D() {
constructor(aValue: string) {
super(aValue + ' and A')
constructedObjectFromA = constructedObject(this)
constructedObjectFromA = this
log('A:', aValue)
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/seclude.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Diamond, { constructedObject, Seclude } from '../src'
import Diamond, { Seclude } from '../src'

interface Scenario {
pubFld: number
Expand Down

0 comments on commit 889385f

Please sign in to comment.