Created
March 2, 2017 13:56
-
-
Save jamiewinder/d148f7289ee980ce4275d5e75d470304 to your computer and use it in GitHub Desktop.
Example Repository
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DataRecord<T> { | |
// Constructor | |
constructor(value: T, initialRef: any) { | |
this.value = value; | |
this.refs = new Set([initialRef]); | |
} | |
// Properties | |
public readonly value: T; | |
public readonly refs: Set<any>; | |
} | |
export class KeyedRepository<T> { | |
// Fields | |
private readonly _records = new Map<any, DataRecord<T>>(); | |
// Methods | |
/** | |
* Gets a record value from the repository, or `undefined` if it cannot be found. The record | |
* will be updated with the given reference. | |
*/ | |
public get(key: any, ref: any): T | undefined { | |
const record = this._records.get(key); | |
if (record === undefined) { | |
return undefined; | |
} | |
if (ref != null) { | |
record.refs.add(ref); | |
} | |
return record.value; | |
} | |
/** | |
* Adds a value to the repository using the given key, and initial reference. This | |
* function will throw if a record with this key already exists. | |
*/ | |
public add(key: any, ref: any, value: T): void { | |
let record = this._records.get(key); | |
if (record !== undefined) { | |
throw new Error('Cannot add because a record with this key already exists'); | |
} | |
record = new DataRecord<T>(value, ref); | |
this._records.set(key, record); | |
} | |
/** | |
* Attempts to get the value for the given key. If this fails, the | |
* record will be added by invoking the given value function. | |
*/ | |
public getOrAdd(key: any, ref: any, createValue: () => T): T { | |
const existing = this.get(key, ref); | |
if (existing === undefined) { | |
const value = createValue(); | |
if (value === undefined) { | |
throw new Error('Cannot add the value of `undefined`'); | |
} | |
this.add(key, ref, value); | |
return value; | |
} else { | |
return existing; | |
} | |
} | |
/** | |
* Gets all values | |
*/ | |
public getAll(ref: any): Array<T> { | |
const values = []; | |
for (const record of this._records.values()) { | |
values.push(record.value); | |
if (ref != null) { | |
record.refs.add(ref); | |
} | |
} | |
return values; | |
} | |
/** | |
* Unreferences all records with the given reference. Any unreferenced data | |
* will be removed. | |
*/ | |
public unref(ref: any): void { | |
for (const [key, record] of this._records.entries()) { | |
record.refs.delete(ref); | |
if (record.refs.size === 0) { | |
this._records.delete(key); | |
} | |
} | |
} | |
// Properties | |
public get size() { | |
return this._records.size; | |
} | |
} | |
export type Constructor<T> = { new(): T }; | |
export class TypedRepository { | |
// Fields | |
private readonly _typeRepositories = new Map<object, KeyedRepository<any>>(); | |
// Methods | |
/** | |
* Gets a value from the repository, or `undefined` if it cannot be found. The record | |
* will be updated with the given reference. | |
*/ | |
public get<T extends object>(type: Constructor<T>, key: any, ref: any): T | undefined { | |
const typeRepository = this._typeRepositories.get(type); | |
if (!typeRepository) { | |
return undefined; | |
} | |
return typeRepository.get(key, ref); | |
} | |
/** | |
* Adds a value using the given type and key, and initial reference. This | |
* function will throw if a record with the type and key already exists. | |
*/ | |
public add<T extends object>(type: Constructor<T>, key: any, ref: any, value: T): void { | |
let typeRepository = this._typeRepositories.get(type); | |
if (!typeRepository) { | |
typeRepository = new KeyedRepository<any>(); | |
this._typeRepositories.set(type, typeRepository); | |
} | |
return typeRepository.add(key, ref, value); | |
} | |
/** | |
* Attempts to get the value with the given type and key. If this fails, the | |
* record will be added by invoking the given value function. | |
*/ | |
public getOrAdd<T extends object>(type: Constructor<T>, key: any, ref: any, createValue: () => T): T { | |
const existing = this.get(type, key, ref); | |
if (existing === undefined) { | |
const value = createValue(); | |
if (value === undefined) { | |
throw new Error('Cannot add the value of undefined'); | |
} | |
this.add(type, key, ref, value); | |
return value; | |
} else { | |
return existing; | |
} | |
} | |
/** | |
* Gets all values for the given type | |
*/ | |
public getAllOfType<T extends object>(type: Constructor<T>, ref: any): Array<T> { | |
const typeMap = this._typeRepositories.get(type); | |
if (!typeMap) { | |
return []; | |
} | |
return typeMap.getAll(ref); | |
} | |
/** | |
* Unreferences records in the repository across all types and keys. | |
* Any unreferenced records will be removed. | |
*/ | |
public unref(ref: any): void { | |
for (const [type, typeRepository] of this._typeRepositories.entries()) { | |
typeRepository.unref(ref); | |
if (typeRepository.size === 0) { | |
this._typeRepositories.delete(type); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment