Custom runtimes, Motia global configuration, Workbench Plugins
This reason this RFC talks about these three topic is because they're related.
Motia Global Configuration
We propose to add a global configuration file for Motia, similarly to what Vite does, it's flexible enough to be able to add custom logic like Plugins and Runtimes.
import { config } from 'motia'
// Example of Motia official runtimes
import { JavascriptRuntime } from '@motiadev/runtime-javascript'
import { TypeScriptRuntime } from '@motiadev/runtime-typescript'
import { RustRuntime } from '@motiadev/runtime-rust'
// Example of Community runtime
import { JavaRuntime } from '@community/runtime-java'
// Example of a Motia official plugin that doesn't come out of the box
import { mermaidPlugin } from '@motiadev/plugin-mermaid'
import { openApi } from '@motiadev/plugin-open-api'
// Example of Community plugin
import { llmChatPlugin } from '@community/motia-plugin-llm-chat'
export default config({
runtimes: [
{
steps: 'src/**/*.step.ts',
streams: 'src/**/*.stream.ts',
runtime: new TypeScriptRuntime(),
},
{
steps: 'src/**/*.step.js',
streams: 'src/**/*.stream.js',
runtime: new JavascriptRuntime(),
},
{
steps: 'src/**/*.step.rs',
streams: 'src/**/*.stream.rs',
runtime: new RustRuntime(),
},
{
steps: 'src/**/*Step.java',
streams: 'src/**/*Stream.java',
runtime: new JavaRuntime(),
},
],
plugins: [
// adding mermaid plugin
mermaidPlugin(),
openApi({ name: 'My API', description: 'My API description' }),
llmChatPlugin({ openAiApiKey: process.env.OPENAI_API_KEY }),
],
api: {
cors: {
allowedOrigins: ['*'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'],
headers: ['*'],
credentials: true,
maxAge: 600,
},
},
})
In this example above:
- We're configuring three runtimes, one for each language: TypeScript, JavaScript and Rust
- The
filesoption is a glob pattern that matches the step files for each runtime - The
runtimeoption is a function that returns a new instance of the runtime
- The
- We're configuring three plugins:
- The
mermaidPluginplugin is a plugin that adds mermaid support to the Framework, which means on code changes it will automatically generate a mermaid diagram of the flow, a background plugin that doesn't show up in the workbench. - The
openApiplugin is a plugin that adds OpenAPI support to the Workbench - The
llmChatPluginplugin is a plugin is an example of a community plugin, in concept: It adds OpenAI support to the Workbench, with a new Tab allowing the user to chat with the LLM about the project.
- The
- We should be able to configure the API cors options
Motia Plugins
First thing I want to propose, is to rename LockedData to Motia, which is the representation of the state of the project.
- List of all: Flows, Steps, Streams
- Add watchers to new Steps that are created, deleted or updated
Returns a list of all the components to add to the Workbench. It's possible to override the defaults of each plugin, like: Label, Placement, and adding props.
Creating a new plugin
class CustomStream extends StreamAdapter<any> {
constructor(private readonly motia: Motia) {
super()
}
get(id: string): Promise<any> {
return Promise.resolve(null)
}
set(id: string, data: any): Promise<any> {
return Promise.resolve(data)
}
delete(id: string): Promise<any> {
return Promise.resolve(null)
}
getGroup(): Promise<any[]> {
return Promise.resolve([])
}
}
export const llmChatPlugin = (args: { openAiApiKey?: string }): MotiaPluginBuilder => {
return (motia: Motia) => {
const stream = motia.createStream({
name: '__motia.custom',
factory: () => new CustomStream(motia),
})
motia.on('flow-created', (flowName) => {
stream.send({ groupId: 'default', id: flowName }, { type: 'flow-created', data: flowName })
})
return {
/**
* The Dirname of the plugin, it's important for Motia Framework to know
* where to find the steps and the workbench components.
*/
dirname: __dirname,
/**
* The steps to load for the plugin.
*/
steps: ['steps/*.step.ts'],
/**
* The workbench components to load for the plugin.
*/
workbench: {
{
packageName: '@community/motia-plugin-llm-chat',
// optional fields
/**
* will ultimately convert to import { LLMChat } from '@community/motia-plugin-llm-chat'
*/
componentName: 'LLMChat',
/**
* The label to display in the workbench
*/
label: 'Chat with AI',
/**
* The placement of the component in the workbench
*/
placement: 'bottom',
/**
* The props to pass to the component
*/
props: {
// example of a prop
disableOpenAi: true,
}
},
},
}
}
}
Motia Interface
The Motia interface is the main interface that will be used to interact with the framework. Features that should be supported day one:
- Add watchers to see new steps, streams, flows being created, updated or deleted
- Create Streams
- Create Steps
- Add custom middlewares to the step execution
Motia Runtime
The proposal is to move runtimes to be under a Runtime interface. Motia will orchestrate the runtimes and plugins in one single place. The framework will handle all the triggers depending on the configuration of each runtime.
export interface ReadConfigOptions {
/**
* The absolute path to the project root
*/
projectRoot: string
/**
* The absolute path to the step file
*/
stepFilePath: string
}
export interface BuildResult {
/**
* The compressed size of the build in bytes
*/
compressedSize: number
/**
* The uncompressed size of the build in bytes
*/
uncompressedSize: number
/**
* The absolute path to the build in the file system
*/
path: string
}
export interface ConfigResult {
/**
* Is it a step? Then should return a step config
*/
stepConfig?: StepConfig
/**
* Is it a stream? Then should return a stream config
*/
streamConfig?: StreamConfig
/**
* List of all the files that were read to build the config
* Important to understand whenever the files are changed to
* rebuild the config and ask for a new compile.
*/
paths: string[] // relative paths to the project root
}
export interface Runtime {
name: string
/**
* Called whenever the Runtime understands that the step has changed.
* @param step
* @returns
*/
compile: (step: Step) => Promise<void>
/**
* Reads the config for a step.
* @param options
* @returns
*/
readConfig: (options: ReadConfigOptions) => Promise<ConfigResult | null>
/**
* Calls a step file.
* @param options
* @returns
*/
callStepFile: (step: Step, args?: unknown) => Promise<unknown>
/**
* Builds a step.
* @param step
* @param destPath
* @returns
*/
build(step: Step, destPath: string): Promise<BuildResult>
/**
* Builds a consolidated router for a list of api steps.
* @param steps
* @param destPath
* @returns
*/
buildApiSteps(steps: Step<ApiRouteConfig>[], destPath: string): Promise<BuildResult>
}