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

pact-gen-ts

v0.15.0

Published

Generating pact files from typescript definitions

Downloads

59

Readme

Pact-gen-ts

Pact-gen-ts is a tool for generating contracts using TypeScript type definitions and custom JSDoc tags.

It's an alternative to the pact-js package but without the necessity for writing separate tests. It provides automated, low maintenance and more flexible way to generate contracts according to Pact specification version 2.

Installation and usage

You can install pact-gen-ts using npm:

npm install pact-gen-ts --save-dev

or yarn:

yarn add --dev pact-gen-ts

Next you should create a minimal pacts.config.js configuration file in the root directory:

module.exports = {
    consumer: 'consumer-name',
    providers: [
        {
            provider: 'some-provider',
            files: ['src/api/**/*.ts'],
        },
    ],
};

where files property will be an array of glob patterns pointing to API functions definitions.

After that pact-gen-ts is ready, now you need to mark all API functions which will be analysed:

/**
 * @pact
 */
function fetchComments() {
    // ...
}

The last thing is to execute the command:

pact-gen-ts

which does the analysis and generates pacts in JSON format inside (by default) ./pacts directory.

Compatibility with TypeScript

Due to TypeScript's occasional changes to its compiler API and not following semantic versioning in their releases, the latest versions of pact-gen-ts can only guarantee compatibility with the latest versions of TypeScript.

If you're limited to historical versions of TypeScript, you should install a corresponding version of pact-gen-ts. The below table presents what TS versions pact-gen-ts will work with:

| pact-gen-ts | TypeScript | | -------------- | ---------- | | 0.8 | 4.1 - 4.2 | | 0.9 - 0.9.3 | 4.5 - 4.6 | | 0.9.4 - 0.10.0 | 4.7 - 4.8 | | 0.11.0 | 4.9 | | 0.12.0 | 5.0 | | 0.13.0 | 5.1 | | 0.14.0 | 5.2 - 5.3 | | 0.15.0 | >=5.4 |

Configuration

Pact-gen-ts uses configuration stored in pacts.config.js file in project's root directory:

module.exports = {
    consumer: 'consumer-name',
    buildDir: 'pacts',
    verbose: true,
    providers: [
        {
            provider: 'provider-name',
            files: ['src/api/firstProvider/*.ts'],
            queryArrayFormat: 'indices',
            requestHeaders: {
                authorization: 'auth',
            },
            responseHeaders: {
                'Content-Type': 'application/json',
            },
        },
    ],
};

Options

| Option | Required | Default | Description | | ------------------------------ | :------: | :----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | consumer | Yes | - | Consumer's name | | providers[].provider | Yes | - | Provider's name | | providers[].files | Yes | - | Array of glob patterns where API functions are defined | | providers[].requestHeaders | No | - | Request headers shared across all requests | | providers[].responseHeaders | No | - | Response headers shared across all responses | | providers[].queryArrayFormat | No | "brackets" | Sets separator for array in query - possible options are "indices", "brackets", "comma" and "repeat" (source). The default value is brackets. | | buildDir | No | ./pacts | Directory where generated pacts will be placed | | verbose | No | false | If set to true additional information during pacts generating process will be logged |

You can specify common config shared between providers in pacts.config.js:

module.exports = {
    commonConfigForProviders: {
        queryArrayFormat: 'indices',
        requestHeaders: {
            authorization: 'auth',
        },
        responseHeaders: {
            'Content-Type': 'application/json',
        },
    },
    providers: [
        {
            provider: 'first-provider',
            files: ['src/api1/**/*.ts'],
        },
        {
            provider: 'second-provider',
            files: ['src/api2/**/*.ts'],
        },
        {
            provider: 'third-provider',
            files: ['src/api3/**/*.ts'],
            // you can override common config in provider config
            queryArrayFormat: 'comma',
        },
    ],
};

Integrations

Axios - @pact-axios

Sets REST method, expected body for the current response, expected body for current request and query based on axios definitions.

/**
 * @pact
 * @pact-axios
 * @pact-path /api
 */
async function fetchComments(commentId: string) {
    const {data} = await axios.post<string>('/api', {commentId});
    // ...
}

IMPORTANT - If axios function does not return any type explicitly it is needed to set <void> as an axios return type

/**
 * @pact
 * @pact-axios
 * @pact-path /api
 */
async function fetchComments(commentId: string) {
    await axios.post<void>('/api', {commentId});
}

Pact interaction options

These JSDoc custom tags are used to adjust generated pact interactions.

@pact-method

Sets REST method (GET, POST, PUT, PATCH, DELETE etc.).

/**
 * @pact
 * @pact-method GET
 */
function fetchComments() {
    // ...
}

@pact-path

Sets path.

/**
 * @pact
 * @pact-path /api/images/100
 */
function fetchImage(imageId: number) {
    // ...
}

@pact-description

Sets description, if not provided, description is set using name of the function / variable / property.

/**
 * @pact
 * @pact-description "request to get comments"
 */
function fetchComments() {
    // ...
}

@pact-response-status

Sets response status, if not provided, it is set based on given HTTP method.

/**
 * @pact
 * @pact-response-status 200
 */
function fetchComments() {
    // ...
}

@pact-request-header

Adds a header to the current request, can override option defined in pacts.config.js.

/**
 * @pact
 * @pact-request-header "Content-Type" "application/pdf"
 */
function fetchImage(imageId: number) {
    // ...
}

@pact-response-header

Adds a header to the current response, can override option defined in pacts.config.js.

/**
 * @pact
 * @pact-response-header "Content-Type" "application/pdf"
 */
function fetchImage(imageId: number) {
    // ...
}

@pact-response-body

Sets expected body for the current response.

/**
 * @pact
 */
async function fetchComments() {
    // ...
    const response = await axios.get<string>('/api');
    /** @pact-response-body */
    const data = response.data;
    // ...
}

IMPORTANT - JSDoc has to be applied to separate variable - not directly to axios response

async function fetchComments() {
    // ...
    /** @pact-response-body */ -WRONG!;
    const response = await axios.get<string>('/api');

    /** @pact-response-body */ -CORRECT;
    const data = response.data;
    // ...
}

@pact-request-body

Sets expected body for current request.

function addComment(/** @pact-request-body */ newComment: NewComment) {
    // ...
}

interface NewComment {
    content: string;
    postId: string;
}

or

function addComment(postId: string, commentContent: string) {
    /** @pact-request-body */
    const newComment = {
        postId,
        commentContent,
    };
    // ...
}

@pact-query

Sets query, IMPORTANT - JSDoc tag has to be applied to an object - not a primitive value.

Array separator format can be set using queryArrayFormat in providers options.

function fetchComments(/** @pact-query */ query: Query) {
    // ...
}

interface Query {
    fromUser: string;
    postId: string;
}

or

function fetchComments(pageNo: string) {
    /** @pact-query */
    const params = {
        pageNo,
    };
    // ...
}

Pact matchers

Typescript types can describe the shape of the data and define possible values a variable can store. Pacts definition require specific values, that's why for some individual cases additional information needs to be added.

For example a type string without any modifications will be replaced with simple text which can be later matched by type. Sometimes that's not enough - the matcher needs to be more specific, for instance instead of simple text we need a string in a particular format like [email protected] - that's where a @pact-matcher tag is used.

Pact-matchers are used in the type/interface definition:

interface CommentDTO {
    id: number;
    /** @pact-matcher email */
    user: string;
}

Provided common matchers:

| Pact matcher | Result | | ----------------------------------------------- | ----------------------------------- | | /** @pact-matcher email */ | [email protected] | | /** @pact-matcher iso-date */ | 2021-04-13 | | /** @pact-matcher iso-datetime */ | 2021-04-13T10:14:53+01:00 | | /** @pact-matcher iso-datetime-with-millis */ | 2021-04-13T10:14:53.123+01:00 | | /** @pact-matcher iso-time */ | T10.14.53.342Z | | /** @pact-matcher timestamp */ | Tue, 13 Apr 2021 10:14:53 -0400 | | /** @pact-matcher uuid */ | ce11b6e-d8e1-11e7-9296-cec278b6b50a | | /** @pact-matcher ipv4 */ | 127.0.0.13 | | /** @pact-matcher ipv6 */ | ::ffff:192.0.2.128 | | /** @pact-matcher hex */ | A4C3Ff |

If that's not enough you can easily provide own value using /** @pact-example */:

interface Address {
    city: string;
    address: string;
    /** @pact-example 99-400 */
    postCode: string;
    /** @pact-example 45 */
    age: number;
}