forked from tuhinc/rethink-livedata
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrethink.js
365 lines (307 loc) · 10.2 KB
/
rethink.js
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
var EventEmitter = Npm.require('events').EventEmitter;
var url = Npm.require('url');
var r = Npm.require('rethinkdb');
var Fiber = Npm.require('fibers');
var path = Npm.require('path');
var Future = Npm.require(path.join('fibers', 'future'));
// new comment
_Rethink = function(url) {
var self = this;
self.table_queue = [];
r.connect({
host:'localhost',
port: 28015,
db: 'test'
}, function(err, connection){
if(err) throw err;
console.log('connected');
self.connection = connection;
});
self.invalidator = new Meteor.Invalidator(self);
};
_.extend(_Rethink.prototype, Object.create(EventEmitter.prototype));
_Rethink.prototype._createTable = function(tableName) {
var self = this;
self.tableName = tableName;
r.db('test').tableCreate(tableName).run(self.connection, function(err, cursor) {
console.log('success!', err);
});
};
//TODO:: self.selector is missing -- rethink doesn't have selectors
// so another mechanism will have to be used...
var RethinkCursorDescription = function(tableName, options) {
var self = this;
self.tableName = tableName;
self.options = options || {};
};
var RethinkCursor = function(rethink, cursorDescription) {
var self = this;
self._rethink = rethink;
self._cursorDescription = cursorDescription;
};
RethinkCursor.prototype.init = function(rethinkCursor, table) {
var self = this;
console.log('init was called!');
self._cursor = rethinkCursor;
self._table = table;
self._used = false;
self.emit('ready');
};
_.extend(RethinkCursor.prototype, Object.create(EventEmitter.prototype));
_Rethink.prototype.insert = function(tableName, document) {
var self = this;
console.log('insert function called on the server side');
if (!document._id) {
document._id = Random.id();
}
// this is not where it actually gets inserted -- it gets passed
// to the table constructor prototype where it gets sluttily passed
// around some more, validated, and then finally called somewhere
// I have yet to figure out where....
r.table(self.tableName).insert(document).run(self.connection, function(err, cursor) {
console.log("successfully inserted into server side db");
});
//there has to be something useful you can do here with the cursor
};
_Rethink.prototype.find = function (tableName) {
var self = this;
return new RethinkCursor(self, new RethinkCursorDescription(tableName));
};
// TODO:: make this description more clear -->
// This is RethinkCursor's own publish function -- Meteor's publish function
// does not need to be replaced because the LivedataSubscription class calls
// _publishCursor in the context of the cursor that is returned as a result
// of the handler (which will be a Rethink cursor)
// var fence = Meteor._CurrentWriteFence.get();
// console.log('this is the write fence', fence);
RethinkCursor.prototype._publishCursor = function (sub, handler) {
var self = this;
var table = self._cursorDescription.tableName;
var observeHandle = self.observeChanges({
added: function (id, fields) {
// these result in DDP messages being sent over the wire
console.log('added triggered on the server side!');
sub.added(table, id, fields);
},
changed: function (id, fields) {
sub.changed(table, id, fields);
},
removed: function (id) {
sub.removed(table, id);
}
});
sub.onStop(function () {
observeHandle.stop();
});
};
RethinkCursor.prototype._getTableName = function() {
var self = this;
return self._cursorDescription.tableName;
};
RethinkCursor.prototype._added = function(doc) {
this._fiberEmit('added', doc_id, doc);
};
RethinkCursor.prototype._fiberEmit = function(event, doc) {
var self = this;
var args = arguments;
Fiber(function() {
self.emit.apply(self, args);
}).run();
};
RethinkCursor.prototype.observe = function (callbacks) {
var self = this;
return LocalTable._observeFromObserveChanges(self, callbacks);
};
RethinkCursor.prototype.observeChanges = function (callbacks) {
var self = this;
console.log('observeChanges was called');
//TODO: ordered?
return self._rethink._observeChanges(
self._cursorDescription, callbacks, self);
};
//TODO: missing 'useTransform' argument
//TODO:: this will probably need to be wrapped in a future
//TODO:: what should I do with this beautiful callback I've been given?
_Rethink.prototype._getCursor = function(tableName) {
var self = this;
var future = new Future();
r.db('test').table(tableName).run(self.connection, future.resolver());
return future.wait();
};
_.each(['each', 'map', 'rewind', 'fetch', 'count'], function(method) {
RethinkCursor.prototype[method] = function() {
var self = this;
if (!self._synchronousCursor) {
self._synchronousCursor = self._rethink._createSynchronousCursor(
self._cursorDescription);
}
while (!self._synchronousCursor) {
//don't do anything
}
console.log('made it out of while loop');
return self._synchronousCursor[method].apply(
self._synchronousCursor, arguments);
};
});
_Rethink.prototype._createSynchronousCursor = function (cursorDescription) {
var fiber = Fiber.current;
var self = this;
// var options = cursorDescription.options;
var tableName = cursorDescription.tableName;
var dbCursor = self._getCursor(tableName);
return new RethinkSynchronousCursor(dbCursor);
};
var RethinkSynchronousCursor = function(dbCursor) {
var self = this;
self._dbCursor = dbCursor;
self._synchronousNextObject = Future.wrap(
dbCursor.next.bind(dbCursor), 0);
self._visitedIds = {};
};
_.extend(RethinkSynchronousCursor.prototype, {
_next: function() {
var self = this;
while (true) {
var doc = self._synchronousNextObject().wait();
if (!doc || !doc._id) {
return null;
}
var strId = Meteor.idStringify(doc._id);
if (self._visitedIds[strId]) continue;
self._visitedIds[strId] = true;
return doc;
}
},
each: function(callback) {
var self = this;
// We implement the loop ourself instead of using self._dbCursor.each,
// because "each" will call its callback outside of a fiber which makes it
// much more complex to make this function synchronous.
while (true) {
var doc = self._next();
if (!doc) {
return;
}
callback(doc);
}
},
map: function (callback) {
var self = this;
var res = [];
self.each(function (doc) {
res.push(callback(doc));
});
return res;
},
fetch: function() {
var self = this;
return self.map(_.identity);
},
count: function() {
var self = this;
return self._synchronousCount().wait();
},
hasNext: function() {
var self = this;
return self.hasNext();
},
getRawObjects: function (ordered) {
var self = this;
if (ordered) {
return self.fetch();
} else {
var results = {};
self.each(function (doc) {
results[doc._id] = doc;
});
return results;
}
}
});
var nextObserveHandleId = 1;
var ObserveHandle = function (liveResultsSet, callbacks) {
var self = this;
self._liveResultsSet = liveResultsSet;
self._added = callbacks.added;
self._addedBefore = callbacks.addedBefore;
self._changed = callbacks.changed;
self._removed = callbacks.removed;
self._moved = callbacks.moved;
self._movedBefore = callbacks.movedBefore;
self._observeHandleId = nextObserveHandleId++;
};
ObserveHandle.prototype.stop = function () {
var self = this;
self._liveResultsSet._removeObserveHandle(self);
self._liveResultsSet = null;
};
_Rethink.prototype._observeChanges = function (
cursorDescription, callbacks, cursor) {
console.log('_observeChanges was called');
var self = this;
['added', 'changed', 'removed'].forEach(function(event) {
if (typeof(callbacks[event]) === 'function') {
cursor.on(event, callbacks[event]);
}
});
console.log('this is what fetch returns!', cursor.each(console.log));
// Use the tableName to get the table from the Tables map
// self.invalidator.addCursor(cursor);
// cursor.each(function(item) {
// cursor._added(item);
// });
console.log('cursor as it gets passed into _observeChanges: ', cursor);
// cursor._added();
// var ordered = false;
// var observeKey = JSON.stringify(
// _.extend({ordered: ordered}, cursorDescription));
// var liveResultsSet;
// var observeHandle;
// var newlyCreated = false;
// console.log('callbacks!!!: ', callbacks);
// if (newlyCreated) {
// console.log('newlycreated');
// liveResultsSet._addFirstObserveHandle(observeHandle);
// } else {
// callbacks.added();
// }
// return observeHandle;
};
_.extend(Meteor, {
_Rethink: _Rethink
});
// TODO :: write logic for server side publish function
// and see if long polling can be avoided by using Rethink's eventing system
// TODO :: finish implementing Public API
// Returns the Rethink table object; may yield.
// _Rethink.prototype._getTable = function(tableName) {
// var self = this;
// var future = new Future();
// if (self.db) {
// self.db.collection(tableName, future.resolver());
// } else {
// self.table_queue.push({name: tableName,
// callback: future.resolver()});
// }
// return future.wait();
// };
// _Rethink.prototype._maybeBeginWrite = function () {
// var self = this;
// // var fence = Meteor._CurrentWriteFence.get();
// if (fence) {
// return fence.beginWrite();
// }
// else {
// return {committed: function () {}};
// }
// };
//////////// Public API //////////
//TODO -- provide support for durability / returnVals arguments
// Insert returns an object that contains the following attributes:
// inserted - the number of documents that were succesfully inserted;
// replaced - the number of documents that were updated when upsert is used;
// unchanged - the number of documents that would have been modified, except that the new value was the same as the old value when doing an upsert;
// errors - the number of errors encountered while inserting;
// if errors where encountered while inserting, first_error contains the text of the first error;
// generated_keys - a list of generated primary key values;
// deleted and skipped - 0 for an insert operation.