motia 0.5.12-beta.121 → 0.6.0-beta.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/cjs/cli.js +13 -0
  2. package/dist/cjs/create/index.d.ts +1 -0
  3. package/dist/cjs/create/index.js +12 -38
  4. package/dist/cjs/create/interactive.js +1 -1
  5. package/dist/cjs/create/setup-template.d.ts +2 -0
  6. package/dist/cjs/create/setup-template.js +13 -0
  7. package/dist/cjs/create/templates/generate.d.ts +1 -1
  8. package/dist/cjs/create/templates/generate.js +28 -5
  9. package/dist/cjs/create/templates/generate.ts +33 -6
  10. package/dist/cjs/create/templates/index.js +1 -1
  11. package/dist/cjs/create/templates/index.ts +1 -1
  12. package/dist/cjs/create/templates/python/motia-workbench.json +14 -16
  13. package/dist/cjs/create/templates/python/requirements.txt +2 -0
  14. package/dist/cjs/create/templates/python/steps/api_step.py-features.json.txt +69 -0
  15. package/dist/cjs/create/templates/python/steps/api_step.py.txt +52 -0
  16. package/dist/cjs/create/templates/python/steps/notification_step.py.txt +40 -0
  17. package/dist/cjs/create/templates/python/steps/process_food_order_step.py-features.json.txt +68 -0
  18. package/dist/cjs/create/templates/python/steps/process_food_order_step.py.txt +50 -0
  19. package/dist/cjs/create/templates/python/steps/services/__init__.py.txt +0 -0
  20. package/dist/cjs/create/templates/python/steps/services/pet_store.py.txt +37 -0
  21. package/dist/cjs/create/templates/python/steps/services/types.py.txt +19 -0
  22. package/dist/cjs/create/templates/python/steps/state_audit_cron_step.py-features.json.txt +26 -0
  23. package/dist/cjs/create/templates/python/steps/state_audit_cron_step.py.txt +39 -0
  24. package/dist/cjs/create/templates/python/tutorial.tsx.txt +647 -0
  25. package/dist/cjs/create/templates/typescript/motia-workbench.json +28 -0
  26. package/dist/cjs/create/templates/typescript/services/pet-store.ts.txt +29 -0
  27. package/dist/cjs/create/templates/typescript/services/types.ts.txt +21 -0
  28. package/dist/cjs/create/templates/typescript/steps/api.step.ts-features.json.txt +67 -0
  29. package/dist/cjs/create/templates/typescript/steps/api.step.ts.txt +50 -0
  30. package/dist/cjs/create/templates/typescript/steps/notification.step.ts.txt +35 -0
  31. package/dist/cjs/create/templates/typescript/steps/process-food-order.step.ts-features.json.txt +67 -0
  32. package/dist/cjs/create/templates/typescript/steps/process-food-order.step.ts.txt +44 -0
  33. package/dist/cjs/create/templates/typescript/steps/state-audit-cron.step.ts-features.json.txt +26 -0
  34. package/dist/cjs/create/templates/typescript/steps/state-audit-cron.step.ts.txt +51 -0
  35. package/dist/cjs/create/templates/typescript/tutorial.tsx.txt +647 -0
  36. package/dist/cjs/create/utils.d.ts +2 -0
  37. package/dist/cjs/create/utils.js +21 -0
  38. package/dist/cjs/docker/utils/print-intro.js +0 -1
  39. package/dist/esm/cli.js +10 -0
  40. package/dist/esm/create/index.d.ts +1 -0
  41. package/dist/esm/create/index.js +4 -30
  42. package/dist/esm/create/interactive.js +1 -1
  43. package/dist/esm/create/setup-template.d.ts +2 -0
  44. package/dist/esm/create/setup-template.js +9 -0
  45. package/dist/esm/create/templates/generate.d.ts +1 -1
  46. package/dist/esm/create/templates/generate.js +29 -6
  47. package/dist/esm/create/templates/generate.ts +33 -6
  48. package/dist/esm/create/templates/index.js +1 -1
  49. package/dist/esm/create/templates/index.ts +1 -1
  50. package/dist/esm/create/templates/python/motia-workbench.json +14 -16
  51. package/dist/esm/create/templates/python/requirements.txt +2 -0
  52. package/dist/esm/create/templates/python/steps/api_step.py-features.json.txt +69 -0
  53. package/dist/esm/create/templates/python/steps/api_step.py.txt +52 -0
  54. package/dist/esm/create/templates/python/steps/notification_step.py.txt +40 -0
  55. package/dist/esm/create/templates/python/steps/process_food_order_step.py-features.json.txt +68 -0
  56. package/dist/esm/create/templates/python/steps/process_food_order_step.py.txt +50 -0
  57. package/dist/esm/create/templates/python/steps/services/__init__.py.txt +0 -0
  58. package/dist/esm/create/templates/python/steps/services/pet_store.py.txt +37 -0
  59. package/dist/esm/create/templates/python/steps/services/types.py.txt +19 -0
  60. package/dist/esm/create/templates/python/steps/state_audit_cron_step.py-features.json.txt +26 -0
  61. package/dist/esm/create/templates/python/steps/state_audit_cron_step.py.txt +39 -0
  62. package/dist/esm/create/templates/python/tutorial.tsx.txt +647 -0
  63. package/dist/esm/create/templates/typescript/motia-workbench.json +28 -0
  64. package/dist/esm/create/templates/typescript/services/pet-store.ts.txt +29 -0
  65. package/dist/esm/create/templates/typescript/services/types.ts.txt +21 -0
  66. package/dist/esm/create/templates/typescript/steps/api.step.ts-features.json.txt +67 -0
  67. package/dist/esm/create/templates/typescript/steps/api.step.ts.txt +50 -0
  68. package/dist/esm/create/templates/typescript/steps/notification.step.ts.txt +35 -0
  69. package/dist/esm/create/templates/typescript/steps/process-food-order.step.ts-features.json.txt +67 -0
  70. package/dist/esm/create/templates/typescript/steps/process-food-order.step.ts.txt +44 -0
  71. package/dist/esm/create/templates/typescript/steps/state-audit-cron.step.ts-features.json.txt +26 -0
  72. package/dist/esm/create/templates/typescript/steps/state-audit-cron.step.ts.txt +51 -0
  73. package/dist/esm/create/templates/typescript/tutorial.tsx.txt +647 -0
  74. package/dist/esm/create/utils.d.ts +2 -0
  75. package/dist/esm/create/utils.js +13 -0
  76. package/dist/esm/docker/utils/print-intro.js +0 -1
  77. package/dist/types/create/index.d.ts +1 -0
  78. package/dist/types/create/setup-template.d.ts +2 -0
  79. package/dist/types/create/templates/generate.d.ts +1 -1
  80. package/dist/types/create/utils.d.ts +2 -0
  81. package/package.json +4 -4
  82. package/dist/cjs/create/templates/default/motia-workbench.json +0 -29
  83. package/dist/cjs/create/templates/default/steps/00-noop.step.ts.txt +0 -33
  84. package/dist/cjs/create/templates/default/steps/00-noop.step.tsx.txt +0 -18
  85. package/dist/cjs/create/templates/default/steps/01-api.step.ts.txt +0 -70
  86. package/dist/cjs/create/templates/default/steps/02-test-state.step.ts.txt +0 -53
  87. package/dist/cjs/create/templates/default/steps/03-check-state-change.step.ts.txt +0 -54
  88. package/dist/cjs/create/templates/python/steps/00_noop_step.py.txt +0 -24
  89. package/dist/cjs/create/templates/python/steps/00_noop_step.tsx.txt +0 -18
  90. package/dist/cjs/create/templates/python/steps/01_api_step.py.txt +0 -53
  91. package/dist/cjs/create/templates/python/steps/02_test_state_step.py.txt +0 -38
  92. package/dist/cjs/create/templates/python/steps/03_check_state_change_step.py.txt +0 -43
  93. package/dist/esm/create/templates/default/motia-workbench.json +0 -29
  94. package/dist/esm/create/templates/default/steps/00-noop.step.ts.txt +0 -33
  95. package/dist/esm/create/templates/default/steps/00-noop.step.tsx.txt +0 -18
  96. package/dist/esm/create/templates/default/steps/01-api.step.ts.txt +0 -70
  97. package/dist/esm/create/templates/default/steps/02-test-state.step.ts.txt +0 -53
  98. package/dist/esm/create/templates/default/steps/03-check-state-change.step.ts.txt +0 -54
  99. package/dist/esm/create/templates/python/steps/00_noop_step.py.txt +0 -24
  100. package/dist/esm/create/templates/python/steps/00_noop_step.tsx.txt +0 -18
  101. package/dist/esm/create/templates/python/steps/01_api_step.py.txt +0 -53
  102. package/dist/esm/create/templates/python/steps/02_test_state_step.py.txt +0 -38
  103. package/dist/esm/create/templates/python/steps/03_check_state_change_step.py.txt +0 -43
