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 { defineGeneratorItem, s } from 'sixpack-sdk/item'

// Define the input schema
const inputSchema = {
country: s.select('Czechia', 'France', 'Italy', 'Hungary', 'Poland').optional(),
language: s.string().describe('Language of the invoice'), // field is required by default
amountToBill: s.number().describe('Amount on the invoice').required(),
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 = defineGeneratorItem({
generate,
metadata: {
name: 'Invoice',
inputSchema,
outputSchema,
outputKind: 'FLAT',
// reportIssueEmail: is inherited from the parent supplier
},
})

// 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 SupplierMetadata / ItemMetadata 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:

  • Unique attributes
    • name that uniquely identifies the supplier or the item, it is case-sensitive
    • description that describes the supplier or the item
  • Attributes inheritable from parent
    • maintainer that specifies the person or team responsible for the supplier or the item
    • 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 specifies emails to which information about the supplier status is sent to.

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 { defineGeneratorItem, 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 = defineGeneratorItem({
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,
outputKind: 'FLAT',
},
})

The input must be a flat object, but the output can also be a list of objects or list of lists of objects. For all three cases, the outputSchema remains the same, describing only the flat object. Field outputKind describes the shape of the returned object:

  • FLAT: Flat object is returned
  • LIST: List of objects defined by outputSchema is returned
  • LIST_OF_LISTS: List of lists of objects defined by outputSchema is returned

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')
.withVerbose(true) // optional: same as setting SIXPACK_VERBOSE=true

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 (equivalent to setting SIXPACK_VERBOSE=true)
withGenerators(...items)Registers generator items
withOrchestrators(...items)Registers orchestrator items
withStatusListener(listener)Registers a supplier status listener callback
withRootCAPath(path)When using sixpack from website other than app.sixpack.dev, root cert path must be provided

Supplier lifecycle status events

You can subscribe to supplier lifecycle status changes by registering a listener with withStatusListener(...). The SDK currently emits CONNECTED once the supplier is connected and workers are started.

import { Supplier, SupplierStatusEvent } from 'sixpack-sdk'

function onSupplierStatusChanged(event: SupplierStatusEvent) {
console.log(`Supplier state changed to: ${event.type}`)
}

async function main() {
const supplier = new Supplier({
name: 'BillingSupplier',
reportIssueEmail: 'developer@sixpack.dev',
})
.withGenerators(invoiceGenerator)
.withStatusListener(onSupplierStatusChanged)

await supplier.bootstrap()
}

Verbose logging

You can enable verbose logging in either of these ways:

export SIXPACK_VERBOSE=true
const supplier = new Supplier({
name: 'BillingSupplier',
reportIssueEmail: 'developer@sixpack.dev',
}).withVerbose(true)

The Supplier loads values from environment variables first, and fluent setter methods (such as .withVerbose(...)) can override them.

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 can be:

  • A single object (outputKind: 'FLAT')
  • A list of objects (outputKind: 'LIST')
  • A nested list of objects (outputKind: 'LIST_OF_LISTS')

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 a value matching metadata.outputKind and metadata.outputSchema (or a Promise)
    • FLAT -> object
    • LIST -> object array
    • LIST_OF_LISTS -> object array of arrays
  • Complete within 30s

It may have:

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

Context is Sixpack's runtime object passed into generate(...) as the optional second argument. Use it when your generator needs information about the current runtime execution rather than just the input. In practice this usually means reading built-in metadata such as the current iteration number, environment, or dataset ID, and persisting custom values so the next iteration can continue where the previous one stopped.

This is Sixpack runtime context, not React context and not any other frontend framework context API.

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['someKey'] = 'myValue'
throw new IterateRequest(10 * 60 * 1000) // Wait 10 minutes
}

const myValue = context['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:

  • readonly iteration: number - the current iteration index (starts at 0)
  • readonly environment: string - the current environment
  • readonly datasetId: string - the dataset ID
  • [key: string]: unknown - arbitrary contextual data persisted between iterations

Treat the built-in fields as Sixpack-managed runtime state. Persist only your own iteration state under custom keys such as context['pollStartedAt'] or context['externalRequestId'].

Custom context values are persisted directly on Context and are available on the next iteration, including after IterateRequest and failed attempts.

Plain generator Context is mainly for runtime metadata and persisted custom values. Additional helper methods are only available on runtime shapes that explicitly include them, such as OrchestratorContext, which adds request* helpers.

Templates

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

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

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

const invoiceGenerator = defineGeneratorItem({
generate,
metadata: {
name: 'Invoice',
inputSchema,
outputSchema,
reportIssueEmail: 'developer@sixpack.dev',
},
templates: [
{ input: { language: 'English', amountToBill: 100 }, minimum: 10 },
{ input: { language: 'French' }, minimum: 5 },
// Optional schema fields may be omitted from template input objects.
{ input: { language: 'Italian', extendedTerm: true }, minimum: 2 },
],
})

// These fail during TypeScript compilation:
// { input: { language: 123 }, minimum: 1 }
// { input: { language: 'English', unexpected: true }, minimum: 1 }

This will tell Sixpack to always try to maintain at least 10 invoices in English and 5 invoices in French in stock. Use defineGeneratorItem(...) or defineOrchestratorItem(...) when you want template inputs inferred from inputSchema. With plain GeneratorItem or OrchestratorItem annotations, that stronger templates[].input inference is not available.

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.request() 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 {
defineOrchestratorItem,
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>> {
// Request customer from another generator
const customer = await context.request(
'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 = defineOrchestratorItem({
// When using CommonJS Modules:
generatePath: __filename,
// Or alternative when using ESM: (import { urlToPath } from "sixpack-sdk/item";).
// generatePath: fileURLToPath(import.meta.url),
// make sure you are not using node:url package, as imports from node: are not supported.
metadata: {
name: 'Invoice Orchestrator',
inputSchema,
outputSchema,
outputKind: 'FLAT',
reportIssueEmail: 'dev@sixpack.dev',
description: 'Orchestrates invoice generation with customer data',
},
templates: [
{ input: { language: 'English' }, minimum: 5 },
],
})

The context.request() 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.

Choosing the correct request method

When an orchestrator requests data from another item, the context method must match the requested item's output kind.

Use this mapping:

  • context.request(...) for items returning a single object (outputKind: 'FLAT')
  • context.requestList(...) for items returning a list of objects (outputKind: 'LIST')
  • context.requestListOfLists(...) for items returning a list of lists (outputKind: 'LIST_OF_LISTS')
  • context.requestMany(...) for collection input (Configuration[]) when the child item returns outputKind: 'FLAT'
  • context.requestManyLists(...) for collection input (Configuration[]) when the child item returns outputKind: 'LIST'

When using collection input (context.requestMany*), output kind is promoted by one level:

  • Child FLAT -> parent receives LIST (use context.requestMany(...))
  • Child LIST -> parent receives LIST_OF_LISTS (use context.requestManyLists(...))
  • Child LIST_OF_LISTS -> not supported for collection input (context.requestMany* throws an error)

Both context.requestMany(...) and context.requestManyLists(...) take a collection of input configurations. Conceptually, Sixpack performs one child request per collection entry and aggregates the results in the returned collection shape. This means the item is called n-times where n is the number of inputs.

If you use the wrong method for the target output kind, the request fails at runtime.

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 request() 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')

Self hosting

If you are self hosting sixpack, (or using instance other than app.sixpack.dev), you will need to provide root certificate (as well as the other two certificates):


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')
.withRootCAPath('./config/ca.pem') // this line needs to be added

await supplier.bootstrap()
}

main().catch(console.error)

You can download all three certificates in settings tab on UI on self hosted instances.

Common issues

__filename and fileURLToPath(import.meta.url) issues

If __filename or import.meta.url are not recognized, please make sure you are using the correct term for your target module system. CommonJS environments use __filename and __dirname, while ES Modules (ESM) rely on import.meta.url. You can verify your current transpilation target by checking the "module" setting in your tsconfig.json file, as well as the "type" field in your package.json.