Skip to content

Instantly share code, notes, and snippets.

@sebinsua
Last active July 21, 2023 14:40
Show Gist options
  • Save sebinsua/9f8fb82ad59b5f5b38015d05dd40144b to your computer and use it in GitHub Desktop.
Save sebinsua/9f8fb82ad59b5f5b38015d05dd40144b to your computer and use it in GitHub Desktop.
WIP after playing around in GPT.
type Constructor<T> = new (...args: any[]) => T;
type DefaultType<T> = T extends Constructor<infer U> ? U : T;
function defaultObject<V, K extends string | symbol = string | symbol>(Type: Constructor<V> | V): Record<K, DefaultType<V>> & Iterable<[K, DefaultType<V>]> {
const store = new Map<K, DefaultType<V>>();
const iterable = {
*[Symbol.iterator] () {
for (let [key, value] of store) {
yield [key, value];
}
}
};
return new Proxy(iterable as Record<K, DefaultType<V>>, {
get(target: Record<K, DefaultType<V>>, property: K): DefaultType<V> {
if (!store.has(property)) {
if (typeof Type === 'function') {
store.set(property, new (Type as Constructor<V>)() as DefaultType<V>);
} else {
store.set(property, Type as DefaultType<V>);
}
}
return store.get(property)!;
},
has(target: Record<K, DefaultType<V>>, property: K): boolean {
return store.has(property);
},
ownKeys(): (string | symbol)[] {
return Array.from(store.keys());
},
deleteProperty(target: Record<K, DefaultType<V>>, property: K): boolean {
return store.delete(property);
},
set(target: Record<K, DefaultType<V>>, property: K, value: DefaultType<V>): boolean {
if (typeof Type === 'function') {
if (!(value instanceof (Type as Constructor<V>))) {
throw new Error(`Value does not belong to the default type.`);
}
} else {
if (value !== Type) {
throw new Error(`Value does not match the default value.`);
}
}
store.set(property, value);
return true;
},
getOwnPropertyDescriptor(target: Record<K, DefaultType<V>>, property: K) {
if (store.has(property)) {
return {
enumerable: true,
configurable: true,
value: store.get(property),
};
}
return undefined;
}
}) as Record<K, DefaultType<V>> & Iterable<[K, DefaultType<V>]>;
}
// Usage with Array
const d = defaultObject(Array);
d['abc'].push('def');
const abc = d['abc'];
console.log('abc' in d); // Outputs: true
for (const [key, value] of d) {
console.log(key, value);
}
delete d['abc'];
console.log('abc' in d); // Outputs: false
// Usage with Set
const s = defaultObject(Set);
s['abc'].add('def');
console.log('abc' in s); // Outputs: true
for (const [key, value] of s) {
console.log(key, value);
}
delete s['abc'];
console.log('abc' in s); // Outputs: false
// Usage with number
const n = defaultObject(0);
n['abc']++;
console.log('abc' in n); // Outputs: true
for (const [key, value] of n) {
console.log(key, value);
}
delete n['abc'];
console.log('abc' in n); // Outputs: false
// Usage with type checking on set
try {
d['abc'] = 'def'; // Throws an error
} catch (err) {
console.error(err);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment