diff --git a/types/sharedb/index.d.ts b/types/sharedb/index.d.ts index 0f0f5575fd..2f78d1d3dc 100644 --- a/types/sharedb/index.d.ts +++ b/types/sharedb/index.d.ts @@ -1,7 +1,9 @@ // Type definitions for sharedb 1.0 // Project: https://github.com/share/sharedb // Definitions by: Steve Oney +// Eric Hwang // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 /// @@ -18,20 +20,33 @@ export = sharedb; declare class sharedb { constructor(options?: {db?: any, pubsub?: sharedb.PubSub, disableDocAction?: boolean, disableSpaceDelimitedActions?: boolean}); - connect: () => sharedb.Connection; - addProjection(name: string, collection: string, fields: {}): any; + connect: (connection?: any, req?: any) => sharedb.Connection; + /** + * Registers a projection that can be used from clients just like a normal collection. + * + * @param name name of the projection + * @param collection name of the backing collection + * @param fields field whitelist for the projection + */ + addProjection(name: string, collection: string, fields: ProjectionFields): void; listen(stream: any): void; - close(callback?: (err: Error) => any): void; - use(action: sharedb.UseAction, fn: sharedb.UseCallback): void; + close(callback?: (err?: Error) => any): void; + /** + * Registers a server middleware function. + * + * @param action name of an action from https://github.com/share/sharedb#middlewares + * @param fn listener invoked when the specified action occurs + */ + use( + action: A, + fn: (context: middleware.ActionContextMap[A], callback: (err?: any) => void) => void, + ): void; static types: { register: (type: { name?: string, uri?: string, [key: string]: any}) => void; }; } declare namespace sharedb { - type UseAction = 'connect' | 'op' | 'doc' | 'query' | 'submit' | 'apply' | 'commit' | 'after submit' | 'receive'; - type UseCallback = ((request: {action: UseAction, agent: any, req: any, collection: string, id: string, query: any, op: ShareDB.RawOp}, callback: () => void) => void); - abstract class DB { projectsSnapshots: boolean; disableSubscribe: boolean; @@ -100,3 +115,108 @@ declare namespace sharedb { type Path = ShareDB.Path; type ShareDBSourceOptions = ShareDB.ShareDBSourceOptions; } + +declare namespace middleware { + interface ActionContextMap { + afterSubmit: SubmitContext; + apply: ApplyContext; + commit: CommitContext; + connect: ConnectContext; + doc: DocContext; // Deprecated, use 'readSnapshots' instead. + op: OpContext; + query: QueryContext; + readSnapshots: ReadSnapshotsContext; + receive: ReceiveContext; + reply: ReplyContext; + submit: SubmitContext; + } + + interface BaseContext { + action: keyof ActionContextMap; + agent: any; + backend: sharedb; + } + + interface ApplyContext extends BaseContext, SubmitRequest { + } + + interface CommitContext extends BaseContext, SubmitRequest { + } + + interface ConnectContext extends BaseContext { + stream: any; + req: any; // Property always exists, value may be undefined + } + + interface DocContext extends BaseContext { + collection: string; + id: string; + snapshot: ShareDB.Snapshot; + } + + interface OpContext extends BaseContext { + collection: string; + id: string; + op: ShareDB.Op; + } + + interface QueryContext extends BaseContext { + index: string; + collection: string; + projection: Projection | undefined; + fields: ProjectionFields | undefined; + channel: string; + query: ShareDB.JSONObject; + options: ShareDB.JSONObject; + db: sharedb.DB | null; + snapshotProjection: Projection | null; + } + + interface ReadSnapshotsContext extends BaseContext { + collection: string; + snapshots: ShareDB.Snapshot[]; + snapshotType: SnapshotType; + } + + interface ReceiveContext extends BaseContext { + data: ShareDB.JSONObject; // ClientRequest, but before any validation + } + + interface ReplyContext extends BaseContext { + request: ShareDB.ClientRequest; + reply: ShareDB.JSONObject; + } + + type SnapshotType = 'current' | 'byVersion' | 'byTimestamp'; + + interface SubmitContext extends BaseContext, SubmitRequest { + } +} + +interface Projection { + target: string; + fields: ProjectionFields; +} + +interface ProjectionFields { + [propertyName: string]: true; +} + +interface SubmitRequest { + index: string; + projection: Projection | undefined; + collection: string; + id: string; + op: sharedb.Op; + options: any; + start: number; + + saveMilestoneSnapshot: boolean | null; + suppressPublish: boolean | null; + maxRetries: number | null; + retries: number; + + snapshot: ShareDB.Snapshot | null; + ops: ShareDB.Op[]; + channels: string[] | null; +} diff --git a/types/sharedb/lib/sharedb.d.ts b/types/sharedb/lib/sharedb.d.ts index 426eac5a0e..01eace14c6 100644 --- a/types/sharedb/lib/sharedb.d.ts +++ b/types/sharedb/lib/sharedb.d.ts @@ -1,7 +1,26 @@ import { EventEmitter } from 'events'; +export type JSONValue = string | number | boolean | null | JSONObject | JSONArray; +export interface JSONObject { + [name: string]: JSONValue; +} +export interface JSONArray extends Array {} + export type Path = ReadonlyArray; -export type Snapshot = number; +export interface Snapshot { + id: string; + v: number; + type: string | null; + data: JSONObject | undefined; + m: SnapshotMeta | null; +} + +export interface SnapshotMeta { + ctime: number; + mtime: number; + // Users can use server middleware to add additional metadata to snapshots. + [key: string]: JSONValue; +} export interface AddNumOp { p: Path; na: number; } @@ -83,3 +102,12 @@ export class Query extends EventEmitter { removeListener(event: QueryEvent, listener: (...args: any[]) => any): this; destroy(): void; } + +export type RequestAction = 'qf' | 'qs' | 'qu' | 'bf' | 'bs' | 'bu' | 'f' | 's' | 'u' | 'op' | 'nf' | 'nt'; + +export interface ClientRequest { + /** Short name of the request's action */ + a: RequestAction; + + [propertyName: string]: JSONValue; +} diff --git a/types/sharedb/sharedb-tests.ts b/types/sharedb/sharedb-tests.ts index 55c3dc797d..da4e5ed948 100644 --- a/types/sharedb/sharedb-tests.ts +++ b/types/sharedb/sharedb-tests.ts @@ -30,7 +30,104 @@ class WebSocketJSONStream extends Duplex { } const backend = new ShareDB(); + +backend.addProjection('notes_minimal', 'notes', {title: true, creator: true, lastUpdateTime: true}); + +// 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) => { + console.log( + request.action, + request.agent, + request.backend, + request.index, + request.collection, + request.projection, + request.id, + request.op, + request.options, + request.snapshot, + request.ops, + request.channels, + ); + callback(); + }); +} +backend.use('connect', (context, callback) => { + console.log( + context.action, + context.agent, + context.backend, + context.stream, + context.req, + ); + callback(); +}); +backend.use('op', (context, callback) => { + console.log( + context.action, + context.agent, + context.backend, + context.collection, + context.id, + context.op, + ); + callback(); +}); +backend.use('query', (context, callback) => { + console.log( + context.action, + context.agent, + context.backend, + context.index, + context.collection, + context.projection, + context.fields, + context.channel, + context.query, + context.options, + context.snapshotProjection, + ); + callback(); +}); +backend.use('receive', (context, callback) => { + console.log( + context.action, + context.agent, + context.backend, + context.data, + ); + callback(); +}); +backend.use('reply', (context, callback) => { + console.log( + context.action, + context.agent, + context.backend, + context.request, + context.reply, + ); + callback(); +}); +backend.use('readSnapshots', (context, callback) => { + console.log( + context.action, + context.agent, + context.backend, + context.collection, + context.snapshots, + context.snapshotType, + ); + callback(); +}); + const connection = backend.connect(); +const netRequest = {}; // Passed through to 'connect' middleware, not used by sharedb itself +const connectionWithReq = backend.connect(null, netRequest); +const reboundConnection = backend.connect(backend.connect(), netRequest); + const doc = connection.get('examples', 'counter'); doc.fetch((err) => {