Skip to content

Instantly share code, notes, and snippets.

@jamiewinder
Last active March 2, 2017 16:14
Show Gist options
  • Save jamiewinder/944e038c86e19d81c4f5a455288b48f2 to your computer and use it in GitHub Desktop.
Save jamiewinder/944e038c86e19d81c4f5a455288b48f2 to your computer and use it in GitHub Desktop.
Example ObservableRepository
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);
}
}
}
}
@jamiewinder
Copy link
Author

jamiewinder commented Mar 2, 2017

objectuid.ts

let nextId = 0;
let uidMap = new WeakMap<object, string>();

export function objectUid(type: object): string {
    let uid = uidMap.get(type);
    if (uid != null) {
        return uid;
    }
    uid = '_' + (nextId++);
    uidMap.set(type, uid);
    return uid;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment