[sharedb] Add Agent class and extraDbs option for ShareDB constructor, loosen query params to any (#38109)

* [sharedb] Add Agent class and extraDbs option for ShareDB constructor

* Loosen query spec for Connection createFetchQuery and createSubscribeQuery
This commit is contained in:
Eric Hwang 2019-09-09 20:12:38 -07:00 committed by Mine Starks
parent 187683dd7f
commit e341115317
4 changed files with 117 additions and 11 deletions

View File

@ -3,10 +3,11 @@
// Definitions by: Steve Oney <https://github.com/soney>
// Eric Hwang <https://github.com/ericyhwang>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.1
// TypeScript Version: 3.0
/// <reference path="lib/sharedb.d.ts" />
import Agent = require('./lib/agent');
import * as ShareDB from './lib/sharedb';
interface PubSubOptions {
@ -19,7 +20,17 @@ interface Stream {
export = sharedb;
declare class sharedb {
constructor(options?: {db?: any, pubsub?: sharedb.PubSub, disableDocAction?: boolean, disableSpaceDelimitedActions?: boolean});
db: sharedb.DB;
pubsub: sharedb.PubSub;
extraDbs: {[extraDbName: string]: sharedb.ExtraDB};
constructor(options?: {
db?: any,
pubsub?: sharedb.PubSub,
extraDbs?: {[extraDbName: string]: sharedb.ExtraDB},
disableDocAction?: boolean,
disableSpaceDelimitedActions?: boolean
});
connect: (connection?: any, req?: any) => sharedb.Connection;
/**
* Registers a projection that can be used from clients just like a normal collection.
@ -30,7 +41,7 @@ declare class sharedb {
*/
addProjection(name: string, collection: string, fields: ProjectionFields): void;
listen(stream: any): void;
close(callback?: (err?: Error) => any): void;
close(callback?: BasicCallback): void;
/**
* Registers a server middleware function.
*
@ -50,7 +61,7 @@ declare namespace sharedb {
abstract class DB {
projectsSnapshots: boolean;
disableSubscribe: boolean;
close(callback?: () => void): void;
close(callback?: BasicCallback): void;
commit(collection: string, id: string, op: Op, snapshot: any, options: any, callback: (...args: any[]) => any): void;
getSnapshot(collection: string, id: string, fields: any, options: any, callback: (...args: any[]) => any): void;
getSnapshotBulk(collection: string, ids: string, fields: any, options: any, callback: (...args: any[]) => any): void;
@ -58,15 +69,24 @@ declare namespace sharedb {
getOpsToSnapshot(collection: string, id: string, from: number, snapshot: number, options: any, callback: (...args: any[]) => any): void;
getOpsBulk(collection: string, fromMap: any, toMap: any, options: any, callback: (...args: any[]) => any): void;
getCommittedOpVersion(collection: string, id: string, snapshot: any, op: any, options: any, callback: (...args: any[]) => any): void;
query(collection: string, query: Query, fields: any, options: any, callback: (...args: any[]) => any): void;
queryPoll(collection: string, query: Query, options: any, callback: (...args: any[]) => any): void;
queryPollDoc(collection: string, id: string, query: Query, options: any, callback: (...args: any[]) => any): void;
query: DBQueryMethod;
queryPoll(collection: string, query: any, options: any, callback: (...args: any[]) => any): void;
queryPollDoc(collection: string, id: string, query: any, options: any, callback: (...args: any[]) => any): void;
canPollDoc(): boolean;
skipPoll(): boolean;
}
class MemoryDB extends DB { }
// The DBs in `extraDbs` are only ever used for queries, so they don't need the other DB methods.
interface ExtraDB {
query: DBQueryMethod;
close(callback?: BasicCallback): void;
}
type DBQueryMethod = (collection: string, query: any, fields: ProjectionFields | undefined, options: any, callback: DBQueryCallback) => void;
type DBQueryCallback = (err: Error | null, snapshots: ShareDB.Snapshot[], extra?: any) => void;
abstract class PubSub {
private static shallowCopy(obj: any): any;
protected prefix?: string;
@ -132,7 +152,7 @@ declare namespace sharedb {
interface BaseContext {
action: keyof ActionContextMap;
agent: any;
agent: Agent;
backend: sharedb;
}
@ -220,3 +240,5 @@ interface SubmitRequest {
ops: ShareDB.Op[];
channels: string[] | null;
}
type BasicCallback = (err?: Error) => void;

47
types/sharedb/lib/agent.d.ts vendored Normal file
View File

@ -0,0 +1,47 @@
import { Duplex } from 'stream';
import { JSONObject } from './sharedb';
import ShareDbBackend = require('..');
export = Agent;
/**
* An `Agent` is the representation of a client's `Connection` state on the
* server. If the `Connection` was created through `backend.connect` (i.e. the
* client is running in the same Node process as the server), then the `Agent`
* associated with a `Connection` can be accessed through `connection.agent`.
*
* The `Agent` will be made available in all middleware requests. The
* `agent.custom` field is an object that can be used for storing arbitrary
* information for use in middleware.
*
* @see https://github.com/share/sharedb#class-sharedbagent
*/
declare class Agent {
backend: ShareDbBackend;
stream: Duplex & {
/**
* `true` if this is agent is handling a ShareDB client in the same
* Node process.
*/
isServer?: boolean;
};
/**
* Object for custom use in middleware to store app-specific state for a
* given client session. It is in memory only as long as the session is
* active, and it is passed to each middleware call.
*/
custom: Agent.Custom;
/**
* Sends a JSON-compatible message to the client for this agent.
*
* @param message
*/
send(message: JSONObject): void;
}
declare namespace Agent {
interface Custom {
[key: string]: unknown;
}
}

View File

@ -5,8 +5,8 @@ import * as ShareDB from './sharedb';
export class Connection {
constructor(ws: WebSocket | WS);
get(collectionName: string, documentID: string): Doc;
createFetchQuery(collectionName: string, query: string, options: {results?: Query[]}, callback: (err: Error, results: any) => any): Query;
createSubscribeQuery(collectionName: string, query: string, options: {results?: Query[]}, callback: (err: Error, results: any) => any): Query;
createFetchQuery(collectionName: string, query: any, options: {results?: Query[]}, callback: (err: Error, results: any[]) => void): Query;
createSubscribeQuery(collectionName: string, query: any, options: {results?: Query[]}, callback: (err: Error, results: any[]) => void): Query;
}
export type Doc = ShareDB.Doc;
export type Query = ShareDB.Query;

View File

@ -29,15 +29,37 @@ class WebSocketJSONStream extends Duplex {
}
}
const backend = new ShareDB();
class CustomExtraDb {
query(collection: string, query: any, fields: unknown, options: {customDbOption: boolean}, callback: ShareDB.DBQueryCallback) {
callback(null, [], {});
}
close() {}
}
const backend = new ShareDB({
extraDbs: {myDb: new CustomExtraDb()}
});
console.log(backend.db);
console.log(backend.pubsub);
console.log(backend.extraDbs);
backend.addProjection('notes_minimal', 'notes', {title: true, creator: true, lastUpdateTime: true});
// Test module augmentation to attach custom typed properties to `agent.custom`.
import _ShareDbAgent = require('sharedb/lib/agent');
declare module 'sharedb/lib/agent' {
interface Custom {
user?: {id: string};
}
}
// Exercise middleware (backend.use)
type SubmitRelatedActions = 'afterSubmit' | 'apply' | 'commit' | 'submit';
const submitRelatedActions: SubmitRelatedActions[] = ['afterSubmit', 'apply', 'commit', 'submit'];
for (const action of submitRelatedActions) {
backend.use(action, (request, callback) => {
if (request.agent.custom.user) {
console.log(request.agent.custom.user.id);
}
console.log(
request.action,
request.agent,
@ -56,9 +78,16 @@ for (const action of submitRelatedActions) {
});
}
backend.use('connect', (context, callback) => {
// `context.req` is the HTTP / websocket request.
// This assumes that some request middleware has handled auth and popuplated `req.userId`.
if (context.req.userId) {
context.agent.custom.user = {id: context.req.userId};
}
console.log(
context.action,
context.agent,
context.agent.backend === context.backend,
context.agent.stream.isServer,
context.backend,
context.stream,
context.req,
@ -172,4 +201,12 @@ function startClient(callback) {
doc.submitOp([{p: ['numClicks'], na: 1}]);
callback();
});
// sharedb-mongo query object
connection.createSubscribeQuery('examples', {numClicks: {$gte: 5}}, null, (err, results) => {
console.log(err, results);
});
// SQL-ish query adapter that takes a string query condition
connection.createSubscribeQuery('examples', 'numClicks >= 5', null, (err, results) => {
console.log(err, results);
});
}