Context API Reference
Complete reference for all methods available on the feature context (ctx) object.
ctx.step()
Define a feature step with optional rollback.
- TypeScript
- Java
- Go
- .NET
const result = await ctx.step<T>(
tag: string,
handler: () => Promise<T>,
rollback?: ((result: T) => Promise<void>) | null,
options?: IFeatureStepOptions
): Promise<T>;
Map<String, Object> result = ctx.step<T>(
tag: string,
handler: () => Promise<T>,
rollback?: ((result: T) => Promise<void>) | null,
options?: IFeatureStepOptions
): Promise<T>;
result := ctx.step<T>(
tag: string,
handler: () => Promise<T>,
rollback?: ((result: T) => Promise<void>) | null,
options?: IFeatureStepOptions
): Promise<T>;
var result = await ctx.step<T>(
tag: string,
handler: () => Promise<T>,
rollback?: ((result: T) => Promise<void>) | null,
options?: IFeatureStepOptions
): Promise<T>;
Parameters
| Parameter | Type | Description |
|---|---|---|
tag | string | Unique step identifier |
handler | function | Async function that performs the step's work |
rollback | function | null | Optional function called during rollback |
options | object | Step configuration options |
Options
| Option | Type | Default | Description |
|---|---|---|---|
allow_fail | boolean | false | Continue feature if step fails |
retries | number | 0 | Number of retry attempts |
retry_delay | number | 1000 | Milliseconds between retries |
timeout | number | - | Step timeout in milliseconds |
critical | boolean | false | Always rollback this step on failure |
Examples
- TypeScript
- Java
- Go
- .NET
// Basic step
const user = await ctx.step('create-user', async () => {
return ctx.database.insert({ database: 'users', event: 'create', data: {} });
});
// With rollback
const charge = await ctx.step(
'charge',
async () => ctx.api.run({ app: 'stripe', event: 'charge', input: { amount: 1000 } }),
async (result) => ctx.api.run({ app: 'stripe', event: 'refund', input: { chargeId: result.id } })
);
// With options
await ctx.step(
'notify',
async () => ctx.notification.email({ /* ... */ }),
null,
{ allow_fail: true, retries: 3 }
);
// Basic step
Map<String, Object> user = ctx.step('create-user', async () => Map.of(
return ctx.database.insert(Map.of( "database", "users", "event", "create", data: Map.of() ));
));
// With rollback
Map<String, Object> charge = ctx.step(
'charge',
async () => ctx.api.run(Map.of( "app", "stripe", "event", "charge", input: Map.of( "amount", 1000 ) )),
async (result) => ctx.api.run(Map.of( "app", "stripe", "event", "refund", input: Map.of( chargeId: result.id ) ))
);
// With options
ctx.step(
'notify',
async () => ctx.notification.email(Map.of( /* ... */ )),
null,
Map.of( "allow_fail", true, "retries", 3 )
);
// Basic step
user := ctx.step('create-user', async () => {
return ctx.database.insert({ "database": "users", "event": "create", data: {} });
});
// With rollback
charge := ctx.step(
'charge',
async () => ctx.api.run({ "app": "stripe", "event": "charge", input: { "amount": 1000 } }),
async (result) => ctx.api.run({ "app": "stripe", "event": "refund", input: { chargeId: result.id } })
);
// With options
ctx.step(
'notify',
async () => ctx.notification.email({ /* ... */ }),
null,
{ "allow_fail": true, "retries": 3 }
);
// Basic step
var user = await ctx.step('create-user', async () => {
return ctx.database.insert({ ["database"] = "users", ["event"] = "create", data: {} });
});
// With rollback
var charge = await ctx.step(
'charge',
async () => ctx.api.run({ ["app"] = "stripe", ["event"] = "charge", input: { ["amount"] = 1000 } }),
async (result) => ctx.api.run({ ["app"] = "stripe", ["event"] = "refund", input: { chargeId: result.id } })
);
// With options
await ctx.step(
'notify',
async () => ctx.notification.email({ /* ... */ }),
null,
{ ["allow_fail"] = true, ["retries"] = 3 }
);
ctx.action
Call external APIs through connected apps.
ctx.api.run()
- TypeScript
- Java
- Go
- .NET
const result = await ctx.api.run<T>({
app: string,
event: string,
input: Record<string, unknown>, // Flat input format
retries?: number,
timeout?: number,
}): Promise<T>;
Map<String, Object> result = ctx.api.run<T>(Map.of(
app: string,
event: string,
input: Record<string, unknown>, // Flat input format
retries?: number,
timeout?: number
)): Promise<T>;
result := ctx.api.run<T>({
app: string,
event: string,
input: Record<string, unknown>, // Flat input format
retries?: number,
timeout?: number,
}): Promise<T>;
var result = await ctx.api.run<T>({
app: string,
event: string,
input: Record<string, unknown>, // Flat input format
retries?: number,
timeout?: number,
}): Promise<T>;
Flat Input Format
Fields are automatically resolved to the correct location (body, params, query, or headers) based on the action's schema:
- TypeScript
- Java
- Go
- .NET
input: {
amount: 1000, // auto-resolves to body.amount
currency: 'usd', // auto-resolves to body.currency
userId: '123' // auto-resolves to params.userId
}
input: Map.of(
"amount", 1000, // auto-resolves to body.amount
"currency", "usd", // auto-resolves to body.currency
"userId", "123" // auto-resolves to params.userId
)
input: {
"amount": 1000, // auto-resolves to body.amount
"currency": "usd", // auto-resolves to body.currency
"userId": "123" // auto-resolves to params.userId
}
input: {
["amount"] = 1000, // auto-resolves to body.amount
["currency"] = "usd", // auto-resolves to body.currency
["userId"] = "123" // auto-resolves to params.userId
}
For conflicting keys, use prefix syntax:
| Prefix | Target Location | Example |
|---|---|---|
body: | Request body | 'body:id': 'item_456' |
params: | Route parameters | 'params:id': 'user_123' |
query: | Query parameters | 'query:limit': 10 |
headers: | HTTP headers | 'headers:X-Custom': 'value' |
Example
- TypeScript
- Java
- Go
- .NET
const charge = await ctx.api.run({
app: 'stripe',
event: 'create-charge',
input: {
amount: 1000,
currency: 'usd',
'headers:Idempotency-Key': ctx.feature_id
},
retries: 3,
timeout: 10000,
});
Map<String, Object> charge = ctx.api.run(Map.of(
"app", "stripe",
"event", "create-charge",
input: Map.of(
"amount", 1000,
"currency", "usd",
'headers:Idempotency-Key': ctx.feature_id
),
"retries", 3,
"timeout", 10000
));
charge := ctx.api.run({
"app": "stripe",
"event": "create-charge",
input: {
"amount": 1000,
"currency": "usd",
'headers:Idempotency-Key': ctx.feature_id
},
"retries": 3,
"timeout": 10000,
});
var charge = await ctx.api.run({
["app"] = "stripe",
["event"] = "create-charge",
input: {
["amount"] = 1000,
["currency"] = "usd",
"headers:Idempotency-Key": ctx.feature_id
},
["retries"] = 3,
["timeout"] = 10000,
});
ctx.database
Database operations.
ctx.database.insert()
- TypeScript
- Java
- Go
- .NET
const record = await ctx.database.insert<T>({
database: string,
event: string,
data: Record<string, unknown>,
}): Promise<T>;
Map<String, Object> record = ctx.database.insert<T>(Map.of(
database: string,
event: string,
data: Record<string, unknown>
)): Promise<T>;
record := ctx.database.insert<T>({
database: string,
event: string,
data: Record<string, unknown>,
}): Promise<T>;
var record = await ctx.database.insert<T>({
database: string,
event: string,
data: Record<string, unknown>,
}): Promise<T>;
ctx.database.query()
- TypeScript
- Java
- Go
- .NET
const records = await ctx.database.query<T>({
database: string,
event: string,
params?: Record<string, unknown>,
}): Promise<T[]>;
Map<String, Object> records = ctx.database.query<T>(Map.of(
database: string,
event: string,
params?: Record<string, unknown>
)): Promise<T[]>;
records := ctx.database.query<T>({
database: string,
event: string,
params?: Record<string, unknown>,
}): Promise<T[]>;
var records = await ctx.database.query<T>({
database: string,
event: string,
params?: Record<string, unknown>,
}): Promise<T[]>;
ctx.database.update()
- TypeScript
- Java
- Go
- .NET
const updated = await ctx.database.update<T>({
database: string,
event: string,
where: Record<string, unknown>,
data: Record<string, unknown>,
}): Promise<T>;
Map<String, Object> updated = ctx.database.update<T>(Map.of(
database: string,
event: string,
where: Record<string, unknown>,
data: Record<string, unknown>
)): Promise<T>;
updated := ctx.database.update<T>({
database: string,
event: string,
where: Record<string, unknown>,
data: Record<string, unknown>,
}): Promise<T>;
var updated = await ctx.database.update<T>({
database: string,
event: string,
where: Record<string, unknown>,
data: Record<string, unknown>,
}): Promise<T>;
ctx.database.delete()
- TypeScript
- Java
- Go
- .NET
const success = await ctx.database.delete({
database: string,
event: string,
where: Record<string, unknown>,
}): Promise<boolean>;
Map<String, Object> success = ctx.database.delete(Map.of(
database: string,
event: string,
where: Record<string, unknown>
)): Promise<boolean>;
success := ctx.database.delete({
database: string,
event: string,
where: Record<string, unknown>,
}): Promise<boolean>;
var success = await ctx.database.delete({
database: string,
event: string,
where: Record<string, unknown>,
}): Promise<boolean>;
ctx.database.execute()
- TypeScript
- Java
- Go
- .NET
const result = await ctx.database.execute<T>({
database: string,
event: string,
input: IDbActionRequest,
retries?: number,
timeout?: number,
}): Promise<T>;
Map<String, Object> result = ctx.database.execute<T>(Map.of(
database: string,
event: string,
input: IDbActionRequest,
retries?: number,
timeout?: number
)): Promise<T>;
result := ctx.database.execute<T>({
database: string,
event: string,
input: IDbActionRequest,
retries?: number,
timeout?: number,
}): Promise<T>;
var result = await ctx.database.execute<T>({
database: string,
event: string,
input: IDbActionRequest,
retries?: number,
timeout?: number,
}): Promise<T>;
ctx.graph
Graph database operations.
ctx.graph.createNode()
- TypeScript
- Java
- Go
- .NET
const node = await ctx.graph.createNode<T>({
graph: string,
labels: string[],
properties: Record<string, unknown>,
}): Promise<T>;
Map<String, Object> node = ctx.graph.createNode<T>(Map.of(
graph: string,
labels: string[],
properties: Record<string, unknown>
)): Promise<T>;
node := ctx.graph.createNode<T>({
graph: string,
labels: string[],
properties: Record<string, unknown>,
}): Promise<T>;
var node = await ctx.graph.createNode<T>({
graph: string,
labels: string[],
properties: Record<string, unknown>,
}): Promise<T>;
ctx.graph.updateNode()
- TypeScript
- Java
- Go
- .NET
const node = await ctx.graph.updateNode<T>({
graph: string,
id: string | number,
properties: Record<string, unknown>,
}): Promise<T>;
Map<String, Object> node = ctx.graph.updateNode<T>(Map.of(
graph: string,
id: string | number,
properties: Record<string, unknown>
)): Promise<T>;
node := ctx.graph.updateNode<T>({
graph: string,
id: string | number,
properties: Record<string, unknown>,
}): Promise<T>;
var node = await ctx.graph.updateNode<T>({
graph: string,
id: string | number,
properties: Record<string, unknown>,
}): Promise<T>;
ctx.graph.deleteNode()
- TypeScript
- Java
- Go
- .NET
await ctx.graph.deleteNode({
graph: string,
id: string | number,
}): Promise<void>;
ctx.graph.deleteNode(Map.of(
graph: string,
id: string | number
)): Promise<void>;
ctx.graph.deleteNode({
graph: string,
id: string | number,
}): Promise<void>;
await ctx.graph.deleteNode({
graph: string,
id: string | number,
}): Promise<void>;
ctx.graph.createRelationship()
- TypeScript
- Java
- Go
- .NET
const rel = await ctx.graph.createRelationship<T>({
graph: string,
from: string,
to: string,
type: string,
properties?: Record<string, unknown>,
}): Promise<T>;
Map<String, Object> rel = ctx.graph.createRelationship<T>(Map.of(
graph: string,
from: string,
to: string,
type: string,
properties?: Record<string, unknown>
)): Promise<T>;
rel := ctx.graph.createRelationship<T>({
graph: string,
from: string,
to: string,
type: string,
properties?: Record<string, unknown>,
}): Promise<T>;
var rel = await ctx.graph.createRelationship<T>({
graph: string,
from: string,
to: string,
type: string,
properties?: Record<string, unknown>,
}): Promise<T>;
ctx.graph.deleteRelationship()
- TypeScript
- Java
- Go
- .NET
await ctx.graph.deleteRelationship({
graph: string,
id: string | number,
}): Promise<void>;
ctx.graph.deleteRelationship(Map.of(
graph: string,
id: string | number
)): Promise<void>;
ctx.graph.deleteRelationship({
graph: string,
id: string | number,
}): Promise<void>;
await ctx.graph.deleteRelationship({
graph: string,
id: string | number,
}): Promise<void>;
ctx.graph.query()
- TypeScript
- Java
- Go
- .NET
const results = await ctx.graph.query<T>({
graph: string,
action: string,
params?: Record<string, unknown>,
}): Promise<T[]>;
Map<String, Object> results = ctx.graph.query<T>(Map.of(
graph: string,
action: string,
params?: Record<string, unknown>
)): Promise<T[]>;
results := ctx.graph.query<T>({
graph: string,
action: string,
params?: Record<string, unknown>,
}): Promise<T[]>;
var results = await ctx.graph.query<T>({
graph: string,
action: string,
params?: Record<string, unknown>,
}): Promise<T[]>;
ctx.graph.execute()
- TypeScript
- Java
- Go
- .NET
const result = await ctx.graph.execute<T>({
graph: string,
action: string,
input: Record<string, unknown>,
}): Promise<T>;
Map<String, Object> result = ctx.graph.execute<T>(Map.of(
graph: string,
action: string,
input: Record<string, unknown>
)): Promise<T>;
result := ctx.graph.execute<T>({
graph: string,
action: string,
input: Record<string, unknown>,
}): Promise<T>;
var result = await ctx.graph.execute<T>({
graph: string,
action: string,
input: Record<string, unknown>,
}): Promise<T>;
ctx.notification
Send notifications.
ctx.notification.email()
- TypeScript
- Java
- Go
- .NET
await ctx.notification.email({
notification: string,
event: string,
recipients: string[],
subject: Record<string, unknown>,
template: Record<string, unknown>,
}): Promise<void>;
ctx.notification.email(Map.of(
notification: string,
event: string,
recipients: string[],
subject: Record<string, unknown>,
template: Record<string, unknown>
)): Promise<void>;
ctx.notification.email({
notification: string,
event: string,
recipients: string[],
subject: Record<string, unknown>,
template: Record<string, unknown>,
}): Promise<void>;
await ctx.notification.email({
notification: string,
event: string,
recipients: string[],
subject: Record<string, unknown>,
template: Record<string, unknown>,
}): Promise<void>;
ctx.notification.sms()
- TypeScript
- Java
- Go
- .NET
await ctx.notification.sms({
notification: string,
event: string,
phones: string[],
message: Record<string, unknown>,
}): Promise<void>;
ctx.notification.sms(Map.of(
notification: string,
event: string,
phones: string[],
message: Record<string, unknown>
)): Promise<void>;
ctx.notification.sms({
notification: string,
event: string,
phones: string[],
message: Record<string, unknown>,
}): Promise<void>;
await ctx.notification.sms({
notification: string,
event: string,
phones: string[],
message: Record<string, unknown>,
}): Promise<void>;
ctx.notification.push()
- TypeScript
- Java
- Go
- .NET
await ctx.notification.push({
notification: string,
event: string,
tokens: string[],
title: Record<string, unknown>,
body: Record<string, unknown>,
data?: Record<string, unknown>,
}): Promise<void>;
ctx.notification.push(Map.of(
notification: string,
event: string,
tokens: string[],
title: Record<string, unknown>,
body: Record<string, unknown>,
data?: Record<string, unknown>
)): Promise<void>;
ctx.notification.push({
notification: string,
event: string,
tokens: string[],
title: Record<string, unknown>,
body: Record<string, unknown>,
data?: Record<string, unknown>,
}): Promise<void>;
await ctx.notification.push({
notification: string,
event: string,
tokens: string[],
title: Record<string, unknown>,
body: Record<string, unknown>,
data?: Record<string, unknown>,
}): Promise<void>;
ctx.notification.send()
- TypeScript
- Java
- Go
- .NET
await ctx.notification.send({
notification: string,
event: string,
input: INotificationRequest,
retries?: number,
}): Promise<void>;
ctx.notification.send(Map.of(
notification: string,
event: string,
input: INotificationRequest,
retries?: number
)): Promise<void>;
ctx.notification.send({
notification: string,
event: string,
input: INotificationRequest,
retries?: number,
}): Promise<void>;
await ctx.notification.send({
notification: string,
event: string,
input: INotificationRequest,
retries?: number,
}): Promise<void>;
ctx.storage
File operations.
ctx.storage.upload()
- TypeScript
- Java
- Go
- .NET
const result = await ctx.storage.upload({
storage: string,
event: string,
input: {
buffer: Buffer | string,
fileName: string,
mimeType: string,
},
retries?: number,
}): Promise<{
file_key: string,
url?: string,
size?: number,
content_type?: string,
}>;
Map<String, Object> result = ctx.storage.upload(Map.of(
storage: string,
event: string,
input: Map.of(
buffer: Buffer | string,
fileName: string,
mimeType: string
),
retries?: number
)): Promise<Map.of(
file_key: string,
url?: string,
size?: number,
content_type?: string
)>;
result := ctx.storage.upload({
storage: string,
event: string,
input: {
buffer: Buffer | string,
fileName: string,
mimeType: string,
},
retries?: number,
}): Promise<{
file_key: string,
url?: string,
size?: number,
content_type?: string,
}>;
var result = await ctx.storage.upload({
storage: string,
event: string,
input: {
buffer: Buffer | string,
fileName: string,
mimeType: string,
},
retries?: number,
}): Promise<{
file_key: string,
url?: string,
size?: number,
content_type?: string,
}>;
ctx.storage.download()
- TypeScript
- Java
- Go
- .NET
const file = await ctx.storage.download({
storage: string,
event: string,
input: { file_key: string },
}): Promise<{
content: Buffer | string,
content_type?: string,
size?: number,
}>;
Map<String, Object> file = ctx.storage.download(Map.of(
storage: string,
event: string,
input: Map.of( file_key: string )
)): Promise<Map.of(
content: Buffer | string,
content_type?: string,
size?: number
)>;
file := ctx.storage.download({
storage: string,
event: string,
input: { file_key: string },
}): Promise<{
content: Buffer | string,
content_type?: string,
size?: number,
}>;
var file = await ctx.storage.download({
storage: string,
event: string,
input: { file_key: string },
}): Promise<{
content: Buffer | string,
content_type?: string,
size?: number,
}>;
ctx.storage.delete()
- TypeScript
- Java
- Go
- .NET
await ctx.storage.delete({
storage: string,
event: string,
input: { file_key: string },
}): Promise<void>;
ctx.storage.delete(Map.of(
storage: string,
event: string,
input: Map.of( file_key: string )
)): Promise<void>;
ctx.storage.delete({
storage: string,
event: string,
input: { file_key: string },
}): Promise<void>;
await ctx.storage.delete({
storage: string,
event: string,
input: { file_key: string },
}): Promise<void>;
ctx.messaging
Message broker (Ductape primitive). Use this to publish to broker topics.
ctx.messaging.produce()
- TypeScript
- Java
- Go
- .NET
await ctx.messaging.produce({
event: string, // "broker-tag:topic-tag" (e.g. "order-events:payment-processed")
message: Record<string, unknown>,
}): Promise<void>;
ctx.messaging.produce(Map.of(
event: string, // "broker-tag:topic-tag" (e.g. "order-events:payment-processed")
message: Record<string, unknown>
)): Promise<void>;
ctx.messaging.produce({
event: string, // "broker-tag:topic-tag" (e.g. "order-events:payment-processed")
message: Record<string, unknown>,
}): Promise<void>;
await ctx.messaging.produce({
event: string, // "broker-tag:topic-tag" (e.g. "order-events:payment-processed")
message: Record<string, unknown>,
}): Promise<void>;
ctx.publish
Deprecated. Prefer ctx.messaging.produce() above.
ctx.publish.send()
- TypeScript
- Java
- Go
- .NET
await ctx.publish.send({
broker: string,
event: string,
input: {
message: Record<string, unknown>,
},
retries?: number,
}): Promise<void>;
ctx.publish.send(Map.of(
broker: string,
event: string,
input: Map.of(
message: Record<string, unknown>
),
retries?: number
)): Promise<void>;
ctx.publish.send({
broker: string,
event: string,
input: {
message: Record<string, unknown>,
},
retries?: number,
}): Promise<void>;
await ctx.publish.send({
broker: string,
event: string,
input: {
message: Record<string, unknown>,
},
retries?: number,
}): Promise<void>;
ctx.quota
Rate limiting.
ctx.quota.execute()
- TypeScript
- Java
- Go
- .NET
const result = await ctx.quota.execute<T>({
quota: string,
input: Record<string, unknown>,
timeout?: number,
}): Promise<T>;
Map<String, Object> result = ctx.quota.execute<T>(Map.of(
quota: string,
input: Record<string, unknown>,
timeout?: number
)): Promise<T>;
result := ctx.quota.execute<T>({
quota: string,
input: Record<string, unknown>,
timeout?: number,
}): Promise<T>;
var result = await ctx.quota.execute<T>({
quota: string,
input: Record<string, unknown>,
timeout?: number,
}): Promise<T>;
ctx.fallback
Failure handling with alternatives.
ctx.fallback.execute()
- TypeScript
- Java
- Go
- .NET
const result = await ctx.fallback.execute<T>({
fallback: string,
input: Record<string, unknown>,
timeout?: number,
}): Promise<T>;
Map<String, Object> result = ctx.fallback.execute<T>(Map.of(
fallback: string,
input: Record<string, unknown>,
timeout?: number
)): Promise<T>;
result := ctx.fallback.execute<T>({
fallback: string,
input: Record<string, unknown>,
timeout?: number,
}): Promise<T>;
var result = await ctx.fallback.execute<T>({
fallback: string,
input: Record<string, unknown>,
timeout?: number,
}): Promise<T>;
ctx.healthcheck
Check service availability.
ctx.healthcheck.getStatus()
- TypeScript
- Java
- Go
- .NET
const status = await ctx.healthcheck.getStatus(tag: string): Promise<{
status: 'available' | 'unavailable',
lastChecked?: string,
lastLatency?: number,
}>;
Map<String, Object> status = ctx.healthcheck.getStatus(tag: string): Promise<Map.of(
"status", "available" | 'unavailable',
lastChecked?: string,
lastLatency?: number
)>;
status := ctx.healthcheck.getStatus(tag: string): Promise<{
"status": "available" | 'unavailable',
lastChecked?: string,
lastLatency?: number,
}>;
var status = await ctx.healthcheck.getStatus(tag: string): Promise<{
["status"] = "available" | 'unavailable',
lastChecked?: string,
lastLatency?: number,
}>;
Control Flow
ctx.sleep()
Pause feature execution.
- TypeScript
- Java
- Go
- .NET
await ctx.sleep(duration: number | string): Promise<void>;
ctx.sleep(duration: number | string): Promise<void>;
ctx.sleep(duration: number | string): Promise<void>;
await ctx.sleep(duration: number | string): Promise<void>;
| Format | Example | Description |
|---|---|---|
| number | 5000 | Milliseconds |
| string | '5s' | Seconds |
| string | '5m' | Minutes |
| string | '1h' | Hours |
| string | '1d' | Days |
ctx.waitForSignal()
Wait for an external signal.
- TypeScript
- Java
- Go
- .NET
const payload = await ctx.waitForSignal<T>(
signal: string | string[],
options?: { timeout?: number | string }
): Promise<T>;
Map<String, Object> payload = ctx.waitForSignal<T>(
signal: string | string[],
options?: Map.of( timeout?: number | string )
): Promise<T>;
payload := ctx.waitForSignal<T>(
signal: string | string[],
options?: { timeout?: number | string }
): Promise<T>;
var payload = await ctx.waitForSignal<T>(
signal: string | string[],
options?: { timeout?: number | string }
): Promise<T>;
ctx.checkpoint()
Save state for recovery.
- TypeScript
- Java
- Go
- .NET
await ctx.checkpoint(
name: string,
metadata?: Record<string, unknown>
): Promise<void>;
ctx.checkpoint(
name: string,
metadata?: Record<string, unknown>
): Promise<void>;
ctx.checkpoint(
name: string,
metadata?: Record<string, unknown>
): Promise<void>;
await ctx.checkpoint(
name: string,
metadata?: Record<string, unknown>
): Promise<void>;
ctx.triggerRollback()
Manually trigger rollback.
- TypeScript
- Java
- Go
- .NET
const result = await ctx.triggerRollback(reason: string): Promise<{
success: boolean,
rolled_back_steps: string[],
failed_steps?: Array<{ tag: string, error: string }>,
reason: string,
}>;
Map<String, Object> result = ctx.triggerRollback(reason: string): Promise<Map.of(
success: boolean,
rolled_back_steps: string[],
failed_steps?: Array<Map.of( tag: string, error: string )>,
reason: string
)>;
result := ctx.triggerRollback(reason: string): Promise<{
success: boolean,
rolled_back_steps: string[],
failed_steps?: Array<{ tag: string, error: string }>,
reason: string,
}>;
var result = await ctx.triggerRollback(reason: string): Promise<{
success: boolean,
rolled_back_steps: string[],
failed_steps?: Array<{ tag: string, error: string }>,
reason: string,
}>;
ctx.feature()
Run a child feature.
- TypeScript
- Java
- Go
- .NET
const result = await ctx.feature<TInput, TOutput>(
childId: string,
tag: string,
input: TInput,
options?: {
timeout?: number | string,
retries?: number,
parent_close_policy?: 'terminate' | 'abandon' | 'request_cancel',
idempotency_key?: string,
}
): Promise<TOutput>;
Map<String, Object> result = ctx.feature<TInput, TOutput>(
childId: string,
tag: string,
input: TInput,
options?: Map.of(
timeout?: number | string,
retries?: number,
parent_close_policy?: 'terminate' | 'abandon' | 'request_cancel',
idempotency_key?: string
)
): Promise<TOutput>;
result := ctx.feature<TInput, TOutput>(
childId: string,
tag: string,
input: TInput,
options?: {
timeout?: number | string,
retries?: number,
parent_close_policy?: 'terminate' | 'abandon' | 'request_cancel',
idempotency_key?: string,
}
): Promise<TOutput>;
var result = await ctx.feature<TInput, TOutput>(
childId: string,
tag: string,
input: TInput,
options?: {
timeout?: number | string,
retries?: number,
parent_close_policy?: 'terminate' | 'abandon' | 'request_cancel',
idempotency_key?: string,
}
): Promise<TOutput>;
State Management
ctx.setState()
- TypeScript
- Java
- Go
- .NET
ctx.setState(key: string, value: unknown): void;
ctx.setState(key: string, value: unknown): void;
ctx.setState(key: string, value: unknown): void;
ctx.setState(key: string, value: unknown): void;
ctx.getState()
- TypeScript
- Java
- Go
- .NET
const value = ctx.getState<T>(key: string): T | undefined;
Map<String, Object> value = ctx.getState<T>(key: string): T | undefined;
value := ctx.getState<T>(key: string): T | undefined;
var value = ctx.getState<T>(key: string): T | undefined;
Read-Only Properties
| Property | Type | Description |
|---|---|---|
ctx.state | object | All feature state |
ctx.steps | object | Results from completed steps |
ctx.completed_steps | string[] | Tags of completed steps |
ctx.current_step | string | null | Currently executing step |
Data Access
ctx.input
Read-only feature input data.
- TypeScript
- Java
- Go
- .NET
const email = ctx.input.email;
const items = ctx.input.order.items;
Map<String, Object> email = ctx.input.email;
Map<String, Object> items = ctx.input.order.items;
email := ctx.input.email;
items := ctx.input.order.items;
var email = ctx.input.email;
var items = ctx.input.order.items;
ctx.variable()
Get app variable value.
- TypeScript
- Java
- Go
- .NET
const value = ctx.variable(app: string, key: string): unknown;
Map<String, Object> value = ctx.variable(app: string, key: string): unknown;
value := ctx.variable(app: string, key: string): unknown;
var value = ctx.variable(app: string, key: string): unknown;
ctx.constant()
Get app constant value.
- TypeScript
- Java
- Go
- .NET
const value = ctx.constant(app: string, key: string): unknown;
Map<String, Object> value = ctx.constant(app: string, key: string): unknown;
value := ctx.constant(app: string, key: string): unknown;
var value = ctx.constant(app: string, key: string): unknown;
ctx.token()
Get token value.
- TypeScript
- Java
- Go
- .NET
const token = ctx.token(key: string): string;
Map<String, Object> token = ctx.token(key: string): string;
token := ctx.token(key: string): string;
var token = ctx.token(key: string): string;
ctx.default()
Provide fallback for undefined values.
- TypeScript
- Java
- Go
- .NET
const value = ctx.default<T>(value: T | undefined, fallback: T): T;
Map<String, Object> value = ctx.default<T>(value: T | undefined, fallback: T): T;
value := ctx.default<T>(value: T | undefined, fallback: T): T;
var value = ctx.default<T>(value: T | undefined, fallback: T): T;
ctx.transform
Data transformation utilities.
| Method | Description |
|---|---|
size(obj) | Number of keys in object |
length(arr) | Length of array or string |
parseJson<T>(str) | Parse JSON string |
stringify(obj) | Convert to JSON string |
upper(str) | Uppercase string |
lower(str) | Lowercase string |
trim(str) | Trim whitespace |
split(str, sep) | Split string |
join(arr, sep) | Join array |
now() | Current timestamp |
formatDate(date, fmt) | Format date |
ctx.log
Logging methods.
- TypeScript
- Java
- Go
- .NET
ctx.log.debug(message: string, data?: Record<string, unknown>): void;
ctx.log.info(message: string, data?: Record<string, unknown>): void;
ctx.log.warn(message: string, data?: Record<string, unknown>): void;
ctx.log.error(message: string, data?: Record<string, unknown>): void;
ctx.log.debug(message: string, data?: Record<string, unknown>): void;
ctx.log.info(message: string, data?: Record<string, unknown>): void;
ctx.log.warn(message: string, data?: Record<string, unknown>): void;
ctx.log.error(message: string, data?: Record<string, unknown>): void;
ctx.log.debug(message: string, data?: Record<string, unknown>): void;
ctx.log.info(message: string, data?: Record<string, unknown>): void;
ctx.log.warn(message: string, data?: Record<string, unknown>): void;
ctx.log.error(message: string, data?: Record<string, unknown>): void;
ctx.log.debug(message: string, data?: Record<string, unknown>): void;
ctx.log.info(message: string, data?: Record<string, unknown>): void;
ctx.log.warn(message: string, data?: Record<string, unknown>): void;
ctx.log.error(message: string, data?: Record<string, unknown>): void;
Metadata Properties
| Property | Type | Description |
|---|---|---|
ctx.feature_id | string | Unique execution ID |
ctx.feature_tag | string | Feature tag |
ctx.env | string | Environment (dev, staging, prd) |
ctx.product | string | Product tag |
ctx.context | object | Additional metadata |
ctx.session | object | Session info (if provided) |
ctx.auth | object | Authentication data |
Replay, Restart & Resume Properties
Properties for detecting and handling replayed, restarted, or resumed features:
| Property | Type | Description |
|---|---|---|
ctx.is_replay | boolean | Whether this is a replay execution |
ctx.is_restart | boolean | Whether this is a restart execution |
ctx.is_restored | boolean | Whether this is a resumed execution |
ctx.replayed_from | string | undefined | Original feature ID (if replay) |
ctx.restarted_from | string | undefined | Original feature ID (if restart) |
ctx.resumed_from | string | undefined | Original feature ID (if resume) |
ctx.replay_reason | string | undefined | Reason for replay |
ctx.restart_reason | string | undefined | Reason for restart |
ctx.restored_checkpoint | object | undefined | Checkpoint info (if resumed) |
ctx.original_input | object | undefined | Original input (for restart comparison) |
Example: Detecting Replay/Restart/Resume
- TypeScript
- Java
- Go
- .NET
handler: async (ctx) => {
// Log execution context
if (ctx.is_replay) {
ctx.log.info('Replaying feature', {
original_id: ctx.replayed_from,
reason: ctx.replay_reason,
});
}
if (ctx.is_restart) {
ctx.log.info('Restarted feature', {
original_id: ctx.restarted_from,
reason: ctx.restart_reason,
original_input: ctx.original_input,
});
}
if (ctx.is_restored) {
ctx.log.info('Resuming feature', {
original_id: ctx.resumed_from,
checkpoint: ctx.restored_checkpoint?.name,
completed_steps: ctx.completed_steps,
});
// Skip already completed steps
if (ctx.completed_steps.includes('validate-order')) {
ctx.log.info('Skipping validate-order (already completed)');
}
}
// Feature logic...
}
handler: async (ctx) => Map.of(
// Log execution context
if (ctx.is_replay) Map.of(
ctx.log.info('Replaying feature', Map.of(
original_id: ctx.replayed_from,
reason: ctx.replay_reason
));
)
if (ctx.is_restart) Map.of(
ctx.log.info('Restarted feature', Map.of(
original_id: ctx.restarted_from,
reason: ctx.restart_reason,
original_input: ctx.original_input
));
)
if (ctx.is_restored) Map.of(
ctx.log.info('Resuming feature', Map.of(
original_id: ctx.resumed_from,
checkpoint: ctx.restored_checkpoint?.name,
completed_steps: ctx.completed_steps
));
// Skip already completed steps
if (ctx.completed_steps.includes('validate-order')) Map.of(
ctx.log.info('Skipping validate-order (already completed)');
)
)
// Feature logic...
)
handler: async (ctx) => {
// Log execution context
if (ctx.is_replay) {
ctx.log.info('Replaying feature', {
original_id: ctx.replayed_from,
reason: ctx.replay_reason,
});
}
if (ctx.is_restart) {
ctx.log.info('Restarted feature', {
original_id: ctx.restarted_from,
reason: ctx.restart_reason,
original_input: ctx.original_input,
});
}
if (ctx.is_restored) {
ctx.log.info('Resuming feature', {
original_id: ctx.resumed_from,
checkpoint: ctx.restored_checkpoint?.name,
completed_steps: ctx.completed_steps,
});
// Skip already completed steps
if (ctx.completed_steps.includes('validate-order')) {
ctx.log.info('Skipping validate-order (already completed)');
}
}
// Feature logic...
}
handler: async (ctx) => {
// Log execution context
if (ctx.is_replay) {
ctx.log.info('Replaying feature', {
original_id: ctx.replayed_from,
reason: ctx.replay_reason,
});
}
if (ctx.is_restart) {
ctx.log.info('Restarted feature', {
original_id: ctx.restarted_from,
reason: ctx.restart_reason,
original_input: ctx.original_input,
});
}
if (ctx.is_restored) {
ctx.log.info('Resuming feature', {
original_id: ctx.resumed_from,
checkpoint: ctx.restored_checkpoint?.name,
completed_steps: ctx.completed_steps,
});
// Skip already completed steps
if (ctx.completed_steps.includes('validate-order')) {
ctx.log.info('Skipping validate-order (already completed)');
}
}
// Feature logic...
}
SDK Feature Methods
Methods available on ductape.features for managing features outside of the handler context.
ductape.features.define()
Define and register a feature:
- TypeScript
- Java
- Go
- .NET
const feature = await ductape.features.define({
product: string,
tag: string,
name: string,
description?: string,
options?: IFeatureOptions,
signals?: Record<string, { input: Record<string, string> }>,
queries?: Record<string, { handler: (ctx) => unknown }>,
envs?: Array<{ slug: string; active: boolean }>,
handler: (ctx: IFeatureContext) => Promise<TOutput>,
});
Map<String, Object> feature = ductape.features.define(Map.of(
product: string,
tag: string,
name: string,
description?: string,
options?: IFeatureOptions,
signals?: Record<string, Map.of( input: Record<string, string> )>,
queries?: Record<string, Map.of( handler: (ctx) => unknown )>,
envs?: Array<Map.of( slug: string; active: boolean )>,
handler: (ctx: IFeatureContext) => Promise<TOutput>
));
feature := client.features.define({
product: string,
tag: string,
name: string,
description?: string,
options?: IFeatureOptions,
signals?: Record<string, { input: Record<string, string> }>,
queries?: Record<string, { handler: (ctx) => unknown }>,
envs?: Array<{ slug: string; active: boolean }>,
handler: (ctx: IFeatureContext) => Promise<TOutput>,
});
var feature = await ductape.features.define({
product: string,
tag: string,
name: string,
description?: string,
options?: IFeatureOptions,
signals?: Record<string, { input: Record<string, string> }>,
queries?: Record<string, { handler: (ctx) => unknown }>,
envs?: Array<{ slug: string; active: boolean }>,
handler: (ctx: IFeatureContext) => Promise<TOutput>,
});
ductape.features.execute()
Execute a feature immediately:
- TypeScript
- Java
- Go
- .NET
const result = await ductape.features.execute({
product: string,
env: string,
tag: string,
input: Record<string, unknown>,
feature_id?: string,
idempotency_key?: string,
timeout?: number,
context?: Record<string, unknown>,
});
Map<String, Object> result = ductape.features.execute(Map.of(
product: string,
env: string,
tag: string,
input: Record<string, unknown>,
feature_id?: string,
idempotency_key?: string,
timeout?: number,
context?: Record<string, unknown>
));
result := client.features.execute({
product: string,
env: string,
tag: string,
input: Record<string, unknown>,
feature_id?: string,
idempotency_key?: string,
timeout?: number,
context?: Record<string, unknown>,
});
var result = await ductape.features.execute({
product: string,
env: string,
tag: string,
input: Record<string, unknown>,
feature_id?: string,
idempotency_key?: string,
timeout?: number,
context?: Record<string, unknown>,
});
ductape.features.dispatch()
Schedule a feature for later execution:
const result = await ductape.features.dispatch({
product: string,
env: string,
feature: string,
input: Record<string, unknown>,
schedule?: {
start_at?: number | string, // When to start
cron?: string, // Cron expression
every?: number, // Interval in ms
timezone?: string, // Timezone
limit?: number, // Max executions
endDate?: string, // End date
},
options?: {
retries?: number,
timeout?: number,
},
});
// Returns
interface IFeatureDispatchResult {
job_id: string;
status: 'scheduled' | 'queued';
scheduled_at: number;
recurring: boolean;
next_run_at?: number;
}
ductape.features.replay()
Re-execute a feature with the same input:
const result = await ductape.features.replay({
product: string,
env: string,
feature_id: string, // Original feature ID
options?: {
retries?: number,
timeout?: number,
debug?: boolean,
},
reason?: string, // Audit reason
idempotency_key?: string,
});
// Returns
interface IFeatureReplayResult extends IFeatureResult {
replayed_from: string; // Original feature ID
}
ductape.features.restart()
Re-execute a feature with new or modified input:
const result = await ductape.features.restart({
product: string,
env: string,
feature_id: string, // Original feature ID
input?: Record<string, unknown>, // New input (replaces original)
input_override?: Record<string, unknown>, // Partial override
merge_input?: boolean, // Merge with original input
reason?: string,
options?: IFeatureOptions,
});
// Returns
interface IFeatureRestartResult extends IFeatureResult {
restarted_from: string; // Original feature ID
}
ductape.features.resume()
Continue a paused or failed feature:
const result = await ductape.features.resume({
product: string,
env: string,
feature_id: string,
from_checkpoint?: string, // Resume from checkpoint
from_step?: string, // Resume from step
skip_steps?: string[], // Steps to skip
input?: Record<string, unknown>, // Additional input
});
// Returns
interface IFeatureResumeResult extends IFeatureResult {
resumed_from: string; // Original feature ID
resumed_checkpoint?: string;
}
ductape.features.replayFromStep()
Re-execute starting from a specific step:
- TypeScript
- Java
- Go
- .NET
const result = await ductape.features.replayFromStep({
product: string,
env: string,
feature_id: string,
from_step: string, // Step to start from
step_outputs?: Record<string, unknown>, // Override previous step outputs
debug?: {
enabled: boolean,
pause_after_step?: boolean,
log_level?: 'info' | 'verbose' | 'debug',
},
});
Map<String, Object> result = ductape.features.replayFromStep(Map.of(
product: string,
env: string,
feature_id: string,
from_step: string, // Step to start from
step_outputs?: Record<string, unknown>, // Override previous step outputs
debug?: Map.of(
enabled: boolean,
pause_after_step?: boolean,
log_level?: 'info' | 'verbose' | 'debug'
)
));
result := client.features.replayFromStep({
product: string,
env: string,
feature_id: string,
from_step: string, // Step to start from
step_outputs?: Record<string, unknown>, // Override previous step outputs
debug?: {
enabled: boolean,
pause_after_step?: boolean,
log_level?: 'info' | 'verbose' | 'debug',
},
});
var result = await ductape.features.replayFromStep({
product: string,
env: string,
feature_id: string,
from_step: string, // Step to start from
step_outputs?: Record<string, unknown>, // Override previous step outputs
debug?: {
enabled: boolean,
pause_after_step?: boolean,
log_level?: 'info' | 'verbose' | 'debug',
},
});
ductape.features.signal()
Send a signal to a running feature:
- TypeScript
- Java
- Go
- .NET
await ductape.features.signal({
product: string,
env: string,
feature_id: string,
signal: string, // Signal name
payload: Record<string, unknown>, // Signal data
});
ductape.features.signal(Map.of(
product: string,
env: string,
feature_id: string,
signal: string, // Signal name
payload: Record<string, unknown>, // Signal data
));
client.features.signal({
product: string,
env: string,
feature_id: string,
signal: string, // Signal name
payload: Record<string, unknown>, // Signal data
});
await ductape.features.signal({
product: string,
env: string,
feature_id: string,
signal: string, // Signal name
payload: Record<string, unknown>, // Signal data
});
ductape.features.query()
Query a running feature's state:
- TypeScript
- Java
- Go
- .NET
const result = await ductape.features.query<T>({
product: string,
env: string,
feature_id: string,
query: string, // Query handler name
});
Map<String, Object> result = ductape.features.query<T>(Map.of(
product: string,
env: string,
feature_id: string,
query: string, // Query handler name
));
result := client.features.query<T>({
product: string,
env: string,
feature_id: string,
query: string, // Query handler name
});
var result = await ductape.features.query<T>({
product: string,
env: string,
feature_id: string,
query: string, // Query handler name
});
ductape.features.status()
Get feature execution status:
const status = await ductape.features.status({
product: string,
env: string,
feature_id: string,
});
// Returns
interface IFeatureStatus {
feature_id: string;
feature_tag: string;
status: 'running' | 'completed' | 'failed' | 'rolled_back' | 'cancelled';
current_step?: string;
completed_steps: string[];
state: Record<string, unknown>;
started_at: number;
updated_at: number;
}
ductape.features.cancel()
Cancel a running feature:
const result = await ductape.features.cancel({
product: string,
env: string,
feature_id: string,
reason?: string,
});
// Returns
interface IFeatureCancelResult {
cancelled: boolean;
rolled_back_steps: string[];
}
ductape.features.history()
Get feature execution history:
const history = await ductape.features.history({
product: string,
env: string,
feature_id: string,
include_step_details?: boolean,
include_rollback_details?: boolean,
});
// Returns
interface IFeatureHistory {
feature_id: string;
feature_tag: string;
status: FeatureStatus;
events: IFeatureEvent[];
checkpoints: ICheckpoint[];
replays?: string[];
restarts?: string[];
}
ductape.features.stepDetail()
Get detailed information about a step:
const detail = await ductape.features.stepDetail({
product: string,
env: string,
feature_id: string,
step_tag: string,
});
// Returns
interface IStepDetail {
tag: string;
name: string;
status: StepStatus;
input?: Record<string, unknown>;
output?: Record<string, unknown>;
error?: IStepError;
attempts: number;
start_time?: number;
end_time?: number;
duration?: number;
rollback_status?: RollbackStatus;
}
ductape.features.relatedExecutions()
List all related executions (replays, restarts, resumes):
const related = await ductape.features.relatedExecutions({
product: string,
env: string,
feature_id: string,
});
// Returns
interface IRelatedExecutions {
original: string;
executions: Array<{
feature_id: string;
type: 'original' | 'replay' | 'restart' | 'resume';
status: FeatureStatus;
created_at: number;
replayed_from?: string;
restarted_from?: string;
resumed_from?: string;
}>;
}
ductape.features.compare()
Compare two feature executions:
const comparison = await ductape.features.compare({
product: string,
env: string,
feature_ids: string[], // Two feature IDs to compare
});
// Returns
interface IExecutionComparison {
features: string[];
input_diff: Record<string, unknown[]>;
step_diffs: Array<{
step: string;
[feature_id: string]: {
status: StepStatus;
output?: Record<string, unknown>;
error?: string;
};
}>;
outcome_diff: Record<string, { status: FeatureStatus; output?: unknown }>;
}