package/dist/cjs/cli.js CHANGED
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  /* eslint-disable @typescript-eslint/no-require-imports */
5
8
  const commander_1 = require("commander");
6
9
  require("./cloud");
7
10
  const version_1 = require("./version");
8
11
  const config_utils_1 = require("./cloud/config-utils");
12
+ const inquirer_1 = __importDefault(require("inquirer"));
9
13
  const defaultPort = 3000;
10
14
  const defaultHost = '0.0.0.0';
11
15
  require('dotenv/config');
@@ -28,14 +32,23 @@ commander_1.program
28
32
  .option('-c, --cursor', 'Copy .cursor folder from template')
29
33
  .option('-i, --interactive', 'Use interactive prompts to create project')
30
34
  .option('-y, --skip-confirmation', 'Skip confirmation prompt')
35
+ .option('-d, --skip-tutorial', 'Skip the motia tutorial', false)
31
36
  .action((0, config_utils_1.handler)(async (arg, context) => {
32
37
  if (arg.name || arg.template || arg.cursor) {
33
38
  const { create } = require('./create');
39
+ const disableTutorial = await inquirer_1.default.prompt({
40
+ type: 'confirm',
41
+ name: 'disableTutorial',
42
+ message: 'Do you wish to disable the motia tutorial?',
43
+ default: arg.skipTutorial,
44
+ when: () => arg.skipTutorial === false,
45
+ });
34
46
  await create({
35
47
  projectName: arg.name ?? '.',
36
48
  template: arg.template ?? 'default',
37
49
  cursorEnabled: arg.cursor,
38
50
  context,
51
+ skipTutorialTemplates: disableTutorial.disableTutorial,
39
52
  });
40
53
  }
41
54
  else {
@@ -4,6 +4,7 @@ type Args = {
4
4
  template?: string;
5
5
  cursorEnabled?: boolean;
6
6
  context: CliContext;
7
+ skipTutorialTemplates?: boolean;
7
8
  };
8
9
  export declare const create: ({ projectName, template, cursorEnabled, context }: Args) => Promise<void>;
9
10
  export {};
@@ -6,35 +6,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.create = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
- const templates_1 = require("./templates");
10
9
  const execute_command_1 = require("../utils/execute-command");
11
10
  const install_1 = require("../install");
12
11
  const generate_types_1 = require("../generate-types");
13
12
  const version_1 = require("../version");
13
+ const setup_template_1 = require("./setup-template");
14
+ const utils_1 = require("./utils");
14
15
  // eslint-disable-next-line @typescript-eslint/no-require-imports
15
16
  require('ts-node').register({
16
17
  transpileOnly: true,
17
18
  compilerOptions: { module: 'commonjs' },
18
19
  });
19
- const checkIfFileExists = (dir, fileName) => {
20
- return fs_1.default.existsSync(path_1.default.join(dir, fileName));
21
- };
22
- const checkIfDirectoryExists = (dir) => {
23
- try {
24
- return fs_1.default.statSync(dir).isDirectory();
25
- }
26
- catch {
27
- return false;
28
- }
29
- };
30
20
  const getPackageManager = (dir) => {
31
- if (checkIfFileExists(dir, 'yarn.lock')) {
21
+ if ((0, utils_1.checkIfFileExists)(dir, 'yarn.lock')) {
32
22
  return 'yarn';
33
23
  }
34
- else if (checkIfFileExists(dir, 'pnpm-lock.yaml')) {
24
+ else if ((0, utils_1.checkIfFileExists)(dir, 'pnpm-lock.yaml')) {
35
25
  return 'pnpm';
36
26
  }
37
- else if (checkIfFileExists(dir, 'package-lock.json')) {
27
+ else if ((0, utils_1.checkIfFileExists)(dir, 'package-lock.json')) {
38
28
  return 'npm';
39
29
  }
40
30
  else {
@@ -97,14 +87,14 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
97
87
  '\n\n');
98
88
  const isCurrentDir = projectName === '.' || projectName === './' || projectName === '.\\';
99
89
  const rootDir = isCurrentDir ? process.cwd() : path_1.default.join(process.cwd(), projectName);
100
- if (!isCurrentDir && !checkIfDirectoryExists(rootDir)) {
90
+ if (!isCurrentDir && !(0, utils_1.checkIfDirectoryExists)(rootDir)) {
101
91
  fs_1.default.mkdirSync(path_1.default.join(rootDir));
102
92
  context.log('directory-created', (message) => message.tag('success').append('Directory created ').append(projectName, 'gray'));
103
93
  }
104
94
  else {
105
95
  context.log('directory-using', (message) => message.tag('info').append('Using current directory'));
106
96
  }
107
- if (!checkIfFileExists(rootDir, 'package.json')) {
97
+ if (!(0, utils_1.checkIfFileExists)(rootDir, 'package.json')) {
108
98
  const packageJsonContent = {
109
99
  name: projectName,
110
100
  description: '',
@@ -144,7 +134,7 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
144
134
  .append('command to')
145
135
  .append('package.json', 'gray'));
146
136
  }
147
- if (!checkIfFileExists(rootDir, 'tsconfig.json')) {
137
+ if (!(0, utils_1.checkIfFileExists)(rootDir, 'tsconfig.json')) {
148
138
  const tsconfigContent = {
149
139
  compilerOptions: {
150
140
  target: 'ES2020',
@@ -167,7 +157,7 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
167
157
  fs_1.default.writeFileSync(path_1.default.join(rootDir, 'tsconfig.json'), JSON.stringify(tsconfigContent, null, 2));
168
158
  context.log('tsconfig-json-created', (message) => message.tag('success').append('File').append('tsconfig.json', 'cyan').append('has been created.'));
169
159
  }
170
- if (!checkIfFileExists(rootDir, '.gitignore')) {
160
+ if (!(0, utils_1.checkIfFileExists)(rootDir, '.gitignore')) {
171
161
  const gitignoreContent = [
172
162
  'node_modules',
173
163
  'python_modules',
@@ -183,31 +173,15 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
183
173
  }
184
174
  const cursorTemplateDir = path_1.default.join(__dirname, '../../dot-files/.cursor');
185
175
  const cursorTargetDir = path_1.default.join(rootDir, '.cursor');
186
- if (cursorEnabled && !checkIfDirectoryExists(cursorTargetDir)) {
176
+ if (cursorEnabled && !(0, utils_1.checkIfDirectoryExists)(cursorTargetDir)) {
187
177
  fs_1.default.cpSync(cursorTemplateDir, cursorTargetDir, { recursive: true });
188
178
  context.log('cursor-folder-created', (message) => message.tag('success').append('Folder').append('.cursor', 'cyan').append('has been created.'));
189
179
  }
190
- const stepsDir = path_1.default.join(rootDir, 'steps');
191
- if (!checkIfDirectoryExists(stepsDir)) {
192
- fs_1.default.mkdirSync(stepsDir);
193
- context.log('steps-directory-created', (message) => message.tag('success').append('Folder').append('steps', 'cyan').append('has been created.'));
194
- }
195
- if (!template || !(template in templates_1.templates)) {
196
- context.log('template-not-found', (message) => message.tag('failed').append(`Template ${template} not found, please use one of the following:`));
197
- context.log('available-templates', (message) => message.tag('info').append(`Available templates: \n\n ${Object.keys(templates_1.templates).join('\n')}`));
198
- return;
180
+ if (template) {
181
+ await (0, setup_template_1.setupTemplate)(template, rootDir, context);
199
182
  }
200
- await templates_1.templates[template](rootDir, context);
201
183
  const packageManager = await installNodeDependencies(rootDir, context);
202
184
  if (template === 'python') {
203
- if (!checkIfFileExists(rootDir, 'requirements.txt')) {
204
- const requirementsContent = [
205
- // TODO: motia PyPi package
206
- // Add other Python dependencies as needed
207
- ].join('\n');
208
- fs_1.default.writeFileSync(path_1.default.join(rootDir, 'requirements.txt'), requirementsContent);
209
- context.log('requirements-txt-created', (message) => message.tag('success').append('File').append('requirements.txt', 'gray').append('has been created.'));
210
- }
211
185
  await (0, install_1.pythonInstall)({ baseDir: rootDir });
212
186
  }
213
187
  await (0, generate_types_1.generateTypes)(rootDir);
@@ -8,7 +8,7 @@ const inquirer_1 = __importDefault(require("inquirer"));
8
8
  const colors_1 = __importDefault(require("colors"));
9
9
  const index_1 = require("./index");
10
10
  const choices = {
11
- default: 'Base (TypeScript)',
11
+ typescript: 'Base (TypeScript)',
12
12
  python: 'Base (Python)',
13
13
  };
14
14
  const createInteractive = async (_args, context) => {
@@ -0,0 +1,2 @@
1
+ import { CliContext } from '@/cloud/config-utils';
2
+ export declare const setupTemplate: (template: string, rootDir: string, context: CliContext) => Promise<void>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupTemplate = void 0;
4
+ const templates_1 = require("./templates");
5
+ const setupTemplate = async (template, rootDir, context) => {
6
+ if (!template || !(template in templates_1.templates)) {
7
+ context.log('template-not-found', (message) => message.tag('failed').append(`Template ${template} not found, please use one of the following:`));
8
+ context.log('available-templates', (message) => message.tag('info').append(`Available templates: \n\n ${Object.keys(templates_1.templates).join('\n')}`));
9
+ return;
10
+ }
11
+ await templates_1.templates[template](rootDir, context);
12
+ };
13
+ exports.setupTemplate = setupTemplate;
@@ -1,3 +1,3 @@
1
1
  import { CliContext } from '../../cloud/config-utils';
2
2
  export type Generator = (rootDir: string, context: CliContext) => Promise<void>;
3
- export declare const generateTemplateSteps: (templateDir: string) => Generator;
3
+ export declare const generateTemplateSteps: (templateFolder: string) => Generator;
@@ -37,20 +37,43 @@ exports.generateTemplateSteps = void 0;
37
37
  const fs_1 = require("fs");
38
38
  const path = __importStar(require("path"));
39
39
  const glob_1 = require("glob");
40
- const generateTemplateSteps = (templateDir) => {
40
+ const generateTemplateSteps = (templateFolder) => {
41
41
  return async (rootDir, context) => {
42
- const templatePath = path.join(__dirname, templateDir);
42
+ const templatePath = path.join(__dirname, templateFolder);
43
43
  const files = (0, glob_1.globSync)('**/*', { absolute: false, cwd: templatePath });
44
44
  try {
45
45
  for (const fileName of files) {
46
46
  const filePath = path.join(templatePath, fileName);
47
+ const targetFilePath = path.join(rootDir, fileName);
48
+ const targetDir = path.dirname(targetFilePath);
49
+ try {
50
+ // Check if it's a directory in the template
51
+ (0, fs_1.statSync)(targetDir);
52
+ }
53
+ catch {
54
+ (0, fs_1.mkdirSync)(targetDir, { recursive: true });
55
+ }
47
56
  if ((0, fs_1.statSync)(filePath).isDirectory()) {
48
- // ignore folders
57
+ const folderPath = path.basename(filePath);
58
+ (0, fs_1.mkdirSync)(path.join(rootDir, folderPath), { recursive: true });
49
59
  continue;
50
60
  }
51
- const sanitizedFileName = fileName.replace('.txt', '');
61
+ const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '');
62
+ const isWorkbenchConfig = fileName.match('motia-workbench.json');
52
63
  const generateFilePath = path.join(rootDir, sanitizedFileName);
53
- const content = await fs_1.promises.readFile(filePath, 'utf8');
64
+ let content = await fs_1.promises.readFile(filePath, 'utf8');
65
+ // Make sure statSync doesn't break the execution if the file doesn't exist
66
+ try {
67
+ if (isWorkbenchConfig && (0, fs_1.statSync)(generateFilePath).isFile()) {
68
+ const existingWorkbenchConfig = await fs_1.promises.readFile(generateFilePath, 'utf8');
69
+ const workbenchContent = JSON.parse(content);
70
+ content = JSON.stringify([...JSON.parse(existingWorkbenchConfig), ...workbenchContent], null, 2);
71
+ context.log('workbench-config-updated', (message) => message.tag('success').append('Workbench config').append('has been updated.'));
72
+ }
73
+ }
74
+ catch {
75
+ void 0;
76
+ }
54
77
  await fs_1.promises.writeFile(generateFilePath, content, 'utf8');
55
78
  context.log(sanitizedFileName, (message) => {
56
79
  message.tag('success').append('File').append(sanitizedFileName, 'cyan').append('has been created.');
@@ -1,27 +1,54 @@
1
- import { promises as fs, statSync } from 'fs'
1
+ import { promises as fs, statSync, mkdirSync } from 'fs'
2
2
  import * as path from 'path'
3
3
  import { globSync } from 'glob'
4
4
  import { CliContext } from '../../cloud/config-utils'
5
5
 
6
6
  export type Generator = (rootDir: string, context: CliContext) => Promise<void>
7
7
 
8
- export const generateTemplateSteps = (templateDir: string): Generator => {
8
+ export const generateTemplateSteps = (templateFolder: string): Generator => {
9
9
  return async (rootDir: string, context: CliContext): Promise<void> => {
10
- const templatePath = path.join(__dirname, templateDir)
10
+ const templatePath = path.join(__dirname, templateFolder)
11
11
  const files = globSync('**/*', { absolute: false, cwd: templatePath })
12
12
 
13
13
  try {
14
14
  for (const fileName of files) {
15
15
  const filePath = path.join(templatePath, fileName)
16
+ const targetFilePath = path.join(rootDir, fileName)
17
+ const targetDir = path.dirname(targetFilePath)
18
+
19
+ try {
20
+ // Check if it's a directory in the template
21
+ statSync(targetDir)
22
+ } catch {
23
+ mkdirSync(targetDir, { recursive: true })
24
+ }
16
25
 
17
26
  if (statSync(filePath).isDirectory()) {
18
- // ignore folders
27
+ const folderPath = path.basename(filePath)
28
+ mkdirSync(path.join(rootDir, folderPath), { recursive: true })
19
29
  continue
20
30
  }
21
31
 
22
- const sanitizedFileName = fileName.replace('.txt', '')
32
+ const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '')
33
+ const isWorkbenchConfig = fileName.match('motia-workbench.json')
23
34
  const generateFilePath = path.join(rootDir, sanitizedFileName)
24
- const content = await fs.readFile(filePath, 'utf8')
35
+ let content = await fs.readFile(filePath, 'utf8')
36
+
37
+ // Make sure statSync doesn't break the execution if the file doesn't exist
38
+ try {
39
+ if (isWorkbenchConfig && statSync(generateFilePath).isFile()) {
40
+ const existingWorkbenchConfig = await fs.readFile(generateFilePath, 'utf8')
41
+ const workbenchContent = JSON.parse(content)
42
+
43
+ content = JSON.stringify([...JSON.parse(existingWorkbenchConfig), ...workbenchContent], null, 2)
44
+
45
+ context.log('workbench-config-updated', (message) =>
46
+ message.tag('success').append('Workbench config').append('has been updated.'),
47
+ )
48
+ }
49
+ } catch {
50
+ void 0
51
+ }
25
52
 
26
53
  await fs.writeFile(generateFilePath, content, 'utf8')
27
54
  context.log(sanitizedFileName, (message) => {
@@ -3,6 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.templates = void 0;
4
4
  const generate_1 = require("./generate");
5
5
  exports.templates = {
6
- default: (0, generate_1.generateTemplateSteps)('default'),
6
+ typescript: (0, generate_1.generateTemplateSteps)('typescript'),
7
7
  python: (0, generate_1.generateTemplateSteps)('python'),
8
8
  };
@@ -1,6 +1,6 @@
1
1
  import { generateTemplateSteps, Generator } from './generate'
2
2
 
3
3
  export const templates: Record<string, Generator> = {
4
- default: generateTemplateSteps('default'),
4
+ typescript: generateTemplateSteps('typescript'),
5
5
  python: generateTemplateSteps('python'),
6
6
  }
@@ -1,28 +1,26 @@
1
1
  [
2
2
  {
3
- "id": "default",
3
+ "id": "python-tutorial",
4
4
  "config": {
5
- "steps/00_noop_step.py": {
6
- "x": -507,
7
- "y": 111,
5
+ "steps/state_audit_cron_step.py": {
6
+ "x": -38,
7
+ "y": 683,
8
8
  "sourceHandlePosition": "right"
9
9
  },
10
- "steps/01_api_step.py": {
11
- "x": -281,
12
- "y": 94,
13
- "sourceHandlePosition": "right",
10
+ "steps/process_food_order_step.py": {
11
+ "x": 384,
12
+ "y": 476,
14
13
  "targetHandlePosition": "left"
15
14
  },
16
- "steps/02_test_state_step.py": {
17
- "x": 60,
18
- "y": 36,
19
- "sourceHandlePosition": "bottom",
15
+ "steps/notification_step.py": {
16
+ "x": 601,
17
+ "y": 724,
20
18
  "targetHandlePosition": "left"
21
19
  },
22
- "steps/03_check_state_change_step.py": {
23
- "x": 78,
24
- "y": 199,
25
- "targetHandlePosition": "top"
20
+ "steps/api_step.py": {
21
+ "x": 15,
22
+ "y": 461,
23
+ "sourceHandlePosition": "right"
26
24
  }
27
25
  }
28
26
  }
@@ -0,0 +1,2 @@
1
+ pydantic>=2.6.1
2
+ httpx>=0.28.1
@@ -0,0 +1,69 @@
1
+ [
2
+ {
3
+ "id": "step-configuration",
4
+ "title": "Step Configuration",
5
+ "description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
6
+ "lines": [
7
+ "6-30"
8
+ ]
9
+ },
10
+ {
11
+ "id": "api-configuration",
12
+ "title": "API Step",
13
+ "description": "Definition of an API endpoint",
14
+ "lines": [
15
+ "23-24"
16
+ ]
17
+ },
18
+ {
19
+ "id": "request-body",
20
+ "title": "Request body",
21
+ "description": "Definition of the expected request body. Motia will automatically generate types based on this schema.",
22
+ "lines": [
23
+ "6-16",
24
+ "25"
25
+ ]
26
+ },
27
+ {
28
+ "id": "response-payload",
29
+ "title": "Response Payload",
30
+ "description": "Definition of the expected response payload, Motia will generate the types automatically based on this schema. This is also important to create the Open API spec later.",
31
+ "lines": [
32
+ "4",
33
+ "26-28"
34
+ ]
35
+ },
36
+ {
37
+ "id": "event-driven-architecture",
38
+ "title": "Emits",
39
+ "description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
40
+ "lines": [
41
+ "29",
42
+ "42-50"
43
+ ]
44
+ },
45
+ {
46
+ "id": "handler",
47
+ "title": "Handler",
48
+ "description": "The handler is the function that will be executed when the step is triggered. This one receives the request body and emits events.",
49
+ "lines": [
50
+ "32-52"
51
+ ]
52
+ },
53
+ {
54
+ "id": "logger",
55
+ "title": "Logger",
56
+ "description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function. We encourage you to use it instead of console.log. It will automatically be tied to the trace id of the request.",
57
+ "lines": [
58
+ "34"
59
+ ]
60
+ },
61
+ {
62
+ "id": "http-response",
63
+ "title": "HTTP Response",
64
+ "description": "The handler can return a response to the client. This is how we can return a response to the client. It must comply with the responseSchema defined in the step configuration.",
65
+ "lines": [
66
+ "52"
67
+ ]
68
+ }
69
+ ]
@@ -0,0 +1,52 @@
1
+ from pydantic import BaseModel
2
+ from typing import Optional
3
+ from .services.pet_store import pet_store_service
4
+ from .services.types import Pet
5
+
6
+ class PetRequest(BaseModel):
7
+ name: str
8
+ photo_url: str
9
+
10
+ class FoodOrder(BaseModel):
11
+ id: str
12
+ quantity: int
13
+
14
+ class RequestBody(BaseModel):
15
+ pet: PetRequest
16
+ food_order: Optional[FoodOrder] = None
17
+
18
+ config = {
19
+ "type": "api",
20
+ "name": "PythonApiTrigger",
21
+ "description": "basic-tutorial api trigger",
22
+ "flows": ["python-tutorial"],
23
+ "method": "POST",
24
+ "path": "/python-basic-tutorial",
25
+ "bodySchema": RequestBody.model_json_schema(),
26
+ "responseSchema": {
27
+ 200: Pet.model_json_schema(),
28
+ },
29
+ "emits": ["python-process-food-order"],
30
+ }
31
+
32
+ async def handler(req, context):
33
+ body = req.get("body", {})
34
+ context.logger.info("Step 01 – Processing API Step", {"body": body})
35
+
36
+ pet = body.get("pet", {})
37
+ food_order = body.get("food_order", {})
38
+
39
+ new_pet_record = await pet_store_service.create_pet(pet)
40
+
41
+ if food_order:
42
+ await context.emit({
43
+ "topic": "python-process-food-order",
44
+ "data": {
45
+ "id": food_order.get("id"),
46
+ "quantity": food_order.get("quantity"),
47
+ "email": "test@test.com", # sample email
48
+ "pet_id": new_pet_record.get("id"),
49
+ },
50
+ })
51
+
52
+ return {"status": 200, "body": new_pet_record}
@@ -0,0 +1,40 @@
1
+ from pydantic import BaseModel
2
+ from typing import Dict, Any
3
+ import re
4
+
5
+ class InputSchema(BaseModel):
6
+ template_id: str
7
+ email: str
8
+ template_data: Dict[str, Any]
9
+
10
+ config = {
11
+ "type": "event",
12
+ "name": "PythonNotification",
13
+ "description": "Checks a state change",
14
+ "flows": ["python-tutorial"],
15
+ "subscribes": ["python-notification"],
16
+ "emits": [],
17
+ "input": InputSchema.model_json_schema(),
18
+ }
19
+
20
+ async def handler(input_data, context):
21
+ email = input_data.get("email")
22
+ template_id = input_data.get("template_id")
23
+ template_data = input_data.get("template_data")
24
+
25
+ redacted_email = re.sub(r'(?<=.{2}).(?=.*@)', '*', email)
26
+
27
+ context.logger.info("Processing Notification", {
28
+ "template_id": template_id,
29
+ "template_data": template_data,
30
+ "email": redacted_email,
31
+ })
32
+
33
+ # This represents a call to some sort of
34
+ # notification service to indicate that a
35
+ # new order has been placed
36
+ context.logger.info("New notification sent", {
37
+ "template_id": template_id,
38
+ "email": redacted_email,
39
+ "template_data": template_data,
40
+ })
@@ -0,0 +1,68 @@
1
+ [
2
+ {
3
+ "id": "step-configuration",
4
+ "title": "Step Configuration",
5
+ "description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
6
+ "lines": [
7
+ "5-19"
8
+ ]
9
+ },
10
+ {
11
+ "id": "event-configuration",
12
+ "title": "Event Step",
13
+ "description": "Definition of an event step that subscribes to specific topics",
14
+ "lines": [
15
+ "12",
16
+ "15-16"
17
+ ]
18
+ },
19
+ {
20
+ "id": "input-schema",
21
+ "title": "Input Schema",
22
+ "description": "Definition of the expected input data structure from the subscribed topic. Motia will automatically generate types based on this schema.",
23
+ "lines": [
24
+ "5-9",
25
+ "17"
26
+ ]
27
+ },
28
+ {
29
+ "id": "event-emits",
30
+ "title": "Emits",
31
+ "description": "We can define the events that this step will emit, triggering other Motia Steps.",
32
+ "lines": [
33
+ "17"
34
+ ]
35
+ },
36
+ {
37
+ "id": "handler",
38
+ "title": "Handler",
39
+ "description": "The handler is the function that will be executed when the step receives an event from its subscribed topic. It processes the input data and can emit new events.",
40
+ "lines": [
41
+ "21-50"
42
+ ]
43
+ },
44
+ {
45
+ "id": "state",
46
+ "title": "State Management",
47
+ "description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
48
+ "lines": [
49
+ "35"
50
+ ]
51
+ },
52
+ {
53
+ "id": "event-emission",
54
+ "title": "Event Emission",
55
+ "description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
56
+ "lines": [
57
+ "37-50"
58
+ ]
59
+ },
60
+ {
61
+ "id": "logger",
62
+ "title": "Logger",
63
+ "description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function and automatically ties to the trace id of the request.",
64
+ "lines": [
65
+ "22"
66
+ ]
67
+ }
68
+ ]
@@ -0,0 +1,50 @@
1
+ from pydantic import BaseModel
2
+ from datetime import datetime
3
+ from .services.pet_store import pet_store_service
4
+
5
+ class InputSchema(BaseModel):
6
+ id: str
7
+ email: str
8
+ quantity: int
9
+ pet_id: int
10
+
11
+ config = {
12
+ "type": "event",
13
+ "name": "PythonProcessFoodOrder",
14
+ "description": "basic-tutorial event step, demonstrates how to consume an event from a topic and persist data in state",
15
+ "flows": ["python-tutorial"],
16
+ "subscribes": ["python-process-food-order"],
17
+ "emits": ["python-notification"],
18
+ "input": InputSchema.model_json_schema(),
19
+ }
20
+
21
+ async def handler(input_data, context):
22
+ context.logger.info("Step 02 – Process food order", {"input": input_data})
23
+
24
+ order = await pet_store_service.create_order({
25
+ "id": input_data.get("id"),
26
+ "quantity": input_data.get("quantity"),
27
+ "pet_id": input_data.get("pet_id"),
28
+ "email": input_data.get("email"),
29
+ "ship_date": datetime.now().isoformat(),
30
+ "status": "placed",
31
+ })
32
+
33
+ context.logger.info("Order created", {"order": order})
34
+
35
+ await context.state.set("orders_python", order.get("id"), order)
36
+
37
+ await context.emit({
38
+ "topic": "python-notification",
39
+ "data": {
40
+ "email": input_data["email"],
41
+ "template_id": "new-order",
42
+ "template_data": {
43
+ "status": order.get("status"),
44
+ "ship_date": order.get("shipDate"),
45
+ "id": order.get("id"),
46
+ "pet_id": order.get("petId"),
47
+ "quantity": order.get("quantity"),
48
+ },
49
+ },
50
+ })