From e060dd0398d5aedf8d01330749bbd05366c849e8 Mon Sep 17 00:00:00 2001 From: Eric Hwang Date: Fri, 22 Mar 2019 18:28:50 -0700 Subject: [PATCH 1/2] [sharedb] Update type signatures for Backend #use and #addProjection Backend#use(action, listener) now gets rich type information on listener params, by using the same pattern as addEventListener in TypeScript's built-in DOM type definitions. This also tweaks some method param types on the base sharedb Backend class, to better match the JS. --- types/sharedb/index.d.ts | 128 +++++++++++++++++++++++++++++++-- types/sharedb/lib/sharedb.d.ts | 14 +++- types/sharedb/sharedb-tests.ts | 87 ++++++++++++++++++++++ 3 files changed, 221 insertions(+), 8 deletions(-) diff --git a/types/sharedb/index.d.ts b/types/sharedb/index.d.ts index 0f0f5575fd..7657075f8e 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,102 @@ 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; + receive: ReceiveContext; + readSnapshots: ReadSnapshotsContext; + 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: any; + options: any; + db: sharedb.DB | null; + snapshotProjection: Projection | null; + } + + interface ReceiveContext extends BaseContext { + data: any; + } + + interface ReadSnapshotsContext extends BaseContext { + collection: string; + snapshots: ShareDB.Snapshot[]; + snapshotType: SnapshotType; + } + + 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..169b7e6674 100644 --- a/types/sharedb/lib/sharedb.d.ts +++ b/types/sharedb/lib/sharedb.d.ts @@ -1,7 +1,19 @@ import { EventEmitter } from 'events'; export type Path = ReadonlyArray; -export type Snapshot = number; +export interface Snapshot { + id: string; + v: number; + type: string | null; + data: any; // JSON object, could be undefined + m: SnapshotMeta | null; +} + +export interface SnapshotMeta { + ctime: number; + mtime: number; + [key: string]: any; +} export interface AddNumOp { p: Path; na: number; } diff --git a/types/sharedb/sharedb-tests.ts b/types/sharedb/sharedb-tests.ts index 55c3dc797d..a4e35d3507 100644 --- a/types/sharedb/sharedb-tests.ts +++ b/types/sharedb/sharedb-tests.ts @@ -30,7 +30,94 @@ 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('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) => { From 9b7c019dc8ec6869c9f2f3e90f11e319e0048477 Mon Sep 17 00:00:00 2001 From: Eric Hwang Date: Wed, 27 Mar 2019 22:00:32 -0700 Subject: [PATCH 2/2] Add "reply" middleware, tighten up some any types --- types/sharedb/index.d.ts | 20 +++++++++++++------- types/sharedb/lib/sharedb.d.ts | 20 ++++++++++++++++++-- types/sharedb/sharedb-tests.ts | 10 ++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/types/sharedb/index.d.ts b/types/sharedb/index.d.ts index 7657075f8e..2f78d1d3dc 100644 --- a/types/sharedb/index.d.ts +++ b/types/sharedb/index.d.ts @@ -125,8 +125,9 @@ declare namespace middleware { doc: DocContext; // Deprecated, use 'readSnapshots' instead. op: OpContext; query: QueryContext; - receive: ReceiveContext; readSnapshots: ReadSnapshotsContext; + receive: ReceiveContext; + reply: ReplyContext; submit: SubmitContext; } @@ -165,22 +166,27 @@ declare namespace middleware { projection: Projection | undefined; fields: ProjectionFields | undefined; channel: string; - query: any; - options: any; + query: ShareDB.JSONObject; + options: ShareDB.JSONObject; db: sharedb.DB | null; snapshotProjection: Projection | null; } - interface ReceiveContext extends BaseContext { - data: any; - } - 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 { diff --git a/types/sharedb/lib/sharedb.d.ts b/types/sharedb/lib/sharedb.d.ts index 169b7e6674..01eace14c6 100644 --- a/types/sharedb/lib/sharedb.d.ts +++ b/types/sharedb/lib/sharedb.d.ts @@ -1,18 +1,25 @@ 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 interface Snapshot { id: string; v: number; type: string | null; - data: any; // JSON object, could be undefined + data: JSONObject | undefined; m: SnapshotMeta | null; } export interface SnapshotMeta { ctime: number; mtime: number; - [key: string]: any; + // Users can use server middleware to add additional metadata to snapshots. + [key: string]: JSONValue; } export interface AddNumOp { p: Path; na: number; } @@ -95,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 a4e35d3507..da4e5ed948 100644 --- a/types/sharedb/sharedb-tests.ts +++ b/types/sharedb/sharedb-tests.ts @@ -101,6 +101,16 @@ backend.use('receive', (context, callback) => { ); 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,