Last active
March 2, 2017 16:14
-
-
Save jamiewinder/944e038c86e19d81c4f5a455288b48f2 to your computer and use it in GitHub Desktop.
Example ObservableRepository
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
import { observable, computed, ObservableMap } from 'mobx'; | |
import { objectUid } from './objectuid'; | |
export type KeyType = string | number | object; | |
export type RefType = string | number | object; | |
function keyFn(key: KeyType) { | |
if (typeof key === 'string') { | |
return key; | |
} else if (typeof key === 'number') { | |
return '' + key; | |
} else { | |
return objectUid(key); | |
} | |
} | |
function refFn(key: RefType): string; | |
function refFn(key: RefType | null): string | null; | |
function refFn(key: RefType | null) { | |
if (key == null) { | |
return null; | |
} else if (typeof key === 'string') { | |
return key; | |
} else if (typeof key === 'number') { | |
return '' + key; | |
} else { | |
return objectUid(key); | |
} | |
} | |
class DataRecord<T> { | |
// Constructor | |
constructor(value: T, initialRef: string) { | |
this.value = value; | |
this.refs = observable.shallowMap<boolean>([[initialRef, true]]); | |
} | |
// Methods | |
public addRef(ref: string) { | |
this.refs.set(ref, true); | |
} | |
public removeRef(ref: string) { | |
this.refs.delete(ref); | |
} | |
public hasRef(ref: string) { | |
return this.refs.has(ref); | |
} | |
// Properties | |
public get refCount(): number { | |
return this.refs.size; | |
} | |
// Properties | |
public readonly value: T; | |
public readonly refs: ObservableMap<boolean>; | |
} | |
export class ObservableKeyedRepository<T> { | |
// Properties | |
private readonly _records = observable.shallowMap<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: KeyType, ref: RefType | null): T | undefined { | |
const keyStr = keyFn(key); | |
const refStr = refFn(ref); | |
const record = this._records.get(keyStr); | |
if (record === undefined) { | |
return undefined; | |
} | |
if (refStr != null) { | |
record.addRef(refStr); | |
} | |
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: KeyType, ref: RefType, value: T): void { | |
const keyStr = keyFn(key); | |
const refStr = refFn(ref); | |
let record = this._records.get(keyStr); | |
if (record !== undefined) { | |
throw new Error('Cannot add because a record with this key already exists'); | |
} | |
record = new DataRecord<T>(value, refStr); | |
this._records.set(keyStr, 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: KeyType, ref: RefType, 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: RefType | null): Array<T> { | |
const refStr = refFn(ref); | |
const values = []; | |
for (const record of this._records.values()) { | |
values.push(record.value); | |
if (refStr != null) { | |
record.addRef(refStr); | |
} | |
} | |
return values; | |
} | |
public getAllWithRef(ref: RefType): Array<T> { | |
const refStr = refFn(ref); | |
return this._records.values() | |
.filter((record) => record.hasRef(refStr)) | |
.map((record) => record.value); | |
} | |
/** | |
* Unreferences all records with the given reference. Any unreferenced data | |
* will be removed. | |
*/ | |
public unref(ref: RefType): void { | |
const refStr = refFn(ref); | |
for (const [key, record] of this._records.entries()) { | |
record.refs.delete(refStr); | |
if (record.refCount === 0) { | |
this._records.delete(key); | |
} | |
} | |
} | |
// Properties | |
public get size() { | |
return this._records.size; | |
} | |
} | |
export type Constructor<T> = { new(): T }; | |
export class ObservableTypedRepository { | |
// Fields | |
private readonly _typeRepositories = observable.shallowMap<ObservableKeyedRepository<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: KeyType, ref: RefType | null): T | undefined { | |
const typeKey = keyFn(type); | |
const typeRepository = this._typeRepositories.get(typeKey); | |
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: KeyType, ref: RefType, value: T): void { | |
const typeKey = keyFn(type); | |
let typeRepository = this._typeRepositories.get(typeKey); | |
if (!typeRepository) { | |
typeRepository = new ObservableKeyedRepository<any>(); | |
this._typeRepositories.set(typeKey, 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: KeyType, ref: RefType, 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: RefType | null): Array<T> { | |
const typeKey = keyFn(type); | |
const typeMap = this._typeRepositories.get(typeKey); | |
if (!typeMap) { | |
return []; | |
} | |
return typeMap.getAll(ref); | |
} | |
/** | |
* Gets all values for the given type with the given reference | |
*/ | |
public getAllOfTypeWithRef<T extends object>(type: Constructor<T>, ref: RefType): Array<T> { | |
const typeKey = keyFn(type); | |
const typeMap = this._typeRepositories.get(typeKey); | |
if (!typeMap) { | |
return []; | |
} | |
return typeMap.getAllWithRef(ref); | |
} | |
/** | |
* Unreferences records in the repository across all types and keys. | |
* Any unreferenced records will be removed. | |
*/ | |
public unref(ref: RefType): 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
objectuid.ts