npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@elm-street-technology/webapi-client

v1.3.0

Published

Drop In Replacement For Node Rets-Client

Downloads

19

Readme

webapi-client

A RETS (Real Estate Transaction Standard) client for Node.js based on the webapi standards.

this library implements webapi the same way as the rets-client

This allows you to support both libraries will little changes.

It is not a fully implemented library and does not support all features of the rets-client

Implementation Notes

This interface uses promises, and an optional stream-based interface for better performance with large search results. Future development will include an optional stream-based interface for object downloads, and an improved API for the non-streaming object methods.

This library is written primarily in Typescript. Promises in this module are provided by Bluebird.

The original module was developed against a server running webapi v1.0.3, so there may be incompatibilities with other versions. However, we want this library to work against any webapi servers that are in current use, so issue tickets describing problems or (even better) pull requests that fix interactions with servers running other versions of webapi are welcomed.

For more information about what all the parameters and return values and such mean, you might want to look at the WEBAPI Specifications

Contributions

Issue tickets and pull requests are welcome. Pull requests must be backward-compatible to be considered, and ideally should match existing code style.

TODO

  • create unit tests -- specifically ones that run off example WEBAPI data rather than requiring access to a real WEBAPI server
  • refactor streaming API to correctly respond to backpressure

Example Usage

Client Configuration
    //create webapi-client
    var clientSettings = {
        apiUrl: webApiUrl,
        auth: {
          loginUrl: webApiLoginUrl,
          username: webApiUser,
          password: webApiPassword,
          strategy: 'oauth2',
          scope: 'api',
          grant_type: 'client_credentials'
        }
    };
...
Output helper used in many examples below
function outputFields(obj, opts) {
  if (!obj) {
    console.log("      "+JSON.stringify(obj))
  } else {
    if (!opts) opts = {};

    var excludeFields;
    var loopFields;
    if (opts.exclude) {
      excludeFields = opts.exclude;
      loopFields = Object.keys(obj);
    } else if (opts.fields) {
      loopFields = opts.fields;
      excludeFields = [];
    } else {
      loopFields = Object.keys(obj);
      excludeFields = [];
    }
    for (var i = 0; i < loopFields.length; i++) {
      if (excludeFields.indexOf(loopFields[i]) != -1) {
        continue;
      }
      if (typeof(obj[loopFields[i]]) == 'object') {
        console.log("      " + loopFields[i] + ": " + JSON.stringify(obj[loopFields[i]], null, 2).replace(/\n/g, '\n      '));
      } else {
        console.log("      " + loopFields[i] + ": " + JSON.stringify(obj[loopFields[i]]));
      }
    }
  }
  console.log("");
}

Example webapi-client code

var rets = require('webapi-client');
var fs = require('fs');
var photoSourceId = '12345'; // <--- dummy example ID!  this will usually be a MLS number / listing id
  
// establish connection to RETS server which auto-logs out when we're done
webapi.getAutoLogoutClient(clientSettings, function (client) {
  console.log("===================================");
  console.log("========  System Metadata  ========");
  console.log("===================================");
  console.log('   ~~~~~~~~~ Header Info ~~~~~~~~~');
  outputFields(client.loginHeaderInfo);
  console.log('   ~~~~~~~~~ System Data ~~~~~~~~~');
  outputFields(client.systemData);

  //get resources metadata
  return client.metadata.getResources()
    .then(function (data) {
      console.log("======================================");
      console.log("========  Resources Metadata  ========");
      console.log("======================================");
      console.log('   ~~~~~~ Resources Metadata ~~~~~');
      outputFields(data.results[0].info);
      for (var dataItem = 0; dataItem < data.results[0].metadata.length; dataItem++) {
        console.log("   -------- Resource " + dataItem + " --------");
        outputFields(data.results[0].metadata[dataItem], {fields: ['ResourceID', 'StandardName', 'VisibleName', 'ObjectVersion']});
      }
    }).then(function () {

      //get class metadata
      return client.metadata.getClass("Property");
    }).then(function (data) {
      console.log("===========================================================");
      console.log("========  Class Metadata (from Property Resource)  ========");
      console.log("===========================================================");
      console.log('   ~~~~~~~~ Class Metadata ~~~~~~~');
      outputFields(data.results[0].info);
      for (var classItem = 0; classItem < data.results[0].metadata.length; classItem++) {
        console.log("   -------- Table " + classItem + " --------");
        outputFields(data.results[0].metadata[classItem], {fields: ['ClassName', 'StandardName', 'VisibleName', 'TableVersion']});
      }
    }).then(function () {

      //get field data for open houses
      return client.metadata.getTable("Property", "RESIDENTIAL");
    }).then(function (data) {
      console.log("==============================================");
      console.log("========  Residential Table Metadata  ========");
      console.log("===============================================");
      console.log('   ~~~~~~~~ Table Metadata ~~~~~~~');
      outputFields(data.results[0].info);
      for (var tableItem = 0; tableItem < data.results[0].metadata.length; tableItem++) {
        console.log("   -------- Field " + tableItem + " --------");
        outputFields(data.results[0].metadata[tableItem], {fields: ['MetadataEntryID', 'SystemName', 'ShortName', 'LongName', 'DataType']});
      }
      return data.results[0].metadata
    }).then(function (fieldsData) {
      var plucked = [];
      for (var fieldItem = 0; fieldItem < fieldsData.length; fieldItem++) {
        plucked.push(fieldsData[fieldItem].SystemName);
      }
      return plucked;
    }).then(function (fields) {

      //perform a query using DMQL2 -- pass resource, class, and query, and options
      return client.search.query("Property", "RESIDENTIAL", "(RecordModDate=2016-06-20+),(ActiveYN=1)", {limit:100, offset:10})
        .then(function (searchData) {
          console.log("=============================================");
          console.log("========  Residential Query Results  ========");
          console.log("=============================================");
          console.log('   ~~~~~~~~~~ Query Info ~~~~~~~~~');
          //iterate through search results
          for (var dataItem = 0; dataItem < searchData.results.length; dataItem++) {
            console.log("   -------- Result " + dataItem + " --------");
            outputFields(searchData.results[dataItem], {fields: fields});
          }
          if (searchData.maxRowsExceeded) {
            console.log("   -------- More rows available!");
          }
        });
    });

}).catch(function (errorInfo) {
  var error = errorInfo.error || errorInfo;
  console.log("   ERROR: issue encountered:");
  outputFields(error);
  console.log('   '+(error.stack||error).replace(/\n/g, '\n   '));
});

