packageDocumentation module textile import { grpc } from "@improbable-eng/grpc-web" ; import { UnaryOutput } from "@improbable-eng/grpc-web/dist/typings/unary" ; import { Context , ContextInterface , defaultHost } from "@textile/context" ; import { Identity } from "@textile/crypto" ; import { WebsocketTransport } from "@textile/grpc-transport" ; import { bytesFromAddr , bytesToOptions , bytesToTuples , stringFromBytes , } from "@textile/multiaddr" ; import { KeyInfo , ThreadKey , UserAuth } from "@textile/security" ; import * as pb from "@textile/threads-client-grpc/threads_pb" ; import { API , APIGetToken , APIListen , } from "@textile/threads-client-grpc/threads_pb_service" ; import { ThreadID } from "@textile/threads-id" ; import "fastestsmallesttextencoderdecoder" ; import toJsonSchema , { JSONSchema3or4 } from "to-json-schema" ; import { ComparisonJSON , CriterionJSON , Event , Filter , Patch , PatchType , Query , QueryJSON , ReadTransaction , SortJSON , ValueJSON , Where , WriteTransaction , } from "./models" ; export { Filter , Query , Where , WriteTransaction , ReadTransaction , QueryJSON , ValueJSON , ComparisonJSON , CriterionJSON , SortJSON , JSONSchema3or4 , Event , Patch , PatchType , } ; function isEmpty ( obj ) { return Object . keys ( obj ) . length === 0 && obj . constructor === Object ; } export function getFunctionBody ( fn ) { function removeCommentsFromSource ( str ) { return str . replace ( /(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm , "$1" ) ; } const s = removeCommentsFromSource ( fn . toString ( ) ) ; return s . substring ( s . indexOf ( "{" ) + 1 , s . lastIndexOf ( "}" ) ) ; } /**
 * CollectionConfig is the configuration options for creating and updating a Collection.
 * It supports the following configuration options:
 *   - Name: The name of the collection, e.g, "Animals" (must be unique per DB).
 *   - Schema: A JSON Schema), which is used for instance validation.
 *   - Indexes: An optional list of index configurations, which define how instances are indexed.
 *   - WriteValidator: An optional JavaScript (ECMAScript 5.1) function that is used to validate
 *     instances on write.
 *   - ReadFilter: An optional JavaScript (ECMAScript 5.1) function that is used to filter
 *     instances on read.
 *
 * The `writeValidator` function receives three arguments:
 *   - writer: The multibase-encoded public key identity of the writer.
 *   - event: An object describing the update event (see core.Event).
 *   - instance: The current instance as a JavaScript object before the update event is applied.
 *
 * A falsy return value indicates a failed validation. Note that the function arguments must
 * be named as documented here (writer, event, instance). These functions run in a secure sandbox
 * where these argument names are specified.
 *
 * Having access to writer, event, and instance opens the door to a variety of app-specific logic.
 * Textile Buckets file-level access roles are implemented in part with a write validator.
 *
 * The `readFilter` function receives three arguments:
 *   - reader: The multibase-encoded public key identity of the reader.
 *   - instance: The current instance as a JavaScript object.
 *
 * The function must return a JavaScript object. Most implementations will modify and return the
 * current instance. Note that the function arguments must be named as documented here (reader,
 * instance). These functions run in a secure sandbox where these argument names are specified.
 * Like write validation, read filtering opens the door to a variety of app-specific logic.
 * Textile Buckets file-level access roles are implemented in part with a read filter.
 */ export interface CollectionConfig < W = any , R = W > { name : string ; schema ? : JSONSchema3or4 | any ; indexes ? : pb . Index . AsObject [ ] ; writeValidator ? : ( ( writer : string , event : Event , instance : W ) => boolean ) | string ; readFilter ? : ( ( reader : string , instance : R ) => R ) | string ; } const encoder = new TextEncoder ( ) ; const decoder = new TextDecoder ( ) ; export function maybeLocalAddr ( ip ) { return ( [ "localhost" , "" , "::1" ] . includes ( ip ) || ip . match ( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) || ip . startsWith ( "192.168." ) || ip . startsWith ( "10.0." ) || ip . endsWith ( ".local" ) ) ; } export enum Action { CREATE = 0 , SAVE , DELETE } export interface Update < T = unknown > { instance : T | undefined ; collectionName : string ; instanceID : string ; action : Action ; } /**
 * DBInfo contains joining/sharing information for a Thread/DB.
 */ export interface DBInfo { see ThreadKey for details.
      key : string ; /**
     * The Multiaddrs for a peer hosting the given Thread/DB.
     */ addrs : string [ ] ; } example export class Client { public serviceHost : string ; public rpcOptions : grpc . RpcOptions ; param context param debug constructor ( public context = new Context ( ) , debug = false ) { this . serviceHost = context . host ; this . rpcOptions = { transport : WebsocketTransport ( ) , debug , } ; } param auth example example static withUserAuth ( auth , host = defaultHost , debug = false ) { const context = typeof auth === "object" ? Context . fromUserAuth ( auth , host ) : Context . fromUserAuthCallback ( auth , host ) ; return new Client ( context , debug ) ; } param key param host param debug example static async withKeyInfo ( key , host = defaultHost , debug = false ) { return new Client ( await new Context ( host ) . withKeyInfo ( key ) , debug ) ; } param identity param ctx example getToken ( identity , ctx ? ) { return this . getTokenChallenge ( identity . public . toString ( ) , async ( challenge ) => { return identity . sign ( challenge ) ; } , ctx ) ; } param publicKey param callback param ctx remarks getTokenChallenge ( publicKey , callback , ctx ? ) { const client = grpc . client < pb . GetTokenRequest , pb . GetTokenReply , APIGetToken > ( API . GetToken , { host : this . serviceHost , transport : this . rpcOptions . transport , debug : this . rpcOptions . debug , } ) ; return new Promise < string > ( ( resolve , reject ) => { let token = "" ; client . onMessage ( async ( message ) => { if ( message . hasChallenge ( ) ) { const challenge = message . getChallenge_asU8 ( ) ; const signature = await callback ( challenge ) ; const req = new pb . GetTokenRequest ( ) ; req . setSignature ( signature ) ; client . send ( req ) ; client . finishSend ( ) ; } else if ( message . hasToken ( ) ) { token = message . getToken ( ) ; } } ) ; client . onEnd ( ( code , message ) => { client . close ( ) ; if ( code === grpc . Code . OK ) { this . context . withToken ( token ) ; resolve ( token ) ; } else { reject ( new Error ( message ) ) ; } } ) ; const req = new pb . GetTokenRequest ( ) ; req . setKey ( publicKey ) ; this . context . toMetadata ( ctx ) . then ( ( metadata ) => { client . start ( metadata ) ; client . send ( req ) ; } ) ; } ) ; } param threadID param name example public newDB ( threadID ? , name ? ) { const dbID = threadID ?? ThreadID . fromRandom ( ) ; const req = new pb . NewDBRequest ( ) ; req . setDbid ( dbID . toBytes ( ) ) ; if ( name !== undefined ) { this . context . withThreadName ( name ) ; req . setName ( name ) ; } return this . unary ( API . NewDB , req , ( ) => { this . context . withThread ( dbID . toString ( ) ) ; return dbID ; } ) ; } param threadID param name example public async open ( threadID , name ? ) { const req = new pb . ListDBsRequest ( ) ; const found = await this . unary ( API . ListDBs , req , ( res ) => { for ( const db of res . getDbsList ( ) ) { const id = ThreadID . fromBytes ( db . getDbid_asU8 ( ) ) ; if ( id === threadID ) { this . context . withThread ( threadID . toString ( ) ) ; return true ; } } return false ; } ) ; if ( found ) return ; await this . newDB ( threadID , name ) ; this . context . withThread ( threadID . toString ( ) ) ; } param threadID example public deleteDB ( threadID ) { const req = new pb . DeleteDBRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; return this . unary ( API . DeleteDB , req ) ; } /**
     * Lists all known DBs.
     */ public listDBs ( ) { const req = new pb . ListDBsRequest ( ) ; return this . unary ( API . ListDBs , req , ( res ) => { const dbs = [ ] ; for ( const db of res . getDbsList ( ) ) { const id = ThreadID . fromBytes ( db . getDbid_asU8 ( ) ) . toString ( ) ; dbs . push ( { id , name : db . getInfo ( ) ?. getName ( ) , } ) ; } return dbs ; } ) ; } param thread public listCollections ( thread ) { const req = new pb . ListCollectionsRequest ( ) ; req . setDbid ( thread . toBytes ( ) ) ; return this . unary ( API . ListCollections , req , ( res ) => res . toObject ( ) . collectionsList ) ; } param threadID param config A configuration object for the collection. See  CollectionConfig . Note
     * that the validator and filter functions can also be provided as strings.
     *
     *  example example public newCollection ( threadID , config ) { const req = new pb . NewCollectionRequest ( ) ; const conf = new pb . CollectionConfig ( ) ; conf . setName ( config . name ) ; if ( config . schema === undefined || isEmpty ( config . schema ) ) { config . schema = { properties : { _id : { type : "string" } } } ; } conf . setSchema ( encoder . encode ( JSON . stringify ( config . schema ) ) ) ; if ( config . writeValidator ) { conf . setWritevalidator ( getFunctionBody ( config . writeValidator ) ) ; } if ( config . readFilter ) { conf . setReadfilter ( getFunctionBody ( config . readFilter ) ) ; } const idx = [ ] ; for ( const item of config . indexes ?? [ ] ) { const index = new pb . Index ( ) ; index . setPath ( item . path ) ; index . setUnique ( item . unique ) ; idx . push ( index ) ; } conf . setIndexesList ( idx ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setConfig ( conf ) ; return this . unary ( API . NewCollection , req ) ; } param threadID param obj param config A configuration object for the collection. See  CollectionConfig .
     *
     *  example public newCollectionFromObject ( threadID , obj , config ) { const schema = toJsonSchema ( obj ) ; return this . newCollection ( threadID , { ... config , schema } ) ; } todo param threadID param config A configuration object for the collection. See  CollectionConfig .
     *
     *  example public updateCollection ( threadID , config ) { const req = new pb . UpdateCollectionRequest ( ) ; const conf = new pb . CollectionConfig ( ) ; conf . setName ( config . name ) ; if ( config . schema === undefined || isEmpty ( config . schema ) ) { config . schema = { properties : { _id : { type : "string" } } } ; } conf . setSchema ( encoder . encode ( JSON . stringify ( config . schema ) ) ) ; if ( config . writeValidator ) { conf . setWritevalidator ( getFunctionBody ( config . writeValidator ) ) ; } if ( config . readFilter ) { conf . setReadfilter ( getFunctionBody ( config . readFilter ) ) ; } const idx = [ ] ; for ( const item of config . indexes ?? [ ] ) { const index = new pb . Index ( ) ; index . setPath ( item . path ) ; index . setUnique ( item . unique ) ; idx . push ( index ) ; } conf . setIndexesList ( idx ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setConfig ( conf ) ; return this . unary ( API . UpdateCollection , req ) ; } param threadID param name param schema example public deleteCollection ( threadID , name ) { const req = new pb . DeleteCollectionRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setName ( name ) ; return this . unary ( API . DeleteCollection , req ) ; } param threadID param name example public getCollectionIndexes ( threadID , name ) { const req = new pb . GetCollectionIndexesRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setName ( name ) ; return this . unary ( API . GetCollectionIndexes , req , ( res ) => res . toObject ( ) . indexesList ) ; } public getCollectionInfo ( threadID , name ) { const req = new pb . GetCollectionInfoRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setName ( name ) ; return this . unary ( API . GetCollectionInfo , req , ( res ) => { const result = { schema : JSON . parse ( decoder . decode ( res . getSchema_asU8 ( ) ) ) , name : res . getName ( ) , indexes : res . getIndexesList ( ) . map ( ( index ) => index . toObject ( ) ) , writeValidator : res . getWritevalidator ( ) , readFilter : res . getReadfilter ( ) , } ; return result ; } ) ; } remarks See getDBInfo for a possible source of the address and keys. See  ThreadKey for
     * information about thread keys.
     *  param address param key param collections public newDBFromAddr ( address , key , collections ? ) { const req = new pb . NewDBFromAddrRequest ( ) ; const addr = bytesFromAddr ( address ) ; req . setAddr ( addr ) ; req . setKey ( typeof key === "string" ? ThreadKey . fromString ( key ) . toBytes ( ) : key ) ; if ( collections !== undefined ) { req . setCollectionsList ( collections . map ( ( c ) => { const config = new pb . CollectionConfig ( ) ; config . setName ( c . name ) ; config . setSchema ( encoder . encode ( JSON . stringify ( c . schema ) ) ) ; const { indexes } = c ; if ( indexes !== undefined ) { const idxs = indexes . map ( ( idx ) => { const index = new pb . Index ( ) ; index . setPath ( idx . path ) ; index . setUnique ( idx . unique ) ; return index ; } ) ; config . setIndexesList ( idxs ) ; } return config ; } ) ) ; } return this . unary ( API . NewDBFromAddr , req , ( ) => { const result = bytesToTuples ( req . getAddr_asU8 ( ) ) . filter ( ( [ key ] ) => key === 406 ) ; return ThreadID . fromString ( result [ 0 ] [ 1 ] as string ) ; } ) ; } remarks param info param includeLocal param collections example public joinFromInfo ( info , includeLocal = false , collections ? ) { const req = new pb . NewDBFromAddrRequest ( ) ; const filtered = info . addrs . map ( bytesFromAddr ) . filter ( ( addr ) => includeLocal || ! maybeLocalAddr ( bytesToOptions ( addr ) . host ) ) ; for ( const addr of filtered ) { req . setAddr ( addr ) ; req . setKey ( typeof info . key === "string" ? ThreadKey . fromString ( info . key ) . toBytes ( ) : info . key ) ; if ( collections !== undefined ) { req . setCollectionsList ( collections . map ( ( c ) => { const config = new pb . CollectionConfig ( ) ; config . setName ( c . name ) ; config . setSchema ( encoder . encode ( JSON . stringify ( c . schema ) ) ) ; const { indexes } = c ; if ( indexes !== undefined ) { const idxs = indexes . map ( ( idx ) => { const index = new pb . Index ( ) ; index . setPath ( idx . path ) ; index . setUnique ( idx . unique ) ; return index ; } ) ; config . setIndexesList ( idxs ) ; } return config ; } ) ) ; } return this . unary ( API . NewDBFromAddr , req , ( ) => { const result = bytesToTuples ( req . getAddr_asU8 ( ) ) . filter ( ( [ key ] ) => key === 406 ) ; return ThreadID . fromString ( result [ 0 ] [ 1 ] as string ) ; } ) ; } throw new Error ( "No viable addresses for dialing" ) ; } param threadID returns example public getDBInfo ( threadID ) { const req = new pb . GetDBInfoRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; return this . unary ( API . GetDBInfo , req , ( res ) => { const key = ThreadKey . fromBytes ( res . getKey_asU8 ( ) ) ; const addrs = [ ] ; for ( const addr of res . getAddrsList ( ) ) { const address = stringFromBytes ( addr as Uint8Array ) ; addrs . push ( address ) ; } return { key : key . toString ( ) , addrs } ; } ) ; } param threadID param collectionName param values example public create ( threadID , collectionName , values ) { const req = new pb . CreateRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; const list = [ ] ; values . forEach ( ( v ) => { list . push ( encoder . encode ( JSON . stringify ( v ) ) ) ; } ) ; req . setInstancesList ( list ) ; return this . unary ( API . Create , req , ( res ) => res . toObject ( ) . instanceidsList ) ; } param threadID param collectionName param values example public save ( threadID , collectionName , values ) { const req = new pb . SaveRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; const list = [ ] ; values . forEach ( ( v ) => { if ( ! v . hasOwnProperty ( "_id" ) ) { v [ "_id" ] = "" ; } list . push ( encoder . encode ( JSON . stringify ( v ) ) ) ; } ) ; req . setInstancesList ( list ) ; return this . unary ( API . Save , req ) ; } param threadID param collectionName param IDs example public delete ( threadID , collectionName , IDs ) { const req = new pb . DeleteRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; req . setInstanceidsList ( IDs ) ; return this . unary ( API . Delete , req ) ; } param threadID param collectionName param IDs example public has ( threadID , collectionName , IDs ) { const req = new pb . HasRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; req . setInstanceidsList ( IDs ) ; return this . unary ( API . Has , req , ( res ) => res . getExists ( ) ) ; } param threadID param collectionName param query example public find < T = unknown > ( threadID , collectionName , query ) { const req = new pb . FindRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; req . setQueryjson ( encoder . encode ( JSON . stringify ( query ) ) ) ; return this . unary ( API . Find , req , ( res ) => { return res . getInstancesList_asU8 ( ) . map ( ( instance ) => JSON . parse ( decoder . decode ( instance ) ) ) ; } ) ; } param threadID param collectionName param ID example example public findByID < T = unknown > ( threadID , collectionName , ID ) { const req = new pb . FindByIDRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; req . setInstanceid ( ID ) ; return this . unary ( API . FindByID , req , ( res ) => JSON . parse ( decoder . decode ( res . getInstance_asU8 ( ) ) ) ) ; } param threadID param collectionName param values example public verify ( threadID , collectionName , values ) { const req = new pb . VerifyRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; req . setCollectionname ( collectionName ) ; const list = values . map ( ( v ) => encoder . encode ( JSON . stringify ( v ) ) ) ; req . setInstancesList ( list ) ; return this . unary ( API . Verify , req ) ; } param threadID param collectionName public readTransaction ( threadID , collectionName ) { const client = grpc . client ( API . ReadTransaction , { host : this . serviceHost , transport : this . rpcOptions . transport , debug : this . rpcOptions . debug , } ) ; return new ReadTransaction ( this . context , client , threadID , collectionName ) ; } param threadID param collectionName public writeTransaction ( threadID , collectionName ) { const client = grpc . client ( API . WriteTransaction , { host : this . serviceHost , transport : this . rpcOptions . transport , debug : this . rpcOptions . debug , } ) ; return new WriteTransaction ( this . context , client , threadID , collectionName ) ; } param threadID param filters param callback example example public listen < T = any > ( threadID , filters , callback ) { const req = new pb . ListenRequest ( ) ; req . setDbid ( threadID . toBytes ( ) ) ; for ( const filter of filters ) { const requestFilter = new pb . ListenRequest . Filter ( ) ; if ( filter . instanceID ) { requestFilter . setInstanceid ( filter . instanceID ) ; } else if ( filter . collectionName ) { requestFilter . setCollectionname ( filter . collectionName ) ; } if ( filter . actionTypes ) { for ( const at of filter . actionTypes ) { switch ( at ) { case "ALL" : { requestFilter . setAction ( pb . ListenRequest . Filter . Action . ALL ) ; break ; } case "CREATE" : { requestFilter . setAction ( pb . ListenRequest . Filter . Action . CREATE ) ; break ; } case "SAVE" : { requestFilter . setAction ( pb . ListenRequest . Filter . Action . SAVE ) ; break ; } case "DELETE" : { requestFilter . setAction ( pb . ListenRequest . Filter . Action . DELETE ) ; break ; } } } } else { requestFilter . setAction ( 0 ) ; } req . addFilters ( requestFilter ) ; } const decoder = new TextDecoder ( ) ; const client = grpc . client < pb . ListenRequest , pb . ListenReply , APIListen > ( API . Listen , { host : this . serviceHost , transport : this . rpcOptions . transport , debug : this . rpcOptions . debug , } ) ; client . onMessage ( ( message ) => { const instanceString = decoder . decode ( message . getInstance_asU8 ( ) ) ; const actionInt = message . getAction ( ) ; const action = ( Action [ actionInt ] as unknown ) as Action ; const collectionName = message . getCollectionname ( ) ; const instanceID = message . getInstanceid ( ) ; const ret = { collectionName , instanceID , action , instance : undefined , } ; if ( instanceString !== "" ) { ret . instance = JSON . parse ( instanceString ) ; } callback ( ret ) ; } ) ; client . onEnd ( ( status , message ) => { if ( status !== grpc . Code . OK ) { callback ( undefined , new Error ( message ) ) ; } callback ( ) ; } ) ; this . context . toMetadata ( ) . then ( ( metadata ) => { client . start ( metadata ) ; client . send ( req ) ; client . finishSend ( ) ; } ) ; return { close : ( ) => client . close ( ) } ; } private async unary < TResponse extends grpc . ProtobufMessage , TRequest extends grpc . ProtobufMessage , M extends grpc . UnaryMethodDefinition < TRequest , TResponse > , O = undefined > ( methodDescriptor , req , mapper = ( ) => undefined ) { const metadata = await this . context . toMetadata ( ) ; return new Promise < O > ( ( resolve , reject ) => { grpc . unary ( methodDescriptor , { transport : this . rpcOptions . transport , debug : this . rpcOptions . debug , request : req , host : this . serviceHost , metadata , onEnd : ( res ) => { const { status , statusMessage , message } = res ; if ( status === grpc . Code . OK ) { resolve ( mapper ( message as any ) as O ) ; } else { reject ( new Error ( statusMessage ) ) ; } } , } ) ; } ) ; } } export default Client ;