diff --git a/types/sharedb/index.d.ts b/types/sharedb/index.d.ts new file mode 100644 index 0000000000..7b34c85583 --- /dev/null +++ b/types/sharedb/index.d.ts @@ -0,0 +1,63 @@ +// Type definitions for sharedb 1.0 +// Project: https://github.com/share/sharedb +// Definitions by: Steve Oney +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/// + +import { Doc, Query, Error, Action, RawOp, Op } from './lib/sharedb'; + +type UseCallback = ((request: {action: Action, agent: any, req: any, collection: string, id: string, query: any, op: RawOp}, callback: () => void) => void); + +interface PubSubOptions { + prefix?: string; +} +interface Stream { + id: string; +} + +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; + listen(stream: any): void; + close(callback?: (err: Error) => any): void; + use(action: Action, fn: UseCallback); + types: { + register: () => void + }; +} + +declare namespace sharedb { + abstract class PubSub { + private static shallowCopy(obj: any); + protected prefix?: string; + protected nextStreamId: number; + protected streamsCount: number; + protected streams: { + [channel: string]: Stream; + }; + protected subscribed: { + [channel: string]: boolean; + }; + protected constructor(options?: PubSubOptions); + close(callback?: (err: Error|null) => void): void; + publish(channels: string[], data: {[k: string]: any}, callback: (err: Error | null) => void): void; + subscribe(channel: string, callback: (err: Error | null, stream?: Stream) => void): void; + protected abstract _subscribe(channel: string, callback: (err: Error | null) => void): void; + protected abstract _unsubscribe(channel: string, callback: (err: Error | null) => void): void; + protected abstract _publish(channels: string[], data: any, callback: (err: Error | null) => void): void; + protected _emit(channel: string, data: {[k: string]: any}): void; + private _createStream(channel); + private _removeStream(channel, stream); + } + + class Connection { + constructor(ws: WebSocket); + 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; + } +} diff --git a/types/sharedb/lib/client.d.ts b/types/sharedb/lib/client.d.ts new file mode 100644 index 0000000000..ad8328dc83 --- /dev/null +++ b/types/sharedb/lib/client.d.ts @@ -0,0 +1,12 @@ +/// +import * as WebSocket from 'ws'; +import * as ShareDB from './sharedb'; + +export class Connection { + constructor(ws: WebSocket); + 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; +} +export type Doc = ShareDB.Doc; +export type Query = ShareDB.Query; diff --git a/types/sharedb/lib/sharedb.d.ts b/types/sharedb/lib/sharedb.d.ts new file mode 100644 index 0000000000..1b9b7ee5df --- /dev/null +++ b/types/sharedb/lib/sharedb.d.ts @@ -0,0 +1,67 @@ +export type Path = ReadonlyArray; +export type Snapshot = number; + +export interface AddNumOp { p: Path; na: number; } + +export interface ListInsertOp { p: Path; li: any; } +export interface ListDeleteOp { p: Path; ld: any; } +export interface ListReplaceOp { p: Path; li: any; ld: any; } +export interface ListMoveOp { p: Path; lm: any; } + +export interface ObjectInsertOp { p: Path; oi: any; } +export interface ObjectDeleteOp { p: Path; od: any; } +export interface ObjectReplaceOp { p: Path; oi: any; od: any; } + +export interface StringInsertOp { p: Path; si: string; } +export interface StringDeleteOp { p: Path; sd: string; } + +export interface SubtypeOp { p: Path; t: string; o: any; } + +export type Op = AddNumOp | ListInsertOp | ListDeleteOp | ListReplaceOp | ListMoveOp | ObjectInsertOp | ObjectDeleteOp | ObjectReplaceOp | StringInsertOp | StringDeleteOp | SubtypeOp; + +export interface RawOp { + src: string; + seq: number; + v: number; + op: Op[]; + m: any; + c: string; + d: string; +} +export type OTType = 'ot-text' | 'ot-json0' | 'ot-text-tp2' | 'rich-text'; +export type Action = 'connect'|'op'|'doc'|'query'|'submit'|'apply'|'commit'|'after submit'|'receive'; +export interface Error { + code: number; + message: string; +} +export interface ShareDBSourceOptions { source?: boolean; } +// interface ShareDBCreateOptions extends ShareDBSourceOptions {} +// interface ShareDBDelOptions extends ShareDBSourceOptions {} +// interface ShareDBSubmitOpOptions extends ShareDBSourceOptions {} + +export type Callback = (err: Error) => any; + +export class Doc { + type: string; + id: string; + data: any; + fetch: (callback: (err: Error) => void) => void; + subscribe: (callback: (err: Error) => void) => void; + on: (event: 'load'|'create'|'before op'|'op'|'del'|'error', callback: (...args: any[]) => any) => void; + ingestSnapshot: (snapshot: Snapshot, callback: Callback) => void; + destroy: () => void; + create(data: any, callback?: Callback): void; + create(data: any, type?: OTType, callback?: Callback): void; + create(data: any, type?: OTType, options?: ShareDBSourceOptions, callback?: Callback): void; + submitOp: (data: ReadonlyArray, options?: ShareDBSourceOptions, callback?: Callback) => void; + del: (options: ShareDBSourceOptions, callback: (err: Error) => void) => void; + removeListener: (eventName: string, listener: () => any) => void; +} + +export class Query { + ready: boolean; + results: Doc[]; + extra: any; + on: (event: 'ready'|'error'|'changed'|'insert'|'move'|'remove'|'extra', callback: (...args: any[]) => any) => any; + destroy: () => void; +} diff --git a/types/sharedb/sharedb-tests.ts b/types/sharedb/sharedb-tests.ts new file mode 100644 index 0000000000..55c3dc797d --- /dev/null +++ b/types/sharedb/sharedb-tests.ts @@ -0,0 +1,71 @@ +/// +import * as ShareDB from 'sharedb'; +import * as http from 'http'; +import * as WebSocket from 'ws'; +import { Duplex } from 'stream'; +import * as ShareDBClient from 'sharedb/lib/client'; + +// Adapted from https://github.com/avital/websocket-json-stream +class WebSocketJSONStream extends Duplex { + constructor(private readonly ws: WebSocket) { + super({ objectMode: true }); + this.ws.on('message', (msg: string) => { + this.push(JSON.parse(msg)); + }); + this.ws.on('close', () => { + this.push(null); // end readable stream + this.end(); // end writable stream + + this.emit('close'); + this.emit('end'); + }); + this.on('error', () => { ws.close(); }); + this.on('end', () => { ws.close(); }); + } + _read(): void {} + _write(msg: any, encoding: string, next: () => void): void { + this.ws.send(JSON.stringify(msg)); + next(); + } +} + +const backend = new ShareDB(); +const connection = backend.connect(); +const doc = connection.get('examples', 'counter'); + +doc.fetch((err) => { + if (err) { throw err; } + if (doc.type === null) { + doc.create({ numClicks: 0 }, startServer); + } else { + startServer(); + } +}); + +function startServer() { + const server = http.createServer(); + + // Connect any incoming WebSocket connection to ShareDB + const wss = new WebSocket.Server({ server }); + wss.on('connection', (ws, req) => { + const stream = new WebSocketJSONStream(ws); + backend.listen(stream); + }); + + server.listen(8080); + // console.log('Listening on http://localhost:8080'); + startClient(() => { + server.close(); + wss.close(); + }); +} + +function startClient(callback) { + const socket = new WebSocket('ws://localhost:8080'); + const connection = new ShareDBClient.Connection(socket); + const doc = connection.get('examples', 'counter'); + doc.subscribe(() => { + doc.submitOp([{p: ['numClicks'], na: 1}]); + callback(); + }); +} diff --git a/types/sharedb/tsconfig.json b/types/sharedb/tsconfig.json new file mode 100644 index 0000000000..8d2c0915c0 --- /dev/null +++ b/types/sharedb/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": false, + "noImplicitThis": false, + "strictNullChecks": false, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "paths": { + }, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "lib/sharedb.d.ts", + "index.d.ts", + "lib/client.d.ts", + "sharedb-tests.ts" + ] +} \ No newline at end of file diff --git a/types/sharedb/tslint.json b/types/sharedb/tslint.json new file mode 100644 index 0000000000..2914fa23e8 --- /dev/null +++ b/types/sharedb/tslint.json @@ -0,0 +1,4 @@ +{ + "extends": "dtslint/dt.json", + "rules": { } +}