Skip to main content

TypeScript SDK

TypeScript SDK reference to build your own generators.

Define a supplier and a generator

The minimum setup is to have one supplier with one generator. Those can be defined as follows:

import { Supplier } from 'sixpack-sdk'
import { GeneratorItem, s } from 'sixpack-sdk/item'

// Define the input schema
const inputSchema = {
language: s.string().describe('Language of the invoice').required(),
amountToBill: s.number().describe('Amount on the invoice').optional(),
extendedTerm: s.boolean().describe('Should term be extended').optional(),
vatId: s.string().optional(),
}

type InvoiceRequest = s.infer<typeof inputSchema>

// Define the output schema
const outputSchema = {
invoiceId: s.string(),
}

// Define the generate function
function generate(input: InvoiceRequest): s.infer<typeof outputSchema> {
const randomId = String(Date.now() % 1000)
return { invoiceId: randomId }
}

// Define the generator item
const invoiceGenerator: GeneratorItem = {
generate,
metadata: {
name: 'Invoice',
inputSchema,
outputSchema,
reportIssueEmail: 'developer@sixpack.dev',
},
}

// Bootstrap the supplier
async function main() {
const supplier = new Supplier({
name: 'BillingSystem',
reportIssueEmail: 'developer@sixpack.dev',
}).withGenerators(invoiceGenerator)

await supplier.bootstrap()
}

main().catch(console.error)

It is required that the supplier is an instance of Supplier and that generators implement the GeneratorItem interface.

The SupplierOptions has the following mandatory attributes:

  • name that uniquely identifies the supplier, it is case-sensitive
  • reportIssueEmail or reportIssueUrl that allows users to report issues with the given supplier

The full list of attributes is:

  • name that uniquely identifies the supplier
  • description that describes the supplier
  • maintainer that specifies the person or team responsible for the supplier
  • reportIssueEmail that is intended to open an email client with a pre-filled email
  • reportIssueUrl that is intended to open a page where an issue can be reported
  • alertEmails array of emails to receive alerts

Note: only one of reportIssueEmail or reportIssueUrl is mandatory but both can be specified.

Schema Definition

The TypeScript SDK uses a custom schema builder s (similar to Zod) to define input and output schemas. Each field can be annotated with additional information:

  • .describe(description) - describes the field
  • .required() - marks the field as mandatory (enforced by sixpack)
  • .optional() - marks the field as optional
  • .nullDescription(description) - gives text description to let users know what happens when the field is null

Available schema types:

  • s.string() - text input with possible null value
  • s.number() - number input with possible null value
  • s.boolean() - 3-state checkbox (true, false, null)
  • s.select("option1", "option2", "option3", ...) - select input with the given options and possible null value

Here is an example of the previous invoice generator with all possible annotations:

import { Supplier } from 'sixpack-sdk'
import { GeneratorItem, s } from 'sixpack-sdk/item'

const inputSchema = {
language: s.string().describe('Language of the invoice').required(),
amountToBill: s
.number()
.describe('Amount on the invoice')
.nullDescription('100 EUR')
.optional(),
extendedTerm: s
.boolean()
.describe('Should term be extended')
.optional(),
vatId: s.string().optional(), // this field will remain without any additional information
}

type InvoiceRequest = s.infer<typeof inputSchema>

const outputSchema = {
invoiceId: s.string(),
}

function generate(input: InvoiceRequest): s.infer<typeof outputSchema> {
const randomId = String(Date.now() % 1000)
return { invoiceId: randomId }
}

const invoiceGenerator: GeneratorItem = {
generate,
metadata: {
name: 'Invoice Generator',
description: 'Generates invoices in the billing system and returns the invoice id',
maintainer: 'Billing team',
reportIssueEmail: 'developer-billing@sixpack.dev',
reportIssueUrl: 'https://bugtracking.com/billing/reportIssue',
inputSchema,
outputSchema,
},
}

Bootstrap the supplier

To bootstrap the supplier and all declared generators:

