From fe98620baa8aaae3cb7090293f3ad5516df0f7dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Marie=20De=20Mey?= Date: Sat, 2 Nov 2024 12:20:22 +0200 Subject: [PATCH] seclusion `instance of` --- README.md | 2 +- src/diamond.ts | 18 +++++++++++------- src/seclude.ts | 7 ++++++- test/seclude.test.ts | 6 ++++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c97d161..63ca57d 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ As simple as that, methods (as well as accessors) of `Plane` and `DuckCourier` w ## But ... How ? And, how can I ... -When a secluded class is implemented, `this` (so, here, a `DuckCourier`) will be used as the prototype for a `Secluded`. A Proxy is added between `Secluded` and `Plane` to manage who is `this` in method calls (either `DuckCourier` or `Secluded`) - et voilĂ ! +When a secluded class is implemented, the `Plane` instance prototype will be replaced by `this` (so, here, a `DuckCourier`). Some `Proxy` voodoo is juggled with to manage who is `this` in method calls (either `DuckCourier` or `Secluded`) - et voilĂ ! Because of prototyping, `Secluded` has access to all the functionalities of `DuckCourier` (and therefore of `Plane`) while never interfering with `DuckCourier::wingSpan`. Also, having several secluded class in the legacy list will only create several "heads" who will share a prototype. diff --git a/src/diamond.ts b/src/diamond.ts index 5eba7e9..c3260eb 100644 --- a/src/diamond.ts +++ b/src/diamond.ts @@ -34,6 +34,16 @@ export const diamondHandler: { } } +export function hasInstanceManager(cls: Class) { + return (obj: any) => { + if (!obj || typeof obj !== 'object') return false + const objBottom = bottomLeg(obj.constructor) + if (objBottom === cls) return true + const fLeg = allFLegs.get(objBottom) + return Boolean(fLeg && fLeg.some((base) => bottomLeg(base) === cls)) + } +} + export default function Diamond( ...baseClasses: TBases ): Newable> { @@ -93,13 +103,7 @@ export default function Diamond( buildingDiamond = null } } - static [Symbol.hasInstance](obj: any) { - if (!obj || typeof obj !== 'object') return false - const objBottom = bottomLeg(obj.constructor) - if (objBottom === Diamond) return true - const fLeg = allFLegs.get(objBottom) - return fLeg && fLeg.some((base) => bottomLeg(base) === Diamond) - } + static [Symbol.hasInstance] = hasInstanceManager(Diamond) } allFLegs.set(Diamond, bases) /** diff --git a/src/seclude.ts b/src/seclude.ts index 3f0f5f0..bda8473 100644 --- a/src/seclude.ts +++ b/src/seclude.ts @@ -1,4 +1,4 @@ -import Diamond, { diamondHandler } from './diamond' +import Diamond, { diamondHandler, hasInstanceManager } from './diamond' import { constructedObject } from './helpers' import { Ctor, KeySet, Newable } from './types' import { allFLegs, bottomLeg, fLegs, nextInLine } from './utils' @@ -49,6 +49,11 @@ export function Seclude newable diamond = diamondSecluded ? (Diamond(PropertyCollector) as any) : PropertyCollector + // We make sure `Secluded(X).secluded(x) instanceof X` + if (diamondSecluded) { + Object.defineProperty(base, Symbol.hasInstance, { value: hasInstanceManager(base) }) + //base[Symbol.hasInstance] = hasInstanceManager(base) + } class GateKeeper extends (diamond as any) { static secluded(obj: TBase): TBase | undefined { return privates.get(obj) diff --git a/test/seclude.test.ts b/test/seclude.test.ts index 7709453..c90a0af 100644 --- a/test/seclude.test.ts +++ b/test/seclude.test.ts @@ -67,6 +67,8 @@ test('leg-less', () => { } let t = testScenario(new Y(), P) + expect(t instanceof X).toBe(true) + expect(builtX! instanceof X).toBe(true) expect(builtX).toBe(P.secluded(t)) }) @@ -106,8 +108,12 @@ test('leg-half', () => { class E extends Diamond(Y, P) {} let t = testScenario(new D(), P) + expect(t instanceof X).toBe(true) + expect(builtX! instanceof X).toBe(true) expect(builtX).toBe(P.secluded(t)) t = testScenario(new E(), P) + expect(t instanceof X).toBe(true) + expect(builtX! instanceof X).toBe(true) expect(builtX).toBe(P.secluded(t)) })