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:
namethat uniquely identifies the supplier, it is case-sensitivereportIssueEmailorreportIssueUrlthat allows users to report issues with the given supplier
The full list of attributes is:
namethat uniquely identifies the supplierdescriptionthat describes the suppliermaintainerthat specifies the person or team responsible for the supplierreportIssueEmailthat is intended to open an email client with a pre-filled emailreportIssueUrlthat is intended to open a page where an issue can be reportedalertEmailsarray 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 values.number()- number input with possible null values.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:
| Method | Description |
|---|---|
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 values.number()- number input with possible null values.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
Contextfor 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 iterationsenvironment: string- the current environmentdatasetId: 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
Supplierfrom'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 timeoutSixpackNonRetriableException- 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- extendsSixpackNonRetriableException. Thrown byobtain()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')