Skip to content

Instantly share code, notes, and snippets.

@nyteshade
Last active March 7, 2024 19:54
Show Gist options
  • Save nyteshade/03dcd7de33a2f3f87c2d989a76be527d to your computer and use it in GitHub Desktop.
Save nyteshade/03dcd7de33a2f3f87c2d989a76be527d to your computer and use it in GitHub Desktop.
CarrierString
/**
* Creates a `CarrierString` by attaching additional properties to a string.
* This function allows for the creation of a string that carries extra data in a type-safe way.
*
* @param string The base string to which properties will be attached.
* @param key The key of the property to attach, or an object with multiple properties.
* @param value The value associated with the key, if `key` is not an object.
* @returns A new `CarrierString` with the attached properties.
*
* @example
* // Attaches a single key-value pair
* const result = CarrierString('text', 'id', 123);
*
* @example
* // Attaches multiple properties from an object
* const result = CarrierString('text', { id: 123, type: 'example' });
*/
export const CarrierString = (string, key, value) => {
const stringInstance = Object(string);
const objectKey = key;
let associatedValue =
key && typeof key === 'object' ? (key as AssociatedData<K, V>) : { [objectKey]: value };
if (Object.getOwnPropertyNames(associatedValue).length > 1) {
associatedValue = { metadata: associatedValue };
}
return Object.assign(stringInstance, associatedValue);
};
/**
* A type representing either a string primitive or a String instance (i.e. new String(...) or
* Object('lorem ipsum dolor...')). **Do not remove or change type!**
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export type StringOrInstance = string | String;
/**
* Represents a key that can be used for object properties. It can be a string, a symbol,
* or a number.
*
* @example
* // Using a string as an object key
* const keyString: ObjectKey = 'name';
*
* @example
* // Using a symbol as an object key
* const keySymbol: ObjectKey = Symbol('unique');
*
* @example
* // Using a number as an object key
* const keyNumber: ObjectKey = 42;
*/
export type ObjectKey = string | symbol | number;
/**
* Defines a mapping of keys to values, where keys are of type `ObjectKey` and values are of
* type `V`. This type is useful for creating objects with dynamic keys.
*
* @example
* // Defines an object with string, symbol, and number as keys, each with a value of type number.
* let data: AssociatedData<string | symbol | number, number> = {
* 'key1': 1,
* [Symbol('key2')]: 2,
* 3: 3
* };
*/
export type AssociatedData<K extends ObjectKey, V> = { [key in K]: V };
/**
* Represents a string with optional additional properties.
*
* This type extends a string instance with a set of optional keys, where each key is of type
* `K` and associated with a value of type `V`. It is used to create string objects that can
* carry additional data in a type-safe manner.
*
* @example
* // Usage with a string and an additional numeric property
* let carrier: CarrierString<'id', number> = Object.assign('example', { id: 123 });
*/
export type CarrierString<K extends ObjectKey, V> = StringOrInstance & AssociatedData<K, V>;
/**
* Creates a `CarrierString` by attaching additional properties to a string.
* This function allows for the creation of a string that carries extra data in a type-safe way.
*
* @param string The base string to which properties will be attached.
* @param key The key of the property to attach, or an object with multiple properties.
* @param value The value associated with the key, if `key` is not an object.
* @returns A new `CarrierString` with the attached properties.
* @throws a TypeError if the key is an object with more than one property; sending an object
* as the key with a single property and any type of value is supported, but if you need more
* than one key value pair for associated data, supply a valid {@link ObjectKey} for the key
* and a complex value for the value.
*
* @example
* // Attaches a single key-value pair
* const result = CarrierString('text', 'id', 123);
*
* @example
* // Attaches multiple properties from an object
* const result = CarrierString('text', 'meta', { id: 123, type: 'example' });
*
* @example
* // This will throw a TypeError
* const result = CarrierString('nope', {id: 123, type: 'example'});
*/
export const CarrierString = <K extends ObjectKey, V>(
string: string,
key: K | AssociatedData<K, V>,
value?: V,
): CarrierString<K, V> => {
const stringInstance: CarrierString<K, V> = Object(string);
const objectKey = key as ObjectKey;
const associatedValue =
key && typeof key === 'object' ? (key as AssociatedData<K, V>) : { [objectKey]: value };
if (Object.getOwnPropertyNames(associatedValue).length > 1) {
const assocValueStr = inspect(associatedValue, { colors: true }).replaceAll(/\n/g, ' ');
throw new TypeError(
[
'An object with more than one key was supplied as the key and value. The',
`supplied value was ${assocValueStr}. Either supply a separate key and the`,
'complex value as an object or alter your approach',
].join(' '),
);
}
return Object.assign(stringInstance, associatedValue);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment