Skip to content

Instantly share code, notes, and snippets.

@nyteshade
Created December 11, 2023 19:09
Show Gist options
  • Save nyteshade/fd5cbb4ef7698e187bca0192b7e2d28e to your computer and use it in GitHub Desktop.
Save nyteshade/fd5cbb4ef7698e187bca0192b7e2d28e to your computer and use it in GitHub Desktop.
Another go at baseline JavaScript additions such as work with Descriptors. Need to compare this with code in ne-reflection and decide which I like best.
Object.assign(Object, {
validKey(value) {
return (typeof value === 'string' || typeof value === 'symbol')
},
isObject(value) {
return value && (value instanceof Object || typeof value === 'object')
},
/**
* The `truthyProperties` function filters an object's properties based on a
* configuration and returns a new object with only the truthy values. Configurations
*
* @param object - The `object` parameter is the JavaScript object for which you
* want to filter the truthy properties.
* @param config - The `config` parameter is an optional object that allows you
* to customize the behavior of the `truthyProperties` function. It has the
* following properties:
* @returns The function `truthyProperties` returns an object that contains the
* properties from the input `object` that meet certain conditions based on the
* provided `config`.
*/
truthyProperties(
object,
config
) {
const defaultConfig = {
allowUndefined: false,
allowNull: false,
allowOther: null /* ([key, value]) => boolean */,
allowAccessors: false,
allowFalseBooleans: true,
}
const suppliedConfig = { ...defaultConfig, ...(config ?? {}) }
const {
allowUndefined,
allowNull,
allowOther,
allowAccessors,
allowFalseBooleans
} = suppliedConfig
const [KEY, VALUE] = [0, 1]
const descriptors = Descriptor.all(object)
const entries = descriptors.entries.map(([key, descriptor]) => {
const entry = [key, undefined]
if (Descriptor.isAccessor(descriptor) && allowAccessors) {
entry[VALUE] = Descriptor.getAccessor(descriptor)
}
else if (Descriptor.isData(descriptor)) {
entry[VALUE] = Descriptor.getData(descriptor)
}
else {
entry[VALUE] = object[key]
}
return entry
})
return (entries
.filter(entry => {
if (
(allowUndefined && entry[VALUE] === undefined) ||
(allowNull && entry[VALUE] === null) ||
(allowFalseBooleans && typeof entry[VALUE] === 'boolean') ||
(allowOther && allowOther(entry))
) {
return true
}
return !!entry[VALUE]
})
.reduce((accumulator, entry) => {
if (entry.accumulator)
Object.defineProperty(accumulator, entry[KEY], entry.boundDescriptor)
else
accumulator[entry[KEY]] = entry[VALUE]
return accumulator
}, {})
)
}
})
Object.assign(Reflect, {
/**
* The function checks if an object has all the specified keys.
*
* @param object - The `object` parameter is the object that we want to check if
* it has all the specified keys.
* @param keys - The `keys` parameter is a rest parameter, which means it can
* accept any number of arguments. In this case, it is expected to receive
* multiple keys as arguments.
* @returns a boolean value.
*/
hasAll(object, ...keys) {
return Object.isObject(object) && (keys.flat(Infinity)
.map(key => Reflect.has(object, key))
.every(has => has)
)
},
/**
* The function checks if an object has at least one of the specified keys.
*
* @param object - The `object` parameter is the object that we want to check for
* the presence of certain keys.
* @param keys - The `keys` parameter is a rest parameter, which means it can
* accept any number of arguments. These arguments are the keys that we want to
* check if they exist in the `object`.
* @returns The function `hasSome` returns a boolean value indicating whether at
* least one of the keys provided as arguments exists in the given object.
*/
hasSome(object, ...keys) {
return Object.isObject(object) && (keys.flat(Infinity)
.map(key => Reflect.has(object, key))
.some(has => has)
)
},
})
class Descriptor {
/**
* The function `all` returns an object that provides information about the
* properties and methods of a given object and its prototype.
*
* @param object - The `object` parameter is the object for which you want to
* retrieve all properties and methods, including both instance
* properties/methods and prototype properties/methods.
* @returns an object that contains the descriptors of the provided object and
* its prototype. The returned object also has several methods and properties for
* accessing and manipulating the descriptors.
*/
static all(object) {
if (!Object.isObject(object))
return {}
const instance = Object.getOwnPropertyDescriptors(object)
const isClass = o => String(o).includes('class')
let prototype = null
if (object?.constructor?.prototype && isClass(object)) {
prototype = Object.getOwnPropertyDescriptors(object.constructor.prototype)
}
const result = {
[Symbol.for('instance')]: instance,
[Symbol.for('prototype')]: prototype,
has(named) {
return (
Reflect.has(prototype, named) ||
Reflect.has(instance, named)
)
},
get(named) {
return (prototype[named] ?? instance[named])
},
object(named) {
return Reflect.has(prototype, named)
? prototype
: (Reflect.has(instance, named) ? instance : null)
},
get prototypeKeys() {
return Object.keys(prototype ?? {})
},
get instanceKeys() {
return Object.keys(instance)
},
get entries() {
return Object.entries(instance).concat(Object.entries(prototype ?? {}))
},
}
return result
}
/**
* The function "static for" retrieves the descriptor of a property from an
* object or its prototype.
*
* @param object - The `object` parameter is the object that you want to get the
* property descriptor from. It can be any JavaScript object.
* @param property - The `property` parameter is a string that represents the
* name of the property you want to get the descriptor for.
* @returns the descriptor of the specified property in the given object.
*/
static for(object, property) {
let descriptor = Object.getOwnPropertyDescriptor(object, property)
if (!descriptor && object?.constructor?.prototype) {
descriptor = Object.getOwnPropertyDescriptor(
object.constructor.prototype,
property
)
}
return descriptor
}
/**
* The function returns an object with enumerable and configurable properties
* based on the input parameters.
*
* @param [enumerable=false] - A boolean value indicating whether the property
* can be enumerated (listed) when iterating over the object's properties.
* @param [configurable=false] - The `configurable` parameter determines whether
* the property can be deleted or its attributes can be modified. If
* `configurable` is set to `true`, the property can be deleted and its
* attributes can be changed. If `configurable` is set to `false`, the property
* cannot be deleted and
* @returns An object with the properties `enumerable` and `configurable` is
* being returned. The values of these properties are determined by the arguments
* passed to the `base` function.
*/
static base(enumerable = false, configurable = false) {
return {
enumerable,
configurable
}
}
/**
* The function "newAccessor" creates a new property descriptor object with a
* getter and setter function, along with optional enumerable and configurable
* flags.
*
* @param getter - The getter parameter is a function that will be used as the
* getter for the property. It will be called when the property is accessed.
* @param setter - The `setter` parameter is a function that will be used as the
* setter for the property. It will be called whenever the property is assigned a
* new value.
* @param [] - - `getter`: A function that will be used as the getter for the
* property.
* @returns an object with properties "get", "set", "enumerable", and
* "configurable".
*/
static newAccessor(
getter,
setter,
{ enumerable, configurable } = Descriptor.base()
) {
return {
get: getter,
set: setter,
enumerable,
configurable
}
}
/**
* The function "newData" creates a new data object with customizable properties.
*
* @param value - The value parameter represents the value that will be assigned
* to the property.
* @param [writable=true] - The `writable` parameter determines whether the value
* of the property can be changed. If `writable` is set to `true`, the value can
* be changed. If `writable` is set to `false`, the value cannot be changed.
* @param [] - - `value`: The value to be assigned to the property.
* @returns an object with properties `value`, `enumerable`, `writable`, and
* `configurable`.
*/
static newData(
value,
writable = true,
{ enumerable, configurable } = Descriptor.base()
) {
return {
value,
enumerable,
writable,
configurable
}
}
/**
* The function checks if an object is a valid object descriptor in JavaScript.
*
* @param object - The `object` parameter is the object that we want to check if
* it is a descriptor.
* @returns a boolean value.
*/
static isDescriptor(object) {
const knownKeys = [
...Descriptor.SHARED_KEYS,
...Descriptor.ACCESSOR_KEYS,
...Descriptor.DATA_KEYS,
]
return (
Reflect.hasSome(knownKeys) &&
(
Descriptor.isAccessor(object) ||
Descriptor.isData(object)
)
)
}
/**
* The function checks if a given property or descriptor is a data property.
*
* @param descriptor_orProp - The `descriptor_orProp` parameter can be either a
* descriptor or a property name.
* @param object - The `object` parameter is the object that you want to check
* for data properties.
* @returns a boolean value. It returns `true` if the `descriptor` object has any
* keys that match the `DATA_KEYS` array, otherwise it returns `false`.
*/
static isData(object_orProp, property) {
const needsDescriptor = (
((typeof object_orProp === 'object') || object_orProp instanceof Object) &&
property instanceof String
)
const descriptor = (needsDescriptor
? Descritor.for(object_orProp, property)
: object_orProp)
const { ACCESSOR_KEYS, DATA_KEYS } = this
if (Reflect.hasSome(descriptor, ACCESSOR_KEYS)) {
return false
}
if (Reflect.hasSome(descriptor, DATA_KEYS)) {
return true
}
return false
}
/**
* The function checks if a given property descriptor or property of an object is
* an accessor.
*
* @param object_orProp - The `descriptor_orProp` parameter can be either a
* descriptor object or a property name.
* @param property - The `object` parameter is the object that you want to check
* for accessor properties.
* @returns a boolean value. It returns true if the descriptor or property passed
* as an argument is an accessor descriptor, and false otherwise.
*/
static isAccessor(object_orProp, property) {
const needsDescriptor = (
(object_orProp && property) &&
((typeof object_orProp === 'object') || object_orProp instanceof Object) &&
(property instanceof String || (typeof property === 'symbol'))
)
const descriptor = (needsDescriptor
? Descritor.for(object_orProp, property)
: object_orProp)
const { ACCESSOR_KEYS, DATA_KEYS } = this
if (Reflect.hasSome(descriptor, DATA_KEYS)) {
return false
}
if (Reflect.hasSome(descriptor, ACCESSOR_KEYS)) {
return true
}
return false
}
/**
* The function `getData` retrieves the value of a property from an object if it
* exists and is a data property.
*
* @param object - The "object" parameter is the object from which we want to
* retrieve data.
* @param property - The `property` parameter is the name of the property that
* you want to retrieve the data from.
* @returns either the value of the specified property if it exists and is a data
* property, or undefined if the property does not exist or is not a data
* property.
*/
static getData(object, property) {
if (typeof object !== 'object' || typeof property !== 'string') {
return null;
}
const descriptors = Descriptor.all(object)
if (descriptors.has(property)) {
const descriptor = descriptors.get(property)
if (Descriptor.isData(descriptor)) {
return descriptor.value
}
}
return undefined
}
/**
* The function `getAccessor` checks if an object has a getter/setter accessor
* for a given property and returns the accessor functions if found.
*
* @param object - The `object` parameter is the object from which we want to
* retrieve the accessor for a specific property.
* @param property - The `property` parameter is the name of the property for
* which we want to get the accessor.
* @returns an object that contains the getter and setter functions for the
* specified property of the given object. If the property is an accessor
* property (defined with a getter and/or setter), the returned object will also
* have additional properties such as "accessor" and "descriptor". If the
* property is not found or is not an accessor property, the function returns
* undefined.
*/
static getAccessor(object, property) {
if (!Object.isObject(object))
return null
const [GETTER, SETTER, OBJECT] = [0, 1, 2]
const results = [undefined, undefined, undefined]
const descriptors = this.all(object)
const isDescriptor = Descriptor.isDescriptor(object)
if (descriptors.has(property) || isDescriptor) {
const descriptor = isDescriptor ? object : descriptors.get(property)
if (Descriptor.isAccessor(descriptor)) {
results[OBJECT] = descriptors.object(property)
results[GETTER] = descriptor?.get
results[SETTER] = descriptor?.set
Object.assign(results, {
get() { this[GETTER].bind(this[OBJECT])() },
set(value) { this[SETTER].bind(this[OBJECT])(value) },
get accessor() { return true },
get descriptor() { return descriptor },
get boundDescriptor() {
return {
...descriptor,
get: descriptor.get?.bind(object),
set: descriptor.set?.bind(object),
}
}
})
return results
}
}
return undefined
}
/**
* A base descriptor (new for each read) that is both enumerable and configurable
*
* @returns The method `flexible` is returning the result of calling the `base`
* method with the arguments `true` and `true`.
*/
static get flexible() {
return this.base(true, true)
}
/**
* A base descriptor (new for each read) that is not enumerable but is configurable
*
* @returns The method `enigmatic` is returning the result of calling the `base`
* method with the arguments `false` and `true`.
*/
static get enigmatic() {
return this.base(false, true)
}
/**
* A base descriptor (new for each read) that is neither enumerable nor configurable
*
* @returns The code is returning the result of calling the `base` method with
* the arguments `false` and `false`.
*/
static get intrinsic() {
return this.base(false, false)
}
/**
* A base descriptor (new for each read) that enumerable but not configurable
*
* @returns The method is returning the result of calling the `base` method with
* the arguments `true` and `false`.
*/
static get transparent() {
return this.base(true, false)
}
/**
* The function returns an array of shared descriptor keys.
*
* @returns An array containing the strings 'configurable' and 'enumerable'.
*/
static get SHARED_KEYS() {
return ['configurable', 'enumerable']
}
/**
* The function returns an array of accessor descriptor keys.
*
* @returns An array containing the strings 'get' and 'set' is being returned.
*/
static get ACCESSOR_KEYS() {
return ['get', 'set']
}
/**
* The function returns an array of data descriptor keys.
*
* @returns An array containing the strings 'value' and 'writable' is being
* returned.
*/
static get DATA_KEYS() {
return ['value', 'writable']
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment