Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion integrations/shopify-admin/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { actions, events, states, configuration, secrets } from './definitions'

export default new IntegrationDefinition({
name: 'shopify-admin',
version: '0.1.2',
version: '0.1.3',
title: 'Shopify Admin',
description:
'Connect your Shopify store via the Admin GraphQL API to manage products, customers, and orders via OAuth 2.0.',
Expand All @@ -14,4 +14,8 @@ export default new IntegrationDefinition({
events,
states,
secrets,
attributes: {
category: 'E-commerce & Payments',
repo: 'botpress',
},
})
6 changes: 5 additions & 1 deletion integrations/shopify-storefront/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { actions, states, configuration, secrets } from './definitions'

export default new IntegrationDefinition({
name: 'shopify-storefront',
version: '0.1.2',
version: '0.1.3',
title: 'Shopify Storefront',
description:
'Connect your Shopify store via the Storefront API to power buyer-facing product browsing, collections, and cart/checkout flows via OAuth 2.0.',
Expand All @@ -13,4 +13,8 @@ export default new IntegrationDefinition({
actions,
states,
secrets,
attributes: {
category: 'E-commerce & Payments',
repo: 'botpress',
},
})
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/cli",
"version": "6.8.6",
"version": "6.8.7",
"description": "Botpress CLI",
"scripts": {
"build": "pnpm run build:types && pnpm run bundle && pnpm run template:gen",
Expand Down Expand Up @@ -28,7 +28,7 @@
"@apidevtools/json-schema-ref-parser": "^11.7.0",
"@botpress/chat": "0.5.5",
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.2",
"@botpress/sdk": "6.12.0",
"@bpinternal/const": "^0.1.0",
"@bpinternal/tunnel": "^0.1.1",
"@bpinternal/verel": "^0.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.2"
"@botpress/sdk": "6.12.0"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.2"
"@botpress/sdk": "6.12.0"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"private": true,
"dependencies": {
"@botpress/sdk": "6.11.2"
"@botpress/sdk": "6.12.0"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/hello-world/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.2"
"@botpress/sdk": "6.12.0"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/webhook-message/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.46.0",
"@botpress/sdk": "6.11.2",
"@botpress/sdk": "6.12.0",
"axios": "^1.6.8"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/sdk",
"version": "6.11.2",
"version": "6.12.0",
"description": "Botpress SDK",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
Expand Down
94 changes: 94 additions & 0 deletions packages/sdk/src/bot/definition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,97 @@ test('addPlugin falls back to raw config when schema validation fails', () => {
// Falls back to raw config; default for sneaky is not applied since safeParse failed
expect(storedConfig).toEqual({ preciousEmail: '$GOLLUM_EMAIL' })
})

// BotDefinition constructor — recurrence field handling

test('BotDefinition strips recurrence from events', () => {
const bot = new BotDefinition({
events: {
heartbeat: {
schema: z.object({}),
recurrence: { cron: '*/5 * * * *', payload: {} },
},
},
})

expect(bot.events?.heartbeat).not.toHaveProperty('recurrence')
})

test('BotDefinition converts inline recurrence to a recurringEvents entry', () => {
const bot = new BotDefinition({
events: {
heartbeat: {
schema: z.object({}),
recurrence: { cron: '*/5 * * * *', payload: {} },
},
},
})

expect(bot.recurringEvents?.heartbeat).toEqual({
type: 'heartbeat',
schedule: { cron: '*/5 * * * *' },
payload: {},
})
})

test('BotDefinition recurringEvents is undefined when no recurring events are defined', () => {
const bot = new BotDefinition({
events: {
plain: { schema: z.object({}) },
},
})

expect(bot.recurringEvents).toBeUndefined()
})

test('BotDefinition: explicit recurringEvents overrides inline recurrence for the same key', () => {
const bot = new BotDefinition({
events: {
heartbeat: {
schema: z.object({}),
recurrence: { cron: '*/5 * * * *', payload: { foo: 'foo' } },
},
},
recurringEvents: {
heartbeat: { type: 'heartbeat', schedule: { cron: '0 * * * *' }, payload: { bar: 'bar' } },
},
})

expect(bot.recurringEvents?.heartbeat).toEqual({
type: 'heartbeat',
schedule: { cron: '0 * * * *' },
payload: { bar: 'bar' },
})
})

test('BotDefinition: two recurringEvents entries with different keys but same type both survive', () => {
const bot = new BotDefinition({
events: {
foo: { schema: z.object({}) },
},
recurringEvents: {
fooEvery6: { type: 'foo', schedule: { cron: '*/6 * * * *' }, payload: {} },
fooEvery7: { type: 'foo', schedule: { cron: '*/7 * * * *' }, payload: {} },
},
})

expect(bot.recurringEvents?.fooEvery6).toEqual({ type: 'foo', schedule: { cron: '*/6 * * * *' }, payload: {} })
expect(bot.recurringEvents?.fooEvery7).toEqual({ type: 'foo', schedule: { cron: '*/7 * * * *' }, payload: {} })
})

test('BotDefinition: explicit recurringEvents with no inline counterpart is preserved', () => {
const bot = new BotDefinition({
events: {
heartbeat: { schema: z.object({}) },
},
recurringEvents: {
dailyDigest: { type: 'heartbeat', schedule: { cron: '0 9 * * *' }, payload: {} },
},
})

expect(bot.recurringEvents?.dailyDigest).toEqual({
type: 'heartbeat',
schedule: { cron: '0 9 * * *' },
payload: {},
})
})
25 changes: 21 additions & 4 deletions packages/sdk/src/bot/definition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Table } from '@botpress/client'
import { stripRecurringFromEvents, resolveRecurringEvents } from '../common/recurring-events'
import { SchemaTransformOptions } from '../common/types'
import * as consts from '../consts'
import { DefinitionError } from '../errors'
Expand Down Expand Up @@ -29,6 +30,9 @@ export type StateDefinition<TState extends BaseStates[string] = BaseStates[strin
expiry?: number
}

/*
* @deprecate
*/
export type RecurringEventDefinition<TEvents extends BaseEvents = BaseEvents> = {
[K in keyof TEvents]: {
type: K
Expand All @@ -38,6 +42,10 @@ export type RecurringEventDefinition<TEvents extends BaseEvents = BaseEvents> =
}[keyof TEvents]

export type EventDefinition<TEvent extends BaseEvents[string] = BaseEvents[string]> = SchemaDefinition<TEvent> & {
recurrence?: {
cron: string
payload: z.input<TEvent>
}
attributes?: Record<string, string>
}

Expand Down Expand Up @@ -178,6 +186,7 @@ export type BotDefinitionProps<
events?: {
[K in keyof TEvents]: EventDefinition<TEvents[K]>
}
/** @deprecated Use the `recurrence` field on each event in `events` instead. */
recurringEvents?: Record<string, RecurringEventDefinition<TEvents>>
actions?: {
[K in keyof TActions]: ActionDefinition<TActions[K]>
Expand Down Expand Up @@ -231,15 +240,23 @@ export class BotDefinition<
>

public constructor(public readonly props: BotDefinitionProps<TStates, TEvents, TActions, TTables, TWorkflows>) {
const events = stripRecurringFromEvents(
props.events as Record<string, EventDefinition> | undefined
) as this['props']['events']
const recurringEvents = resolveRecurringEvents(
props.events as Record<string, EventDefinition>,
props.recurringEvents as BotDefinitionProps['recurringEvents']
)

this.integrations = props.integrations
this.plugins = props.plugins
this.user = props.user
this.conversation = props.conversation
this.message = props.message
this.states = props.states
this.configuration = props.configuration
this.events = props.events
this.recurringEvents = props.recurringEvents
this.events = events
this.recurringEvents = recurringEvents
this.actions = props.actions
this.tables = props.tables
this.secrets = props.secrets
Expand All @@ -252,8 +269,8 @@ export class BotDefinition<
conversation: props.conversation,
message: props.message,
states: props.states,
events: props.events,
recurringEvents: props.recurringEvents,
events,
recurringEvents,
actions: props.actions,
tables: props.tables,
workflows: props.workflows,
Expand Down
101 changes: 101 additions & 0 deletions packages/sdk/src/common/recurring-events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { test, expect } from 'vitest'
import { stripRecurringFromEvents, resolveRecurringEvents } from './recurring-events'
import { z } from '../zui'

// stripRecurringFromEvents

test('stripRecurringFromEvents returns undefined when events is undefined', () => {
expect(stripRecurringFromEvents(undefined)).toBeUndefined()
})

test('stripRecurringFromEvents removes the recurrence field from events', () => {
const result = stripRecurringFromEvents({
heartbeat: { schema: z.object({}), recurrence: { cron: '*/5 * * * *', payload: {} } },
})
expect(result?.heartbeat).not.toHaveProperty('recurrence')
})

test('stripRecurringFromEvents preserves other event fields', () => {
const result = stripRecurringFromEvents({
heartbeat: {
schema: z.object({}),
attributes: { foo: 'bar' },
recurrence: { cron: '*/5 * * * *', payload: {} },
},
})
expect(result?.heartbeat).toHaveProperty('attributes', { foo: 'bar' })
expect(result?.heartbeat).toHaveProperty('schema')
})

test('stripRecurringFromEvents handles events without a recurrence field', () => {
const input = { plain: { schema: z.object({}) } }
const result = stripRecurringFromEvents(input)
expect(result?.plain).not.toHaveProperty('recurrence')
expect(result?.plain?.schema).toBe(input.plain.schema)
})

// resolveRecurringEvents

test('resolveRecurringEvents returns undefined when there are no events and no explicit recurringEvents', () => {
expect(resolveRecurringEvents(undefined, undefined)).toBeUndefined()
})

test('resolveRecurringEvents returns undefined when events have no recurrence field and no explicit recurringEvents', () => {
expect(resolveRecurringEvents({ plain: { schema: z.object({}) } }, undefined)).toBeUndefined()
})

test('resolveRecurringEvents derives a recurringEvents entry from an inline recurrence field', () => {
const result = resolveRecurringEvents(
{ heartbeat: { schema: z.object({}), recurrence: { cron: '*/5 * * * *', payload: {} } } },
undefined
)
expect(result).toEqual({
heartbeat: { type: 'heartbeat', schedule: { cron: '*/5 * * * *' }, payload: {} },
})
})

test('resolveRecurringEvents only derives entries for events that have a recurrence field', () => {
const result = resolveRecurringEvents(
{
heartbeat: { schema: z.object({}), recurrence: { cron: '*/5 * * * *', payload: {} } },
plain: { schema: z.object({}) },
},
undefined
)
expect(Object.keys(result ?? {})).toEqual(['heartbeat'])
})

test('resolveRecurringEvents preserves explicit recurringEvents when there are no inline recurrence events', () => {
const result = resolveRecurringEvents(
{ plain: { schema: z.object({}) } },
{ dailyDigest: { type: 'plain', schedule: { cron: '0 9 * * *' }, payload: {} } }
)
expect(result).toEqual({
dailyDigest: { type: 'plain', schedule: { cron: '0 9 * * *' }, payload: {} },
})
})

test('resolveRecurringEvents: explicit recurringEvents overrides inline recurrence for the same key', () => {
const result = resolveRecurringEvents(
{
heartbeat: {
schema: z.object({}),
recurrence: { cron: '*/5 * * * *', payload: { foo: 'foo' } },
},
},
{ heartbeat: { type: 'heartbeat', schedule: { cron: '0 * * * *' }, payload: { bar: 'bar' } } }
)
expect(result?.heartbeat).toEqual({
type: 'heartbeat',
schedule: { cron: '0 * * * *' },
payload: { bar: 'bar' },
})
})

test('resolveRecurringEvents merges derived and explicit entries with different keys', () => {
const result = resolveRecurringEvents(
{ heartbeat: { schema: z.object({}), recurrence: { cron: '*/5 * * * *', payload: {} } } },
{ dailyDigest: { type: 'heartbeat', schedule: { cron: '0 9 * * *' }, payload: {} } }
)
expect(Object.keys(result ?? {}).sort()).toEqual(['dailyDigest', 'heartbeat'])
})
Loading
Loading