🚨 Shai-Hulud Strikes Again:More than 500 packages and 700+ versions compromised.Technical Analysis →
Socket
Book a DemoInstallSign in
Socket

@qiwi/mixin

Package Overview
Dependencies
Maintainers
3
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@qiwi/mixin

RnD project to compare various mixin approaches in TypeScript

latest
Source
npmnpm
Version
1.3.8
Version published
Weekly downloads
71
44.9%
Maintainers
3
Weekly downloads
 
Created
Source

@qiwi/mixin

RnD project to compare various mixin approaches in TypeScript.

CI Maintainability Test Coverage npm (tag)

Getting started

Requirements

  • Node.js ^12.20.0 || ^14.13.1 || >=16.0.0
  • TypeScript >= 3.7 | 4.x

Install

yarn add @qiwi/mixin
npm i @qiwi/mixin

Usage

import {applyMixins} from '@qiwi/mixin'

interface IA {
  a: () => string
}
interface IB {
  b: () => string
}
class A implements IA {
  a() { return 'a' }
}
const b: IB = {
  b() { return 'b' }
}

const c = applyMixins({}, A, b)
c.a() // 'a'
c.b() // 'b'

const D = applyMixins(A, b)
const d = new D()

d.a() // 'a'
d.b() // 'b'

Exports

The library exposes itself as cjs, esm, umd and ts sources. Follow packages.json test:it:* scripts and integration tests examples if you're having troubles with loading.

Advanced examples

import {
  applyMixinsAsProxy,
  applyMixinsAsMerge,
  applyMixinsAsSubclass,
  applyMixinsAsProto,
  applyMixinsAsPipe
} from '@qiwi/mixin'

interface A {
  a(): string
}
interface B extends A {
  b(): string
}
interface C extends B {
  c(): string
}
interface D {
  d(): number
}
const a: A = {
  a() {
    return 'a'
  },
}
const _a: A = {
  a() {
    return '_a'
  },
}
const b = {
  b() {
    return this.a().toUpperCase()
  },
} as B
const c = {
  c() {
    return this.a() + this.b()
  },
} as C

class ACtor implements A {
  a() {
    return 'a'
  }
  static foo() {
    return 'foo'
  }
}
class BCtor extends ACtor implements B {
  b() {
    return this.a().toUpperCase()
  }
  static bar() {
    return 'bar'
  }
}

class DCtor implements D {
  d() {
    return 1
  }
}

class Blank {}

applyMixinsAsProxy

  type ITarget = { foo: string }
  const t: ITarget = {foo: 'bar'}
  const t2 = applyMixinsAsProxy(t, a, b, c, _a)

  t2.c()  // '_a_A'
  t2.a()  // '_a'
  t2.foo  // 'bar'
  // @ts-ignore
  t2.d    // undefined

applyMixinsAsMerge

  type ITarget = { foo: string }
  const t: ITarget = {foo: 'bar'}
  const t2 = applyMixinsAsMerge(t, a, b, c)

  t === t2  // true
  t2.c()    // 'aA'
  t2.a()    // 'a'
  t2.foo    // 'bar'

applyMixinsAsSubclass

  const M = applyMixinsAsSubclass(ACtor, Blank, BCtor, DCtor)
  const m = new M()

  M.foo()   // 'foo'
  M.bar()   // 'bar'

  m instanceof M // true
  m instanceof ACtor // true
  m.a()     // 'a'
  m.b()     // 'A'
  m.d()     // 1

applyMixinsAsProto

  class Target {
    method() {
      return 'value'
    }
  }
  const Derived = applyMixinsAsProto(Target, ACtor, BCtor, DCtor, Blank)
  const m = new Derived()

  Derived === Target // true
  Derived.foo() // 'foo'
  Derived.bar() // 'bar'

  m.a()   // 'a'
  m.b()   // 'A'
  m.d()   // 1

applyMixinsAsFactory

  const n = (n: number) => ({n})
  const m = ({n}: {n: number}) => ({n: 2 * n})
  const k = ({n}: {n: string}) => n.toUpperCase()
  const e = <T extends {}>(e: T): T & {foo: string} => ({...e, foo: 'foo'})
  const i = <T extends {foo: number}>(i: T): T => i

  const nm = applyMixinsAsPipe(n, m)
  const ie = applyMixinsAsPipe(i, e)

  const v1: number = nm(2).n          // 4
  const v2: string = ie({foo: 1}).foo // 'foo'

Implementation notes

Q&A

  • Definition.

    A mixin is a special kind of multiple inheritance.

  • Is it possible to mix classes with automated type inference?

    There're several solutions:

    • A subclass factory
    • Proto merge + constructor invocation + type cast workarounds
  • How to combine OOP and functional mixins?

    Apply different merge strategies for each target type and rest args converters

  • How to check if composition has a given mixin or not?

    Ref Cache / WeakMap

  • What's about mixin factories?

    It's called applyMixins

Definition

A mixin is a special kind of multiple inheritance. It's a form of object composition, where component features get mixed into a composite object so that properties of each mixin become properties of the composite object.

In OOP, a mixin is a class that contains methods for use by other classes, and can also be viewed as an interface with implemented methods.

Functional mixins are composable factories which connect together in a pipeline; each function adding some properties or behaviors.

Perhaps these are not perfect definitions, but we'll rely on them.

Mixin cases

  • Subclass factory

    type Constructor<T = {}> = new (...args: any[]) => T
    
    function MixFoo<TBase extends Constructor>(Base: TBase) {
      return class extends Base {
        foo() { return 'bar' }
      }
    }
    
  • Prototype injection

    class Derived {}
    class Mixed {
      foo() { return 'bar' }
    }
    
    Object.getOwnPropertyNames(Mixed.prototype).forEach(name => {
        Object.defineProperty(Derived.prototype, name, Object.getOwnPropertyDescriptor(Mixed.prototype, name));
    })
    
  • Object assignment

    const foo = {foo: 'foo'}
    const fooMixin = (target) => Object.assign(target, foo)
    const bar = fooMixin({bar: 'bar'})
    
  • Proxy wrapping

    const mixAsProxy = <P extends IAnyMap, M extends IAnyMap>(target: P, mixin: M): P & M => new Proxy(target, {
      get: (obj, prop: string) => {
        return prop in mixin
          // @ts-ignore
          ? mixin[prop]
          // @ts-ignore
          : obj[prop]
      },
    }) as P & M
    
  • Functional mixin piping

    const foo = <T>(t: T): T & {foo: string} => ({...t, foo: 'foo'})
    const bar = <T>(t: T): T & {bar: number} => ({...t, bar: 1})
    const foobar = pipe(foo, bar) // smth, that composes fn mixins like `(target) => bar(foo(target))`
    const target = {}
    
    const res = foobar(target)
    

Refs

Alternatives

License

MIT

Keywords

mixin

FAQs

Package last updated on 19 Mar 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts