Skip to content

Commit fe1b7c0

Browse files
authored
Merge branch 'master' into fix-whenNothingPending
2 parents 065ed9d + 5394fa4 commit fe1b7c0

30 files changed

+1602
-154
lines changed

README.md

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Community Provided Pub/Sub Adapters
112112
### Listening to WebSocket connections
113113

114114
```js
115-
var WebSocketJSONStream = require('websocket-json-stream');
115+
var WebSocketJSONStream = require('@teamwork/websocket-json-stream');
116116

117117
// 'ws' is a websocket server connection, as passed into
118118
// new (require('ws').Server).on('connection', ...)
@@ -149,17 +149,24 @@ Register a new middleware.
149149
* `'afterSubmit'`: An operation was successfully submitted to
150150
the database.
151151
* `'receive'`: Received a message from a client
152-
* `fn` _(Function(request, callback))_
152+
* `'reply'`: About to send a non-error reply to a client message
153+
* `fn` _(Function(context, callback))_
153154
Call this function at the time specified by `action`.
154-
`request` contains a subset of the following properties, as relevant for the action:
155-
* `action`: The action this middleware is handing
156-
* `agent`: An object corresponding to the server agent handing this client
157-
* `req`: The HTTP request being handled
158-
* `collection`: The collection name being handled
159-
* `id`: The document id being handled
160-
* `snapshots`: The retrieved snapshots for the `readSnapshots` action
161-
* `query`: The query object being handled
162-
* `op`: The op being handled
155+
* `context` will always have the following properties:
156+
* `action`: The action this middleware is hanlding
157+
* `agent`: A reference to the server agent handling this client
158+
* `backend`: A reference to this ShareDB backend instance
159+
* `context` can also have additional properties, as relevant for the action:
160+
* `collection`: The collection name being handled
161+
* `id`: The document id being handled
162+
* `op`: The op being handled
163+
* `req`: HTTP request being handled, if provided to `share.listen` (for 'connect')
164+
* `stream`: The duplex Stream provided to `share.listen` (for 'connect')
165+
* `query`: The query object being handled (for 'query')
166+
* `snapshots`: Array of retrieved snapshots (for 'readSnapshots')
167+
* `data`: Received client message (for 'receive')
168+
* `request`: Client message being replied to (for 'reply')
169+
* `reply`: Reply to be sent to the client (for 'reply')
163170

164171
### Projections
165172

@@ -182,6 +189,27 @@ share.addProjection('users_limited', 'users', { name:true, profileUrl:true });
182189

183190
Note that only the [JSON0 OT type](https://github.com/ottypes/json0) is supported for projections.
184191

192+
### Logging
193+
194+
By default, ShareDB logs to `console`. This can be overridden if you wish to silence logs, or to log to your own logging driver or alert service.
195+
196+
Methods can be overridden by passing a [`console`-like object](https://developer.mozilla.org/en-US/docs/Web/API/console) to `logger.setMethods`:
197+
198+
```javascript
199+
var ShareDB = require('sharedb');
200+
ShareDB.logger.setMethods({
201+
info: () => {}, // Silence info
202+
warn: () => alerts.warn(arguments), // Forward warnings to alerting service
203+
error: () => alerts.critical(arguments) // Remap errors to critical alerts
204+
});
205+
```
206+
207+
ShareDB only supports the following logger methods:
208+
209+
- `info`
210+
- `warn`
211+
- `error`
212+
185213
### Shutdown
186214

187215
`share.close(callback)`
@@ -235,7 +263,28 @@ Get a read-only snapshot of a document at the requested version.
235263
* `id` _(String)_
236264
ID of the snapshot
237265
* `version` _(number) [optional]_
238-
The version number of the desired snapshot
266+
The version number of the desired snapshot. If `null`, the latest version is fetched.
267+
* `callback` _(Function)_
268+
Called with `(error, snapshot)`, where `snapshot` takes the following form:
269+
270+
```javascript
271+
{
272+
id: string; // ID of the snapshot
273+
v: number; // version number of the snapshot
274+
type: string; // the OT type of the snapshot, or null if it doesn't exist or is deleted
275+
data: any; // the snapshot
276+
}
277+
```
278+
279+
`connection.fetchSnapshotByTimestamp(collection, id, timestamp, callback): void;`
280+
Get a read-only snapshot of a document at the requested version.
281+
282+
* `collection` _(String)_
283+
Collection name of the snapshot
284+
* `id` _(String)_
285+
ID of the snapshot
286+
* `timestamp` _(number) [optional]_
287+
The timestamp of the desired snapshot. The returned snapshot will be the latest snapshot before the provided timestamp. If `null`, the latest version is fetched.
239288
* `callback` _(Function)_
240289
Called with `(error, snapshot)`, where `snapshot` takes the following form:
241290

@@ -267,7 +316,7 @@ Populate the fields on `doc` with a snapshot of the document from the server, an
267316
fire events on subsequent changes.
268317

269318
`doc.ingestSnapshot(snapshot, callback)`
270-
Ingest snapshot data. This data must include a version, snapshot and type. This method is generally called interally as a result of fetch or subscribe and not directly. However, it may be called directly to pass data that was transferred to the client external to the client's ShareDB connection, such as snapshot data sent along with server rendering of a webpage.
319+
Ingest snapshot data. The `snapshot` param must include the fields `v` (doc version), `data`, and `type` (OT type). This method is generally called interally as a result of fetch or subscribe and not directly from user code. However, it may still be called directly from user code to pass data that was transferred to the client external to the client's ShareDB connection, such as snapshot data sent along with server rendering of a webpage.
271320

272321
`doc.destroy()`
273322
Unsubscribe and stop firing events.
@@ -358,6 +407,27 @@ after a sequence of diffs are handled.
358407
`query.on('extra', function() {...}))`
359408
(Only fires on subscription queries) `query.extra` changed.
360409

410+
### Logging
411+
412+
By default, ShareDB logs to `console`. This can be overridden if you wish to silence logs, or to log to your own logging driver or alert service.
413+
414+
Methods can be overridden by passing a [`console`-like object](https://developer.mozilla.org/en-US/docs/Web/API/console) to `logger.setMethods`
415+
416+
```javascript
417+
var ShareDB = require('sharedb/lib/client');
418+
ShareDB.logger.setMethods({
419+
info: () => {}, // Silence info
420+
warn: () => alerts.warn(arguments), // Forward warnings to alerting service
421+
error: () => alerts.critical(arguments) // Remap errors to critical alerts
422+
});
423+
```
424+
425+
ShareDB only supports the following logger methods:
426+
427+
- `info`
428+
- `warn`
429+
- `error`
430+
361431

362432
## Error codes
363433

@@ -422,3 +492,5 @@ The `41xx` and `51xx` codes are reserved for use by ShareDB DB adapters, and the
422492
* 5018 - Required QueryEmitter listener not assigned
423493
* 5019 - getMilestoneSnapshot MilestoneDB method unimplemented
424494
* 5020 - saveMilestoneSnapshot MilestoneDB method unimplemented
495+
* 5021 - getMilestoneSnapshotAtOrBeforeTime MilestoneDB method unimplemented
496+
* 5022 - getMilestoneSnapshotAtOrAfterTime MilestoneDB method unimplemented

examples/textarea/client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ var doc = connection.get('examples', 'textarea');
3131
doc.subscribe(function(err) {
3232
if (err) throw err;
3333

34-
var binding = new StringBinding(element, doc);
34+
var binding = new StringBinding(element, doc, ['content']);
3535
binding.setup();
3636
});

examples/textarea/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function createDoc(callback) {
1414
doc.fetch(function(err) {
1515
if (err) throw err;
1616
if (doc.type === null) {
17-
doc.create('', callback);
17+
doc.create({ content: '' }, callback);
1818
return;
1919
}
2020
callback();

lib/agent.js

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var hat = require('hat');
22
var util = require('./util');
33
var types = require('./types');
4+
var logger = require('./logger');
45

56
/**
67
* Agent deserializes the wire protocol messages received from the stream and
@@ -47,7 +48,7 @@ module.exports = Agent;
4748
// Close the agent with the client.
4849
Agent.prototype.close = function(err) {
4950
if (err) {
50-
console.warn('Agent closed due to error', this.clientId, err.stack || err);
51+
logger.warn('Agent closed due to error', this.clientId, err.stack || err);
5152
}
5253
if (this.closed) return;
5354
// This will end the writable stream and emit 'finish'
@@ -95,7 +96,7 @@ Agent.prototype._subscribeToStream = function(collection, id, stream) {
9596
// Log then silently ignore errors in a subscription stream, since these
9697
// may not be the client's fault, and they were not the result of a
9798
// direct request by the client
98-
console.error('Doc subscription stream error', collection, id, data.error);
99+
logger.error('Doc subscription stream error', collection, id, data.error);
99100
return;
100101
}
101102
if (agent._isOwnOp(collection, data)) return;
@@ -104,7 +105,7 @@ Agent.prototype._subscribeToStream = function(collection, id, stream) {
104105
stream.on('end', function() {
105106
// The op stream is done sending, so release its reference
106107
var streams = agent.subscribedDocs[collection];
107-
if (!streams) return;
108+
if (!streams || streams[id] !== stream) return;
108109
delete streams[id];
109110
if (util.hasKeys(streams)) return;
110111
delete agent.subscribedDocs[collection];
@@ -137,7 +138,7 @@ Agent.prototype._subscribeToQuery = function(emitter, queryId, collection, query
137138
// Log then silently ignore errors in a subscription stream, since these
138139
// may not be the client's fault, and they were not the result of a
139140
// direct request by the client
140-
console.error('Query subscription stream error', collection, query, err);
141+
logger.error('Query subscription stream error', collection, query, err);
141142
};
142143

143144
emitter.onOp = function(op) {
@@ -186,23 +187,29 @@ Agent.prototype._sendOps = function(collection, id, ops) {
186187
}
187188
};
188189

190+
function getReplyErrorObject(err) {
191+
if (typeof err === 'string') {
192+
return {
193+
code: 4001,
194+
message: err
195+
};
196+
} else {
197+
if (err.stack) {
198+
logger.warn(err.stack);
199+
}
200+
return {
201+
code: err.code,
202+
message: err.message
203+
};
204+
}
205+
}
206+
189207
Agent.prototype._reply = function(request, err, message) {
208+
var agent = this;
209+
var backend = agent.backend;
190210
if (err) {
191-
if (typeof err === 'string') {
192-
request.error = {
193-
code: 4001,
194-
message: err
195-
};
196-
} else {
197-
if (err.stack) {
198-
console.warn(err.stack);
199-
}
200-
request.error = {
201-
code: err.code,
202-
message: err.message
203-
};
204-
}
205-
this.send(request);
211+
request.error = getReplyErrorObject(err);
212+
agent.send(request);
206213
return;
207214
}
208215
if (!message) message = {};
@@ -216,7 +223,15 @@ Agent.prototype._reply = function(request, err, message) {
216223
if (request.b && !message.data) message.b = request.b;
217224
}
218225

219-
this.send(message);
226+
var middlewareContext = {request: request, reply: message};
227+
backend.trigger(backend.MIDDLEWARE_ACTIONS.reply, agent, middlewareContext, function(err) {
228+
if (err) {
229+
request.error = getReplyErrorObject(err);
230+
agent.send(request);
231+
} else {
232+
agent.send(middlewareContext.reply);
233+
}
234+
});
220235
};
221236

222237
// Start processing events from the stream
@@ -302,6 +317,8 @@ Agent.prototype._handleMessage = function(request, callback) {
302317
return this._submit(request.c, request.d, op, callback);
303318
case 'nf':
304319
return this._fetchSnapshot(request.c, request.d, request.v, callback);
320+
case 'nt':
321+
return this._fetchSnapshotByTimestamp(request.c, request.d, request.ts, callback);
305322
default:
306323
callback({code: 4000, message: 'Invalid or unknown message'});
307324
}
@@ -588,3 +605,7 @@ Agent.prototype._createOp = function(request) {
588605
Agent.prototype._fetchSnapshot = function (collection, id, version, callback) {
589606
this.backend.fetchSnapshot(this, collection, id, version, callback);
590607
};
608+
609+
Agent.prototype._fetchSnapshotByTimestamp = function (collection, id, timestamp, callback) {
610+
this.backend.fetchSnapshotByTimestamp(this, collection, id, timestamp, callback);
611+
};

0 commit comments

Comments
 (0)