import { Supplier } from 'sixpack-sdk'

async function main() {
const supplier = new Supplier({
name: 'BillingSupplier',
reportIssueEmail: 'developer@sixpack.dev',
})
.withGenerators(invoiceGenerator)
.withSixpackUrl('gen.sixpack.dev:443')
.withAccount('myaccount.sixpack.dev')
.withEnvironment('TEST')
.withAuthToken('your-auth-token')
.withClientCertificatePath('./config/generator.pem')
.withClientKeyPath('./config/generator.key')

await supplier.bootstrap()
}

main().catch(console.error)

Configuration Methods

The Supplier class provides fluent configuration methods:

MethodDescription
withSixpackUrl(url)Sets the Sixpack platform URL
withAccount(account)Sets the account namespace
withEnvironment(environment)Sets the target environment
withAuthToken(token)Sets the authentication token
withClientCertificatePath(path)Sets the path to the client certificate
withClientKeyPath(path)Sets the path to the client private key
withVerbose(verbose)Enables verbose logging
withGenerators(...items)Registers generator items
withOrchestrators(...items)Registers orchestrator items

Detailed description of a generator

Generator inputs and outputs

Any generator input is defined using a schema with fields that must be of type s.string(), s.number(), or s.boolean(). The inputs define how the specification form will look on the UI and what inputs will be accepted on the REST interface. Types are mapped as follows:

  • s.string() - text input with possible null value
  • s.number() - number input with possible null value
  • s.boolean() - 3-state checkbox (true, false, null)
  • s.select("option1", "option2", "option3", ...) - select input with the given options and possible null value

Any generator output is a similar schema object. In more complex cases an output of one generator can be used as input of another generator. That is why the field types must follow the same rules.

The schemas used as input or output must have a flat structure (no nested objects), with only allowed field types as listed above.

The generate function

The generate function is mandatory. It performs the required operation to produce a dataset and returns necessary information, usually just a subset of the dataset. It should handle properly null/undefined fields in the input by e.g. generating random values as per required business logic.

It must:

  • Be a function that takes an input object matching the input schema
  • Return an object matching the output schema (or a Promise)
  • Complete within 30s

It may have:

  • A second argument of type Context for accessing iteration info and contextual data

Iterative generation

The generate function can access a Context object as a second parameter to implement iterative patterns:

import { GeneratorItem, Context, s } from 'sixpack-sdk/item'
import { IterateRequest } from 'sixpack-sdk/item'

const inputSchema = {
loanAmount: s.number().required(),
}

const outputSchema = {
loanId: s.string(),
status: s.string(),
}

function generate(
input: s.infer<typeof inputSchema>,
context: Context
): s.infer<typeof outputSchema> {
if (context.iteration === 0) {
// First iteration: do initial setup
context.details['someKey'] = 'myValue'
throw new IterateRequest(10 * 60 * 1000) // Wait 10 minutes
}

const myValue = context.details['someKey']
// ... some business logic
if (!dataReady) {
throw new IterateRequest(5 * 60 * 1000) // Wait 5 more minutes
}

return { loanId: '123', status: 'approved' }
}

The Context interface provides:

  • iteration: number - the current iteration index (starts at 0)
  • details: Record<string, any> - arbitrary contextual data persisted between iterations
  • environment: string - the current environment
  • datasetId: string - the dataset ID

Templates

Templates can be defined to specify input combinations that should be pre-generated and maintained in stock:

const invoiceGenerator: GeneratorItem = {
generate,
metadata: {
name: 'Invoice',
inputSchema,
outputSchema,
reportIssueEmail: 'developer@sixpack.dev',
},
templates: [
{ input: { language: 'English' }, minimum: 10 },
{ input: { language: 'French' }, minimum: 5 },
],
}

This will tell Sixpack to always try to maintain at least 10 invoices in English and 5 invoices in French in stock.

If templates are not defined, Sixpack will compute templates automatically by creating combinations of fields and will try to maintain at least 1 for each combination. If you would like not to pre-generate datasets, return an empty array.

