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 🙏

© 2026 – Pkg Stats / Ryan Hefner

my-type-1-customisation

v1.0.5

Published

Customization library for Gupshup Solution Platform

Readme

Customization Library Boilerplate (TypeScript)

Reference implementation for building a Solution Platform customization library in TypeScript.


1. Folder structure

my-type-1-customisation/
├── src/
│   ├── index.ts                  # entry — factory + customFunctions export
│   ├── types.ts                  # local types (CommonServices, CustomFunctions, etc.)
│   ├── customFunctions/
│   │   └── index.ts              # journey hook definitions
│   └── lib/
│       ├── constants.ts          # package name, swagger tags
│       ├── routes.ts             # HTTP route tree
│       └── SolutionApp.ts        # ISolutionApp — lifecycle + getHttpRoutes()
└── dist/                         # compiled output (generated by npm run build)

| File | Responsibility | |------|----------------| | src/index.ts | Exports factory function and customFunctions — required contract | | src/types.ts | Local interfaces for hooks, health check, and common services | | src/customFunctions/index.ts | Journey customFunctions keyed by hook identifier | | src/lib/SolutionApp.ts | App class implementing ISolutionApp | | src/lib/routes.ts | Route tree exported as Route[] | | src/lib/constants.ts | Package display name and swagger tags |

How you organize routes and swagger definitions inside your library is up to you. The platform only reads what getHttpRoutes() returns.


2. Functions and classes

2.1 Entry module (src/index.ts)

import type { CustomisationInstanceConfig } from '@gs/solution-library';
import customFunctions from './customFunctions';
import SolutionApp from './lib/SolutionApp';
import type { CommonServices } from './types';

type CustomisationFactory = {
    (config: CustomisationInstanceConfig, commonServices?: CommonServices): Promise<SolutionApp>;
    customFunctions: typeof customFunctions;
};

const factory: CustomisationFactory = async (config, commonServices) => {
    return new SolutionApp(config, commonServices?.loggerService ?? null);
};

factory.customFunctions = customFunctions;

export = factory;

The library must export:

  • A factory function — async, returns a SolutionApp instance
  • customFunctions — attached on the factory (not a separate export name)

2.2 App class (src/lib/SolutionApp.ts)

import type { CustomisationInstanceConfig, ISolutionApp, RequestData, RouteConfig, SolutionLoggerService } from '@gs/solution-library';
import { routes } from './routes';
import type { HealthCheckDetails } from '../types';

export default class SolutionApp implements ISolutionApp {
    private readonly config: CustomisationInstanceConfig;
    private readonly loggerService: SolutionLoggerService | null;
    private initialized = false;

    constructor(config: CustomisationInstanceConfig, loggerService: SolutionLoggerService | null = null) {
        this.config = config;
        this.loggerService = loggerService;
    }

    getCustomisationInstanceConfig(): CustomisationInstanceConfig {
        return this.config;
    }

    getHttpRoutes(): RouteConfig {
        return { routes };
    }

    async init(): Promise<void> {
        // startup logic
        this.initialized = true;
    }

    async shutdown(): Promise<void> {
        // cleanup logic
        this.initialized = false;
    }

    async getHealthCheckDetails(_requestData?: RequestData): Promise<HealthCheckDetails> {
        return {
            status: this.initialized ? 'SUCCESS' : 'FAIL',
            routeCount: routes.length,
            message: this.initialized ? 'Running' : 'Not initialized',
            customization: `${this.config.customization_hook_type}_${this.config.customization_hook_instance}`,
        };
    }
}

SolutionApp implements ISolutionApp from @gs/solution-library.

| Method | Returns | Purpose | |--------|---------|---------| | getCustomisationInstanceConfig() | CustomisationInstanceConfig | Identity from SPMS | | getHttpRoutes() | RouteConfig | HTTP route tree — see section 3 | | init() | Promise<void> | Startup | | shutdown() | Promise<void> | Cleanup | | getHealthCheckDetails() | Promise<HealthCheckDetails> | Health check |


2.3 Custom functions (src/customFunctions/index.ts)

import type { CustomFunctions } from '../types';