Simple streaming example

var webapi = require('webapi-client');
var through2 = require('through2');
var Promise = require('bluebird');
  
// this function doesn't do much, it's just a placeholder for whatever you want to do with the results 
function doAsyncProcessing(row, index, callback) {
  console.log("-------- Result " + index + " --------");
  outputFields(row);
  // must be sure callback is called when this is done
  callback();
}

// establish connection to WebApi server which auto-logs out when we're done
webapi.getAutoLogoutClient(clientSettings, function (client) {
  // in order to have the auto-logout function work properly, we need to make a promise that either rejects or
  // resolves only once we're done processing the stream
  return new Promise(function (resolve, reject) {
    console.log("====================================");
    console.log("========  Streamed Results  ========");
    console.log("====================================");
    var count = 0;
    var streamResult = client.search.stream.query("Property", "RES", "(LastChangeTimestamp=2016-06-20+)", {limit:10, offset:4});
    var processorStream = through2.obj(function (event, encoding, callback) {
      switch (event.type) {
        case 'data':
          // event.payload is an object representing a single row of results
          // make sure callback is called only when all processing is complete
          count++;
          doAsyncProcessing(event.payload, count, callback);
          break;
        case 'done':
          // event.payload is an object containing a count of rows actually received, plus some other things
          // now we can resolve the auto-logout promise
          resolve(event.payload.rowsReceived);
          callback();
          break;
        case 'error':
          // event.payload is an Error object
          console.log('Error streaming RETS results: '+event.payload);
          streamResult.retsStream.unpipe(processorStream);
          processorStream.end();
          // we need to reject the auto-logout promise
          reject(event.payload);
          callback();
          break;
        default:
          // ignore other events
          callback();
      }
    });
    streamResult.retsStream.pipe(processorStream);
  });

}).catch(function (errorInfo) {
  var error = errorInfo.error || errorInfo;
  console.log("   ERROR: issue encountered:");
  outputFields(error);
  console.log('   '+(error.stack||error).replace(/\n/g, '\n   '));
});
Errors

There are 6 error classes exposed by this module:

  • RetsError: A parent class for all the errors below, to make it more convenient to catch errors from this library. I've made somewhat of an effort to catch any errors thrown by dependencies of this library and re-throw them as instances of RetsError, so that any error generated by a call to this library can be detected the same way; if you find an error coming through that didn't get this treatment, please open a ticket (or better, a PR!) to let me know.
  • RetsParamError: Used when a required function parameter is missing or has an invalid value
  • RetsServerError: Used when the HTTP response indicates an error, such as a "401 Unauthorized" response
  • RetsReplyError: Used when the HTTP response is valid, but the XML RETS response indicates an error
  • RetsProcessingError: Used when a problem is encountered processing the response from the RETS server
  • RetsPermissionError: Used when RETS login is successful, but the account does not have the full permissions expected