Orchestrator

To combine or chain the output of generators, use an orchestrator. It behaves almost the same as a generator with following differences:

  • Must be deterministic
  • Can use the context.obtain() method to request data from other generators
  • Orchestrator must not import Supplier from 'sixpack-sdk'
  • Orchestrator must not import certain packages such as fs, path, etc.

Bootstrap with an orchestrator

// supplier.ts
import { Supplier } from 'sixpack-sdk'
import { invoiceOrchestrator } from './orchestrator'
import { customerGenerator } from './generators'

async function main() {
const supplier = new Supplier({
name: 'Billing',
description: 'Billing system supplier',
maintainer: 'John Doe',
reportIssueEmail: 'dev@sixpack.dev',
})
.withOrchestrators(invoiceOrchestrator)
.withGenerators(customerGenerator)

await supplier.bootstrap()
}

main().catch(console.error)
// orchestrator.ts
import { OrchestratorItem, OrchestratorContext, s } from 'sixpack-sdk/item'

const inputSchema = {
language: s.string().required(),
amountToBill: s.number().optional(),
extendedTerm: s.boolean().optional(),
vatId: s.string().optional(),
}

type InvoiceRequest = s.infer<typeof inputSchema>

const outputSchema = {
customerId: s.string(),
invoiceId: s.string(),
}

// The generate function must be exported
export async function generate(
input: InvoiceRequest,
context: OrchestratorContext
): Promise<s.infer<typeof outputSchema>> {
// Obtain customer from another generator
const customer = await context.obtain(
'Billing', // supplier name
'CustomerGenerator', // item name
{ language: input.language } // input for the generator
)

const randomId = String(Date.now() % 1000)
return {
customerId: customer.customerId,
invoiceId: randomId,
}
}

export const invoiceOrchestrator: OrchestratorItem = {
generatePath: __filename, // Path to this file
metadata: {
name: 'Invoice Orchestrator',
inputSchema,
outputSchema,
reportIssueEmail: 'dev@sixpack.dev',
description: 'Orchestrates invoice generation with customer data',
},
templates: [
{ input: { language: 'English' }, minimum: 5 },
],
}

The context.obtain() method behaves similarly to dataset request on the UI or via REST - if the requested dataset is in stock, it's returned immediately, otherwise it is requested ad-hoc to the corresponding generator or orchestrator. Orchestrators can obtain data from other orchestrators, making it easy to compose complex datasets using simple native code.

When creating orchestrators, sixpack uses webpack to bundle the orchestrator code and runs it in a sandboxed environment.

Exceptions

The SDK provides the following exception types:

  • IterateRequest - can be thrown to halt the current iteration and restart from the beginning after a specified timeout
  • SixpackNonRetriableException - can be thrown to mark the current dataset as failed and not retry it. Useful when some constraint is violated and retrying would not help.
  • ObtainFailedException - extends SixpackNonRetriableException. Thrown by obtain() in the following events:
    • When the requested dataset generation fails
    • When a required field is null
import { IterateRequest, SixpackNonRetriableException, ObtainFailedException } from 'sixpack-sdk/item'

// Halt and retry after 5 minutes
throw new IterateRequest(5 * 60 * 1000)

// Mark as permanently failed
throw new SixpackNonRetriableException('Invalid input combination')

Custom Logger

You can provide a custom logger implementation:

import { Logger, setLogger } from 'sixpack-sdk/logger'

class CustomLogger implements Logger {
debug(message: string): void {
console.debug(`[DEBUG] ${message}`)
}

info(message: string): void {
console.info(`[INFO] ${message}`)
}

warn(message: string): void {
console.warn(`[WARN] ${message}`)
}

error(message: string): void {
console.error(`[ERROR] ${message}`)
}
}

// Set the custom logger before creating the supplier
setLogger(new CustomLogger())

You can also use the default logger directly:

import { logger } from 'sixpack-sdk/logger'

logger.info('Starting generator...')
logger.error('Something went wrong')