const customFunctions: CustomFunctions = {
    example_hook: {
        name: 'Example Hook',
        run: async (context, _args) => {
            context.services.logger.info('hook executed');
            context.services.userContextManager.setLocalVariable('my_key', 'my_value');
        },
    },
};

export default customFunctions;

Hook types in src/types.ts:

export interface HookContext {
    services: {
        logger: { info: (...args: unknown[]) => void };
        userContextManager: { setLocalVariable: (key: string, value: string) => void };
    };
}

export interface CustomFunctionHook {
    name: string;
    type?: string;
    run: (context: HookContext, args: unknown) => Promise<void> | void;
}

export type CustomFunctions = Record<string, CustomFunctionHook>;

Each key is the hook identifier referenced in the journey builder. The jb customization merges customFunctions from all loaded libraries at startup.


3. Route configuration

Define routes in src/lib/routes.ts and return them from getHttpRoutes().

import { HttpMethod, type Route } from '@gs/solution-library';

export const routes: Route[] = [
    // leaf and parent routes — see below
];

3.1 How URLs are built

Every route is mounted under:

/api/v1/solution_platform/service/{solutionServiceId}/customisation_type/{type}/customisation_instance/{instance}

The platform walks your route tree and concatenates each path:

finalUrl = basePath + parent.path + child.path + ...

Example — SPMS: type = my-type, instance = 1

| You define | Final endpoint | |------------|----------------| | Leaf: { method: 'GET', path: '/status' } | .../customisation_instance/1/status | | Parent: { path: '/admin', childRoutes: [{ method: 'GET', path: '/healthCheck' }] } | .../customisation_instance/1/admin/healthCheck | | Parent + child: { path: '/agant', childRoutes: [{ method: 'GET', path: '/test' }] } | .../customisation_instance/1/agant/test |


3.2 Two route types

Every route object is either a parent or a leaf — never both.

Parent route — groups endpoints under a path prefix

{
    path: '/agant',
    childRoutes: [
        {
            method: HttpMethod.GET,
            path: '/test',
            handler: async () => ({
                statusCode: 200,
                jsonResponse: {
                    id: Math.random().toString(36).slice(2),
                    value: Math.floor(Math.random() * 1000),
                    timestamp: Date.now(),
                },
            }),
            swaggerApiDocs: { /* OpenAPI operation — see 3.5 */ },
        },
    ],
}
  • Has path + childRoutes
  • No method, no handler

Leaf route — handles HTTP requests

{
    method: HttpMethod.GET,
    path: '/hello',
    handler: async (context, requestData) => {
        return {
            statusCode: 200,
            jsonResponse: { status: 'SUCCESS' },
        };
    },
    swaggerApiDocs: { /* OpenAPI operation — see 3.5 */ },
}
  • Has method + path + handler
  • No childRoutes

Use HttpMethod.GET, HttpMethod.POST, etc. from @gs/solution-library.


3.3 Handler

handler: async (context, requestData) => {
    const { body, queryParams, pathParams, headers } = requestData;

    return {
        statusCode: 200,
        jsonResponse: { status: 'SUCCESS', data: body },
    };
}

| Input (requestData) | Description | |-----------------------|-------------| | body | Request body | | queryParams | Query string values | | pathParams | URL path parameters | | headers | Request headers |

| Output | Description | |--------|-------------| | statusCode | HTTP status | | jsonResponse | Response body |


3.4 Composing the route tree

Step 1 — define leaf routes:

import { HttpMethod, type Route } from '@gs/solution-library';

const adminEndpoints: Route[] = [
    {
        method: HttpMethod.GET,
        path: '/healthCheck',
        handler: async () => ({
            statusCode: 200,
            jsonResponse: { status: 'SUCCESS', message: 'Healthy' },
        }),
        swaggerApiDocs: healthCheckDocs,
    },
    {
        method: HttpMethod.POST,
        path: '/log-config',
        handler: async (_context, { body }) => ({
            statusCode: 200,
            jsonResponse: { status: 'SUCCESS', message: 'Updated' },
        }),
        swaggerApiDocs: updateLogConfigDocs,
    },
];

Step 2 — export from routes.ts:

export const routes: Route[] = [
    {
        path: '/admin',
        childRoutes: adminEndpoints,
    },
    {
        method: HttpMethod.GET,
        path: '/hello',
        handler: async () => ({
            statusCode: 200,
            jsonResponse: { message: 'Hello!', status: 'ok' },
        }),
        swaggerApiDocs: { /* ... */ },
    },
];

Step 3 — return from the class:

getHttpRoutes(): RouteConfig {
    return {
        interceptors?: [authInterceptor],
        routes,
    };
}

3.5 Swagger configuration (swaggerApiDocs)

Each leaf route can include swaggerApiDocs — an OpenAPI 3.0 operation object. The platform collects these and renders Swagger UI.

GET endpoint with query parameters

const healthCheckDocs = {
    tags: ['Admin'],
    summary: 'Check bot health status',
    parameters: [
        {
            name: 'monitoring',
            in: 'query',
            schema: { type: 'string', enum: ['true', 'false'] },
        },
    ],
    responses: {
        200: {
            description: 'Health check results',
            content: {
                'application/json': {
                    schema: {
                        type: 'object',
                        properties: {
                            status: { type: 'string', enum: ['SUCCESS', 'FAIL'] },
                            message: { type: 'string' },
                        },
                    },
                },
            },
        },
    },
};

POST endpoint with request body

const updateLogConfigDocs = {
    tags: ['Admin'],
    summary: 'Update log configuration',
    requestBody: {
        required: true,
        content: {
            'application/json': {
                schema: {
                    type: 'object',
                    properties: {
                        level: { type: 'string' },
                    },
                },
            },
        },
    },
    responses: {
        200: {
            description: 'Log configuration updated successfully',
            content: {
                'application/json': {
                    schema: {
                        type: 'object',
                        properties: {
                            status: { type: 'string', example: 'SUCCESS' },
                            message: { type: 'string' },
                        },
                    },
                },
            },
        },
    },
};

| Field | When to use | |-------|-------------| | tags | Group endpoints in Swagger UI | | summary / description | Always on every endpoint | | parameters | GET/DELETE — query, path, or header params | | requestBody | POST/PUT/PATCH — JSON body schema | | responses | Always — at minimum 200; add 400, 401, 500 as needed |


3.6 Route rules

| Rule | Why | |------|-----| | Parent cannot have method or handler | Platform throws Invalid route configuration | | Leaf cannot have childRoutes | Same | | Leaf must have handler | Same | | swaggerApiDocs on leaf routes | Required for endpoint to appear in Swagger UI |


4. Usage

The solution platform loads your library automatically when it is registered in SPMS with a matching library_name.

Load flow

  1. SPMS returns the customization list for the solution
  2. Platform requires your library and calls the factory with CustomisationInstanceConfig and common services
  3. Factory returns a SolutionApp instance
  4. Platform calls init(), registers HTTP routes from getHttpRoutes(), and exposes health check endpoints
  5. jb customization collects customFunctions from all loaded libraries and registers them with bot runtime

Factory contract

// Called by the platform:
const app = await factory(customisationInstanceConfig, commonServices);
// factory.customFunctions is read separately for journey hooks

What you implement

| Area | Where | |------|-------| | HTTP endpoints | src/lib/routes.tsgetHttpRoutes() | | Journey hooks | src/customFunctions/index.tsfactory.customFunctions | | Startup / shutdown | SolutionApp.init() / SolutionApp.shutdown() | | Health metadata | SolutionApp.getHealthCheckDetails() |

Platform types — import from @gs/solution-library:

ISolutionApp, Route, RouteConfig, HttpMethod, CustomisationInstanceConfig, RequestData, SolutionLoggerService


5. Common issues

| Issue | Fix | |-------|-----| | Route not found | Check full URL = base path + all parent paths + leaf path | | Invalid route configuration | Parent/leaf rules violated — see 3.2 | | Swagger not showing | Add swaggerApiDocs on leaf routes | | Hooks not running | Export customFunctions on the factory in src/index.ts |