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
SolutionAppinstance 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, nohandler
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
- SPMS returns the customization list for the solution
- Platform
requires your library and calls the factory withCustomisationInstanceConfigand common services - Factory returns a
SolutionAppinstance - Platform calls
init(), registers HTTP routes fromgetHttpRoutes(), and exposes health check endpoints - jb customization collects
customFunctionsfrom 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 hooksWhat you implement
| Area | Where |
|------|-------|
| HTTP endpoints | src/lib/routes.ts → getHttpRoutes() |
| Journey hooks | src/customFunctions/index.ts → factory.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 |
