doddle
Version:
Tiny yet feature-packed (async) iteration toolkit.
92 lines (82 loc) • 2.99 kB
text/typescript
import { doddle } from "../doddle/index.js"
/** Minimal ES decorator context for class getters/methods used here. */
export interface ClassGetterDecoratorContext {
/** Decorated element kind. We support getters and zero-arg methods. */
readonly kind: "getter" | "method"
/** Class member name or symbol. */
readonly name?: string | symbol
/** Whether the member is static. */
readonly static?: boolean
}
// Unique counter to help name anonymous members when no name/description exists.
let index = 0
function makeSymbolName(name?: string | symbol): string {
// Increment for every decorator application.
const id = String(index++)
if (typeof name === "string") return name
if (typeof name === "symbol") {
const desc = name.description
if (desc != null && desc !== "") return desc
}
return id
}
function wrapMethod(method: (this: any) => any, name?: string | symbol) {
const symName = makeSymbolName(name)
const cacheKey = Symbol(`doddlify:${symName}`)
return function (this: any) {
let cached = this[cacheKey]
if (cached == null) {
cached = this[cacheKey] = doddle(() => method.call(this))
}
return cached.pull()
}
}
// TypeScript legacy decorator overload (experimental): (target, key, descriptor) => descriptor
export function doddlify(
target: any,
key: string | symbol,
descriptor: PropertyDescriptor
): PropertyDescriptor | void
// ES decorators (stage-3): (value, context) => new value
export function doddlify(
value: (this: any) => any,
context: ClassGetterDecoratorContext
): (this: any) => any
// ES decorators (stage-3) for methods: zero-arg functions only
export function doddlify(
value: (this: any) => any,
context: ClassGetterDecoratorContext
): (this: any) => any
export function doddlify(a: any, b: any, c?: any): any {
// ES decorators path: (fn, context)
if (typeof a === "function" && c === undefined) {
const fn = a as (this: any) => any
const ctx = b as ClassGetterDecoratorContext
if (ctx?.kind === "getter") {
return wrapMethod(fn, ctx?.name)
}
if (ctx?.kind === "method") {
// Support only zero-arg methods
if (fn.length === 0) return wrapMethod(fn, ctx?.name)
return fn
}
return fn
}
// TypeScript legacy path: (target, key, descriptor)
const descriptor = c as PropertyDescriptor | undefined
if (!descriptor) return descriptor
// Getter
if (typeof descriptor.get === "function") {
descriptor.get = wrapMethod(descriptor.get!, b as string | symbol)
return descriptor
}
// Method (zero-arg only)
if (typeof descriptor.value === "function") {
const fn = descriptor.value
if (fn.length === 0) {
descriptor.value = wrapMethod(fn, b as string | symbol)
}
return descriptor
}
return descriptor
}