File size: 3,919 Bytes
8969f81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { Utils } from '../lib/Utils';
import db from '../models/Database';
import * as mongodb from 'mongodb';
import { ObjectID, Cursor } from 'mongodb';


export class ObjectFactory {
	static create(__type: any, o: any): any {
		if (__type.fromObject) {
			return __type.fromObject(o);
		}
		const newObject = new __type();
		return Object.assign(newObject, o);
	}
}


export class MongoObject {
	protected static __type;							// The current class
	protected static __collectionName: string;			// The Mongo collection name
	protected static __idField: string = "_id";			// Default id used to findOne
	protected static __wlistJsonAttrs: string[] = [];	// Whitelist of attributes to serialize.
	
	// Json serialization.
	
	private toJsonRepr(): object {
		return Utils.pick(this, (<any>this.constructor).__wlistJsonAttrs);
	}
	
	toJson(): string {
		return JSON.stringify(this.toJsonRepr());
	}
	
	
	/// Find family of methods
	
	static async findOne<T>(id: string | ObjectID | mongodb.FilterQuery<T>, options?: mongodb.FindOneOptions): Promise<T | null> {
		const q = (typeof id === 'string' || id instanceof ObjectID)
			? { [this.__idField]: id }
			: id;
		
		const o = await db.collection(this.__collectionName).findOne(q, options);
		if (o) {
			return ObjectFactory.create(this.__type, o);
		}
		return null;
	}
	
	static async findOneAndUpdate<T>(filter: mongodb.FilterQuery<T>, update: Object, options?: mongodb.FindOneAndReplaceOption): Promise<T | null> {
		const o = await db.collection(this.__collectionName).findOneAndUpdate(filter, update, options);
		if (o && o.value) {
			return ObjectFactory.create(this.__type, o.value);
		}
		return null;
	}
	
	static find<T>(query: mongodb.FilterQuery<T> = {}, options?: mongodb.FindOneOptions): HfCursor<T> {
		const cursor = db.collection(this.__collectionName).find(query, options);
		return HfCursor.cast<T>(cursor, this.__type);
	}
}



export class HfCursor<T> extends Cursor<T> {
	protected __type;
	
	static cast<T>(cursor: Cursor<T>, type: any): HfCursor<T> {
		// “The use of __proto__ is controversial, and has been discouraged.”
		// see stackoverflow.com/a/32186367
		// see developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
		(<any>cursor).__proto__ = HfCursor.prototype;
		(<any>cursor).__type = type;
		return cursor as HfCursor<T>;
	}
	
	toArray(): Promise<T[]> {
		return super.toArray().then((objs) => {
			return objs.map((o) => {
				return ObjectFactory.create(this.__type, o);
			});
		});
	}
	
	forEach(__iterator: mongodb.IteratorCallback<T>, __callback: mongodb.EndCallback = () => {}) {
		super.forEach((o) => {
			const newObject = ObjectFactory.create(this.__type, o);
			__iterator(newObject);
		}, __callback);
	}
	
	
	
	
	on(event: string, listener: (...args) => void): this {
		if (event === 'data') {
			super.on('data', (o) => {
				const newObject = ObjectFactory.create(this.__type, o);
				listener(newObject);
			});
		}
		else {
			super.on(event, listener);
		}
		return this;
	}
	
	once(event: string, listener: (...args) => void): this {
		if (event === 'data') {
			super.once('data', (o) => {
				const newObject = ObjectFactory.create(this.__type, o);
				listener(newObject);
			});
		}
		else {
			super.once(event, listener);
		}
		return this;
	}
	
	//
	// Below: cursor methods are only here to make Typescript
	// know that they return the HfCursor object itself.
	// (We have checked that the mongo driver does the right thing underneath)
	//
	
	limit(value: number): HfCursor<T> {
		return super.limit(value) as HfCursor<T>;
	}
	
	skip(value: number): HfCursor<T> {
		return super.skip(value) as HfCursor<T>;
	}
	
	sort(keyOrList: string | Object[] | Object, direction?: number): HfCursor<T> {
		return super.sort(keyOrList, direction) as HfCursor<T>;
	}
	
	stream(options?: { transform?: Function }): HfCursor<T> {
		return super.stream(options) as HfCursor<T>;
	}
}