Skip to content

Commit

Permalink
feat(patterns): objectMetaMap, objectMetaAssign
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Oct 7, 2023
1 parent 877be98 commit 69f9fe3
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 3 deletions.
7 changes: 6 additions & 1 deletion packages/patterns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ export {

// ////////////////// Temporary, until these find their proper home ////////////

export { listDifference, objectMap } from './src/utils.js';
export {
listDifference,
objectMap,
objectMetaMap,
objectMetaAssign,
} from './src/utils.js';

// eslint-disable-next-line import/export
export * from './src/types.js';
83 changes: 81 additions & 2 deletions packages/patterns/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { isPromise } from '@endo/promise-kit';

/** @typedef {import('@endo/marshal').Checker} Checker */

const { fromEntries, entries } = Object;
const {
fromEntries,
entries,
getOwnPropertyDescriptors,
create,
defineProperties,
} = Object;
const { ownKeys } = Reflect;

const { details: X, quote: q, Fail } = assert;
Expand Down Expand Up @@ -92,8 +98,8 @@ harden(fromUniqueEntries);
* a CopyRecord.
*
* @template {Record<string, any>} O
* @param {O} original
* @template R map result
* @param {O} original
* @param {(value: O[keyof O], key: keyof O) => R} mapFn
* @returns {{ [P in keyof O]: R}}
*/
Expand All @@ -104,6 +110,79 @@ export const objectMap = (original, mapFn) => {
};
harden(objectMap);

/**
* Like `objectMap`, but at the reflective level of property descriptors
* rather than property values.
*
* Except for hardening, the edge case behavior is mostly the opposite of
* the `objectMap` edge cases.
* * No matter how mutable the original object, the returned object is
* hardened.
* * All own properties of the original are mapped, even if symbol-named
* or non-enumerable.
* * If any of the original properties were accessors, the descriptor
* containing the getter and setter are given to `metaMapFn`.
* * The own properties of the returned are according to the descriptors
* returned by `metaMapFn`.
* * The returned object will always be a plain object whose state is
* only these mapped own properties. It will inherit from the third
* argument if provided, defaulting to `Object.prototype` if omitted.
*
* Because a property descriptor is distinct from `undefined`, we bundle
* mapping and filtering together. When the `metaMapFn` returns `undefined`,
* that property is omitted from the result.
*
* @template {Record<PropertyKey, any>} O
* @param {O} original
* @param {(
* desc: TypedPropertyDescriptor<O[keyof O]>,
* key: keyof O
* ) => (PropertyDescriptor | undefined)} metaMapFn
* @param {any} [proto]
* @returns {any}
*/
export const objectMetaMap = (
original,
metaMapFn,
proto = Object.prototype,
) => {
const descs = getOwnPropertyDescriptors(original);
const keys = ownKeys(original);

const descEntries = /** @type {[PropertyKey,PropertyDescriptor][]} */ (
keys
.map(key => [key, metaMapFn(descs[key], key)])
.filter(([_key, optDesc]) => optDesc !== undefined)
);
return harden(create(proto, fromUniqueEntries(descEntries)));
};
harden(objectMetaMap);

/**
* Like `Object.assign` but at the reflective level of property descriptors
* rather than property values.
*
* Unlike `Object.assign`, this includes all own properties, whether enumerable
* or not. An original accessor property is copied by sharing its getter and
* setter, rather than calling the getter to obtain a value. If an original
* property is non-configurable, a property of the same name on a later original
* that would conflict instead causes the call to `objectMetaAssign` to throw an
* error.
*
* Returns the enhanced `target` after hardening.
*
* @param {any} target
* @param {any[]} originals
* @returns {any}
*/
export const objectMetaAssign = (target, ...originals) => {
for (const original of originals) {
defineProperties(target, getOwnPropertyDescriptors(original));
}
return harden(target);
};
harden(objectMetaAssign);

/**
*
* @param {Array<string | symbol>} leftNames
Expand Down

0 comments on commit 69f9fe3

Please sign in to comment.