MQ and Node.js: working with TypeScript

MQ application programs for the Node.js environment can now use TypeScript definitions. This brings the opportunity to compile and check your JavaScript programs for correctness before running them. This post will talk more about what TypeScript is and how it can help your MQ Node.js development activity.

Substantial credit for the API definitions and translations of the example programs needs to be given to Andre, who submitted a Pull Request to our github repository.

The MQ Node.js bindings

In this article, I wrote about a set of bindings that allow you to write JavaScript programs for Node.js with access to full MQ capabilities. The interface provides a version of the MQI mapped into a style that is more natural for JavaScript programmers. And the package is designed to be easy to install and work with. It automatically pulls in the MQ client runtime prerequisite where it can be done, reducing effort during deployment. The full code library is on github, but much of the time people will simply allow npm install to obtain the pieces they need. The package has evolved, with new features added as the base MQ product gets enhanced. There have also been additional sample programs and scripts to show different ways of using it.

What is TypeScript

TypeScript is an extension to the JavaScript language. It is essentially a superset, where objects have known types (number, string, named structure etc) instead of having no specific type. Function definitions state which type is used for each parameter. The types are then checked at compile time instead of causing runtime failures. You cannot assign a value to a variable if it is the wrong type. This makes programs more secure and robust as many problems in application code can be caught early in the process instead of showing up when the program is already in production.

This is the kind of behaviour I have always expected to see in serious programming languages, but it is not part of JavaScript. Untyped languages can be fine for scripts and very simple short programs, but use of JavaScript has ballooned. Programs are much longer than the original, simple, intention of the language to give a level of dynamic content in web pages. It is also harder for a programming team working on a product to keep track of what kinds of objects should be passed around between different pieces of the program: source code comments are never sufficient.

A TypeScript program is compiled into JavaScript, with errors being detected at that point. The generated JavaScript can then be executed directly. Because of this process, you can easily mix TypeScript code with “traditional” JavaScript code – there is no enforcement of using object types in application code. Though programs like eslint can be used to check your style.

The MQ TypeScript enhancements

Because you can mix JavaScript with TypeScript, there is no absolute requirement for any API to give a declaration of its interface. Clearly, however, it can make things better for the application programmer. So what we now have with the MQI bindings are declarations for all of the application-callable functions and the associated structures.

For example, this is the declaration of the Open function (the spelling of MQOPEN in this binding).

function Open(
     jsQueueManager: MQQueueManager,
     jsod: MQOD,
     jsOpenOptions: number,
     cb: (err: MQError | null, obj: MQObject) => void
   ): void;

We can see all of the input parameters with their associated types – there are corresponding definitions of the MQQueueManager and the MQOD classes – and what kinds of parameters get sent to the asynchronous callback function invoked when the Open completes. The TypeScript syntax can assert several different types that a parameter might be. For example, the first parameter to the callback will either be an MQError object, or null.

Many of the fields in structures can only hold certain values from the complete set of MQI definitions. Where possible, those subsets have been named in enum ranges to give an additional level of checking.

Running programs

There are several sample programs, converted from the original JavaScript versions, in the samples/typescript directory of the repository. To run one of them, copy it to a suitable place and then add a package.json file:

{
   "name": "amqsput",
   "description": "Example of TypeScript program",
   "main": "amqsput.ts",
   "dependencies": {
     "ibmmq": ">=0.9.20"
   },
   "devDependencies": {
     "@types/node": "^16.11.19",
     "typescript": "^4.5.4"
   }
 }

Version 0.9.20 is the first version of the package to include these definitions. But the type information is not needed for running the programs, after compiling into JavaScript. The devDependencies block in here allows us to separate what is needed at runtime from what is needed at build time. You can use npm install --only=prod to create runtime-only environments.

Note: Version 0.9.21 includes package.json.sample which can be used as the skeleton for your own package.json so you don’t have to create one from scratch.

Assuming you have a TypeScript compiler installed (npm install -g typescript if you don’t), you can then

$ npm install
various messages ...
$ tsc amqsput.ts  // This compiles the code into JavaScript
$ node amqsput.js // And now run the real program

Finding errors

The important difference between TypeScript and JavaScript shows up when there is an error in your program. Here is one example of a deliberate mistake. I have swapped two parameters to the Put function. This is the kind of error I regularly make as I can never remember exactly which order everything goes in. So given the amqsput.ts or amqsput.js samples in the repository, the correct line is

mq.Put(hObj, mqmd, pmo, msg, function (err) {

I changed that to

mq.Put(hObj, pmo, mqmd, msg, function (err) {

and tried to build or run the programs. In the JavaScript case, I am in the middle of running the program before receiving an error. And it’s not a particularly helpful error message. You can imagine how horrible it would be if this runtime error showed up only in a rarely-executed path of your program.

$ node amqsput.js
Sample AMQSPUT.JS start
MQCONN to QM1 successful 
MQOPEN of DEV.QUEUE.1 successful
terminate called after throwing an instance of 'Napi::Error'
  what():  Parameter must be of type MQMD
Aborted (core dumped)

Contrast that with the error that comes out when compiling the TypeScript program. We get a useful error message, and even the rare paths will have been checked as we are not running the program.

$ tsc amqsput.ts
amqsput.ts:69:16 - error TS2345: Argument of type 'MQPMO' is not assignable to parameter of type 'MQMD'.
   Type 'MQPMO' is missing the following properties from type 'MQMD': Report, MsgType, Expiry, Feedback, and 23 more.
 69   mq.Put(hObj, pmo, mqmd, msg, function (err) {

Health Warning

This initial release of the TypeScript definitions is subject to change based on feedback. I can already see a couple of enhancements I’d like to make. But getting the code released not just in the github repository but also in the npm installation ecosystem gives the opportunity for people to try it and give comments about the details and value.

Update History

  • 2022-01-14: Initial release
  • 2022-01-26: Version 0.9.21 includes a starting point for the package.json file.

This post was last updated on January 26th, 2022 at 01:53 pm

Leave a Reply

Your email address will not be published. Required fields are marked *