motia 0.5.11-beta.120-598191 → 0.5.11-beta.120-167191

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 (55) hide show
  1. package/dist/cjs/create/index.js +0 -8
  2. package/dist/cjs/create/interactive.js +1 -1
  3. package/dist/cjs/create/templates/generate.js +11 -2
  4. package/dist/cjs/create/templates/generate.ts +11 -2
  5. package/dist/cjs/create/templates/index.js +1 -1
  6. package/dist/cjs/create/templates/index.ts +1 -1
  7. package/dist/cjs/create/templates/python/motia-workbench.json +13 -12
  8. package/dist/cjs/create/templates/python/requirements.txt +2 -1
  9. package/dist/cjs/create/templates/python/steps/api_step.py-features.json.txt +4 -4
  10. package/dist/cjs/create/templates/python/steps/api_step.py.txt +3 -3
  11. package/dist/cjs/create/templates/python/steps/notification_step.py.txt +5 -1
  12. package/dist/cjs/create/templates/python/steps/process_food_order_step.py-features.json.txt +8 -8
  13. package/dist/cjs/create/templates/python/steps/process_food_order_step.py.txt +13 -7
  14. package/dist/cjs/create/templates/python/steps/services/pet_store.py.txt +8 -1
  15. package/dist/cjs/create/templates/python/steps/state_audit_cron_step.py.txt +6 -6
  16. package/dist/cjs/create/templates/python/tutorial.tsx.txt +11 -9
  17. package/dist/cjs/create/templates/{default → typescript}/services/pet-store.ts.txt +6 -1
  18. package/dist/cjs/create/templates/{default → typescript}/tutorial.tsx.txt +11 -9
  19. package/dist/esm/create/index.js +0 -8
  20. package/dist/esm/create/interactive.js +1 -1
  21. package/dist/esm/create/templates/generate.js +11 -2
  22. package/dist/esm/create/templates/generate.ts +11 -2
  23. package/dist/esm/create/templates/index.js +1 -1
  24. package/dist/esm/create/templates/index.ts +1 -1
  25. package/dist/esm/create/templates/python/motia-workbench.json +13 -12
  26. package/dist/esm/create/templates/python/requirements.txt +2 -1
  27. package/dist/esm/create/templates/python/steps/api_step.py-features.json.txt +4 -4
  28. package/dist/esm/create/templates/python/steps/api_step.py.txt +3 -3
  29. package/dist/esm/create/templates/python/steps/notification_step.py.txt +5 -1
  30. package/dist/esm/create/templates/python/steps/process_food_order_step.py-features.json.txt +8 -8
  31. package/dist/esm/create/templates/python/steps/process_food_order_step.py.txt +13 -7
  32. package/dist/esm/create/templates/python/steps/services/pet_store.py.txt +8 -1
  33. package/dist/esm/create/templates/python/steps/state_audit_cron_step.py.txt +6 -6
  34. package/dist/esm/create/templates/python/tutorial.tsx.txt +11 -9
  35. package/dist/esm/create/templates/{default → typescript}/services/pet-store.ts.txt +6 -1
  36. package/dist/esm/create/templates/{default → typescript}/tutorial.tsx.txt +11 -9
  37. package/package.json +4 -4
  38. /package/dist/cjs/create/templates/{default → typescript}/motia-workbench.json +0 -0
  39. /package/dist/cjs/create/templates/{default → typescript}/services/types.ts.txt +0 -0
  40. /package/dist/cjs/create/templates/{default → typescript}/steps/01-api.step.ts-features.json.txt +0 -0
  41. /package/dist/cjs/create/templates/{default → typescript}/steps/01-api.step.ts.txt +0 -0
  42. /package/dist/cjs/create/templates/{default → typescript}/steps/02-process-food-order.step.ts-features.json.txt +0 -0
  43. /package/dist/cjs/create/templates/{default → typescript}/steps/02-process-food-order.step.ts.txt +0 -0
  44. /package/dist/cjs/create/templates/{default → typescript}/steps/03-state-audit-cron.step.ts-features.json.txt +0 -0
  45. /package/dist/cjs/create/templates/{default → typescript}/steps/03-state-audit-cron.step.ts.txt +0 -0
  46. /package/dist/cjs/create/templates/{default → typescript}/steps/04-notification.step.ts.txt +0 -0
  47. /package/dist/esm/create/templates/{default → typescript}/motia-workbench.json +0 -0
  48. /package/dist/esm/create/templates/{default → typescript}/services/types.ts.txt +0 -0
  49. /package/dist/esm/create/templates/{default → typescript}/steps/01-api.step.ts-features.json.txt +0 -0
  50. /package/dist/esm/create/templates/{default → typescript}/steps/01-api.step.ts.txt +0 -0
  51. /package/dist/esm/create/templates/{default → typescript}/steps/02-process-food-order.step.ts-features.json.txt +0 -0
  52. /package/dist/esm/create/templates/{default → typescript}/steps/02-process-food-order.step.ts.txt +0 -0
  53. /package/dist/esm/create/templates/{default → typescript}/steps/03-state-audit-cron.step.ts-features.json.txt +0 -0
  54. /package/dist/esm/create/templates/{default → typescript}/steps/03-state-audit-cron.step.ts.txt +0 -0
  55. /package/dist/esm/create/templates/{default → typescript}/steps/04-notification.step.ts.txt +0 -0
@@ -182,14 +182,6 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
182
182
  }
183
183
  const packageManager = await installNodeDependencies(rootDir, context);
184
184
  if (template === 'python') {
185
- if (!(0, utils_1.checkIfFileExists)(rootDir, 'requirements.txt')) {
186
- const requirementsContent = [
187
- // TODO: motia PyPi package
188
- // Add other Python dependencies as needed
189
- ].join('\n');
190
- fs_1.default.writeFileSync(path_1.default.join(rootDir, 'requirements.txt'), requirementsContent);
191
- context.log('requirements-txt-created', (message) => message.tag('success').append('File').append('requirements.txt', 'gray').append('has been created.'));
192
- }
193
185
  await (0, install_1.pythonInstall)({ baseDir: rootDir });
194
186
  }
195
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) => {
@@ -44,12 +44,21 @@ const generateTemplateSteps = (templateFolder) => {
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
57
  const folderPath = path.basename(filePath);
49
- (0, fs_1.mkdirSync)(path.join(rootDir, folderPath));
58
+ (0, fs_1.mkdirSync)(path.join(rootDir, folderPath), { recursive: true });
50
59
  continue;
51
60
  }
52
- const sanitizedFileName = fileName.replace('.txt', '');
61
+ const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '');
53
62
  const isWorkbenchConfig = fileName.match('motia-workbench.json');
54
63
  const generateFilePath = path.join(rootDir, sanitizedFileName);
55
64
  let content = await fs_1.promises.readFile(filePath, 'utf8');
@@ -13,14 +13,23 @@ export const generateTemplateSteps = (templateFolder: string): Generator => {
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
27
  const folderPath = path.basename(filePath)
19
- mkdirSync(path.join(rootDir, folderPath))
28
+ mkdirSync(path.join(rootDir, folderPath), { recursive: true })
20
29
  continue
21
30
  }
22
31
 
23
- const sanitizedFileName = fileName.replace('.txt', '')
32
+ const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '')
24
33
  const isWorkbenchConfig = fileName.match('motia-workbench.json')
25
34
  const generateFilePath = path.join(rootDir, sanitizedFileName)
26
35
  let content = await fs.readFile(filePath, 'utf8')
@@ -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
  }
@@ -2,23 +2,24 @@
2
2
  {
3
3
  "id": "python-tutorial",
4
4
  "config": {
5
- "steps/default_python/state_audit_cron_step.py": {
6
- "x": -332,
7
- "y": 149,
5
+ "steps/state_audit_cron_step.py": {
6
+ "x": -38,
7
+ "y": 683,
8
8
  "sourceHandlePosition": "right"
9
9
  },
10
- "steps/default_python/process_food_order_step.py": {
11
- "x": 81,
12
- "y": -52,
10
+ "steps/process_food_order_step.py": {
11
+ "x": 384,
12
+ "y": 476,
13
13
  "targetHandlePosition": "left"
14
14
  },
15
- "steps/default_python/notification_step.py": {
16
- "x": 142,
17
- "y": 175
15
+ "steps/notification_step.py": {
16
+ "x": 601,
17
+ "y": 724,
18
+ "targetHandlePosition": "left"
18
19
  },
19
- "steps/default_python/api_step.py": {
20
- "x": -302,
21
- "y": -66,
20
+ "steps/api_step.py": {
21
+ "x": 15,
22
+ "y": 461,
22
23
  "sourceHandlePosition": "right"
23
24
  }
24
25
  }
@@ -1 +1,2 @@
1
- pydantic>=2.6.1
1
+ pydantic>=2.6.1
2
+ httpx>=0.28.1
@@ -39,7 +39,7 @@
39
39
  "description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
40
40
  "lines": [
41
41
  "29",
42
- "40-47"
42
+ "42-50"
43
43
  ]
44
44
  },
45
45
  {
@@ -47,7 +47,7 @@
47
47
  "title": "Handler",
48
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
49
  "lines": [
50
- "32-49"
50
+ "32-52"
51
51
  ]
52
52
  },
53
53
  {
@@ -55,7 +55,7 @@
55
55
  "title": "Logger",
56
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
57
  "lines": [
58
- "33"
58
+ "34"
59
59
  ]
60
60
  },
61
61
  {
@@ -63,7 +63,7 @@
63
63
  "title": "HTTP Response",
64
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
65
  "lines": [
66
- "49"
66
+ "52"
67
67
  ]
68
68
  }
69
69
  ]
@@ -39,13 +39,13 @@ async def handler(req, context):
39
39
  new_pet_record = await pet_store_service.create_pet(pet)
40
40
 
41
41
  if food_order:
42
- food_order = FoodOrder(**food_order)
43
42
  await context.emit({
44
43
  "topic": "python-process-food-order",
45
44
  "data": {
46
- **food_order.model_dump(),
45
+ "id": food_order.get("id"),
46
+ "quantity": food_order.get("quantity"),
47
47
  "email": "test@test.com", # sample email
48
- "pet_id": new_pet_record.id,
48
+ "pet_id": new_pet_record.get("id"),
49
49
  },
50
50
  })
51
51
 
@@ -24,7 +24,11 @@ async def handler(input_data, context):
24
24
 
25
25
  redacted_email = re.sub(r'(?<=.{2}).(?=.*@)', '*', email)
26
26
 
27
- context.logger.info("Processing Notification", {"template_id": template_id, "template_data": template_data, "email": redacted_email})
27
+ context.logger.info("Processing Notification", {
28
+ "template_id": template_id,
29
+ "template_data": template_data,
30
+ "email": redacted_email,
31
+ })
28
32
 
29
33
  # This represents a call to some sort of
30
34
  # notification service to indicate that a
@@ -4,7 +4,7 @@
4
4
  "title": "Step Configuration",
5
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
6
  "lines": [
7
- "5-18"
7
+ "5-19"
8
8
  ]
9
9
  },
10
10
  {
@@ -12,7 +12,7 @@
12
12
  "title": "Event Step",
13
13
  "description": "Definition of an event step that subscribes to specific topics",
14
14
  "lines": [
15
- "11",
15
+ "12",
16
16
  "15-16"
17
17
  ]
18
18
  },
@@ -21,7 +21,7 @@
21
21
  "title": "Input Schema",
22
22
  "description": "Definition of the expected input data structure from the subscribed topic. Motia will automatically generate types based on this schema.",
23
23
  "lines": [
24
- "5-8",
24
+ "5-9",
25
25
  "17"
26
26
  ]
27
27
  },
@@ -30,7 +30,7 @@
30
30
  "title": "Emits",
31
31
  "description": "We can define the events that this step will emit, triggering other Motia Steps.",
32
32
  "lines": [
33
- "16"
33
+ "17"
34
34
  ]
35
35
  },
36
36
  {
@@ -38,7 +38,7 @@
38
38
  "title": "Handler",
39
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
40
  "lines": [
41
- "20-44"
41
+ "21-50"
42
42
  ]
43
43
  },
44
44
  {
@@ -46,7 +46,7 @@
46
46
  "title": "State Management",
47
47
  "description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
48
48
  "lines": [
49
- "29"
49
+ "35"
50
50
  ]
51
51
  },
52
52
  {
@@ -54,7 +54,7 @@
54
54
  "title": "Event Emission",
55
55
  "description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
56
56
  "lines": [
57
- "31-44"
57
+ "37-50"
58
58
  ]
59
59
  },
60
60
  {
@@ -62,7 +62,7 @@
62
62
  "title": "Logger",
63
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
64
  "lines": [
65
- "21"
65
+ "22"
66
66
  ]
67
67
  }
68
68
  ]
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  from .services.pet_store import pet_store_service
4
4
 
5
5
  class InputSchema(BaseModel):
6
+ id: str
6
7
  email: str
7
8
  quantity: int
8
9
  pet_id: int
@@ -21,12 +22,17 @@ async def handler(input_data, context):
21
22
  context.logger.info("Step 02 – Process food order", {"input": input_data})
22
23
 
23
24
  order = await pet_store_service.create_order({
24
- **input_data,
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"),
25
29
  "ship_date": datetime.now().isoformat(),
26
30
  "status": "placed",
27
31
  })
28
32
 
29
- await context.state.set("orders_python", order.id, order)
33
+ context.logger.info("Order created", {"order": order})
34
+
35
+ await context.state.set("orders_python", order.get("id"), order)
30
36
 
31
37
  await context.emit({
32
38
  "topic": "python-notification",
@@ -34,11 +40,11 @@ async def handler(input_data, context):
34
40
  "email": input_data["email"],
35
41
  "template_id": "new-order",
36
42
  "template_data": {
37
- "status": order.status,
38
- "ship_date": order.ship_date,
39
- "id": order.id,
40
- "pet_id": order.pet_id,
41
- "quantity": order.quantity,
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"),
42
48
  },
43
49
  },
44
50
  })
@@ -20,9 +20,16 @@ class PetStoreService:
20
20
 
21
21
  async def create_order(self, order: Dict[str, Any]) -> Order:
22
22
  async with httpx.AsyncClient() as client:
23
+ order_data = {
24
+ "quantity": order.get("quantity"),
25
+ "petId": 1,
26
+ "shipDate": order.get("ship_date"),
27
+ "status": order.get("status"),
28
+ }
29
+
23
30
  response = await client.post(
24
31
  'https://petstore.swagger.io/v2/store/order',
25
- json=order,
32
+ json=order_data,
26
33
  headers={'Content-Type': 'application/json'}
27
34
  )
28
35
  return response.json()
@@ -15,12 +15,12 @@ async def handler(context):
15
15
  for item in state_value:
16
16
  # check if current date is after item.ship_date
17
17
  current_date = datetime.now()
18
- ship_date = datetime.fromisoformat(item["ship_date"].replace('Z', '+00:00'))
18
+ ship_date = datetime.fromisoformat(item.get("shipDate", "").replace('Z', '+00:00'))
19
19
 
20
20
  if not item.get("complete", False) and current_date > ship_date:
21
21
  context.logger.warn("Order is not complete and ship date is past", {
22
- "order_id": item["id"],
23
- "ship_date": item["ship_date"],
22
+ "order_id": item.get("id"),
23
+ "ship_date": item.get("shipDate"),
24
24
  "complete": item.get("complete", False),
25
25
  })
26
26
 
@@ -30,9 +30,9 @@ async def handler(context):
30
30
  "email": "test@test.com",
31
31
  "template_id": "order-audit-warning",
32
32
  "template_data": {
33
- "order_id": item["id"],
34
- "status": item["status"],
35
- "ship_date": item["ship_date"],
33
+ "order_id": item.get("id"),
34
+ "status": item.get("status"),
35
+ "ship_date": item.get("shipDate"),
36
36
  "message": "Order is not complete and ship date is past",
37
37
  },
38
38
  },
@@ -54,14 +54,16 @@ export const steps: TutorialStep[] = [
54
54
  elementXpath: workbenchXPath.sidebarContainer,
55
55
  title: 'Step Config',
56
56
  description: () => (
57
- <p>
58
- All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
59
- the programming language).
60
- <br />
61
- <br />
62
- Let's start with the configuration, the common config attributes are
63
- <i>type, name, description, and flows</i>.<br />
64
- <br />
57
+ <div>
58
+ <p>
59
+ All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
60
+ the programming language).
61
+ <br />
62
+ <br />
63
+ Let's start with the configuration, the common config attributes are
64
+ <i>type, name, description, and flows</i>.<br />
65
+ <br />
66
+ </p>
65
67
  <ul>
66
68
  <li>
67
69
  The <b>type</b> attribute is important since it declares the type of Step primitive
@@ -74,7 +76,7 @@ export const steps: TutorialStep[] = [
74
76
  observability tools.
75
77
  </li>
76
78
  </ul>
77
- </p>
79
+ </div>
78
80
  ),
79
81
  before: [
80
82
  { type: 'click', selector: workbenchXPath.flows.previewButton('pythonapitrigger') },
@@ -16,7 +16,12 @@ export const petStoreService = {
16
16
  createOrder: async (order: Omit<Order, 'id'>): Promise<Order> => {
17
17
  const response = await fetch('https://petstore.swagger.io/v2/store/order', {
18
18
  method: 'POST',
19
- body: JSON.stringify(order),
19
+ body: JSON.stringify({
20
+ quantity: order.quantity,
21
+ petId: 1,
22
+ shipDate: order.shipDate,
23
+ status: order.status,
24
+ }),
20
25
  headers: { 'Content-Type': 'application/json' },
21
26
  })
22
27
  return response.json()
@@ -54,14 +54,16 @@ export const steps: TutorialStep[] = [
54
54
  elementXpath: workbenchXPath.sidebarContainer,
55
55
  title: 'Step Config',
56
56
  description: () => (
57
- <p>
58
- All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
59
- the programming language).
60
- <br />
61
- <br />
62
- Let's start with the configuration, the common config attributes are
63
- <i>type, name, description, and flows</i>.<br />
64
- <br />
57
+ <div>
58
+ <p>
59
+ All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
60
+ the programming language).
61
+ <br />
62
+ <br />
63
+ Let's start with the configuration, the common config attributes are
64
+ <i>type, name, description, and flows</i>.<br />
65
+ <br />
66
+ </p>
65
67
  <ul>
66
68
  <li>
67
69
  The <b>type</b> attribute is important since it declares the type of Step primitive
@@ -74,7 +76,7 @@ export const steps: TutorialStep[] = [
74
76
  observability tools.
75
77
  </li>
76
78
  </ul>
77
- </p>
79
+ </div>
78
80
  ),
79
81
  before: [
80
82
  { type: 'click', selector: workbenchXPath.flows.previewButton('apitrigger') },
@@ -176,14 +176,6 @@ export const create = async ({ projectName, template, cursorEnabled, context })
176
176
  }
177
177
  const packageManager = await installNodeDependencies(rootDir, context);
178
178
  if (template === 'python') {
179
- if (!checkIfFileExists(rootDir, 'requirements.txt')) {
180
- const requirementsContent = [
181
- // TODO: motia PyPi package
182
- // Add other Python dependencies as needed
183
- ].join('\n');
184
- fs.writeFileSync(path.join(rootDir, 'requirements.txt'), requirementsContent);
185
- context.log('requirements-txt-created', (message) => message.tag('success').append('File').append('requirements.txt', 'gray').append('has been created.'));
186
- }
187
179
  await pythonInstall({ baseDir: rootDir });
188
180
  }
189
181
  await generateTypes(rootDir);
@@ -2,7 +2,7 @@ import inquirer from 'inquirer';
2
2
  import colors from 'colors';
3
3
  import { create } from './index';
4
4
  const choices = {
5
- default: 'Base (TypeScript)',
5
+ typescript: 'Base (TypeScript)',
6
6
  python: 'Base (Python)',
7
7
  };
8
8
  export const createInteractive = async (_args, context) => {
@@ -8,12 +8,21 @@ export const generateTemplateSteps = (templateFolder) => {
8
8
  try {
9
9
  for (const fileName of files) {
10
10
  const filePath = path.join(templatePath, fileName);
11
+ const targetFilePath = path.join(rootDir, fileName);
12
+ const targetDir = path.dirname(targetFilePath);
13
+ try {
14
+ // Check if it's a directory in the template
15
+ statSync(targetDir);
16
+ }
17
+ catch {
18
+ mkdirSync(targetDir, { recursive: true });
19
+ }
11
20
  if (statSync(filePath).isDirectory()) {
12
21
  const folderPath = path.basename(filePath);
13
- mkdirSync(path.join(rootDir, folderPath));
22
+ mkdirSync(path.join(rootDir, folderPath), { recursive: true });
14
23
  continue;
15
24
  }
16
- const sanitizedFileName = fileName.replace('.txt', '');
25
+ const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '');
17
26
  const isWorkbenchConfig = fileName.match('motia-workbench.json');
18
27
  const generateFilePath = path.join(rootDir, sanitizedFileName);
19
28
  let content = await fs.readFile(filePath, 'utf8');
@@ -13,14 +13,23 @@ export const generateTemplateSteps = (templateFolder: string): Generator => {
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
27
  const folderPath = path.basename(filePath)
19
- mkdirSync(path.join(rootDir, folderPath))
28
+ mkdirSync(path.join(rootDir, folderPath), { recursive: true })
20
29
  continue
21
30
  }
22
31
 
23
- const sanitizedFileName = fileName.replace('.txt', '')
32
+ const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '')
24
33
  const isWorkbenchConfig = fileName.match('motia-workbench.json')
25
34
  const generateFilePath = path.join(rootDir, sanitizedFileName)
26
35
  let content = await fs.readFile(filePath, 'utf8')
@@ -1,5 +1,5 @@
1
1
  import { generateTemplateSteps } from './generate';
2
2
  export const templates = {
3
- default: generateTemplateSteps('default'),
3
+ typescript: generateTemplateSteps('typescript'),
4
4
  python: generateTemplateSteps('python'),
5
5
  };
@@ -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
  }
@@ -2,23 +2,24 @@
2
2
  {
3
3
  "id": "python-tutorial",
4
4
  "config": {
5
- "steps/default_python/state_audit_cron_step.py": {
6
- "x": -332,
7
- "y": 149,
5
+ "steps/state_audit_cron_step.py": {
6
+ "x": -38,
7
+ "y": 683,
8
8
  "sourceHandlePosition": "right"
9
9
  },
10
- "steps/default_python/process_food_order_step.py": {
11
- "x": 81,
12
- "y": -52,
10
+ "steps/process_food_order_step.py": {
11
+ "x": 384,
12
+ "y": 476,
13
13
  "targetHandlePosition": "left"
14
14
  },
15
- "steps/default_python/notification_step.py": {
16
- "x": 142,
17
- "y": 175
15
+ "steps/notification_step.py": {
16
+ "x": 601,
17
+ "y": 724,
18
+ "targetHandlePosition": "left"
18
19
  },
19
- "steps/default_python/api_step.py": {
20
- "x": -302,
21
- "y": -66,
20
+ "steps/api_step.py": {
21
+ "x": 15,
22
+ "y": 461,
22
23
  "sourceHandlePosition": "right"
23
24
  }
24
25
  }
@@ -1 +1,2 @@
1
- pydantic>=2.6.1
1
+ pydantic>=2.6.1
2
+ httpx>=0.28.1
@@ -39,7 +39,7 @@
39
39
  "description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
40
40
  "lines": [
41
41
  "29",
42
- "40-47"
42
+ "42-50"
43
43
  ]
44
44
  },
45
45
  {
@@ -47,7 +47,7 @@
47
47
  "title": "Handler",
48
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
49
  "lines": [
50
- "32-49"
50
+ "32-52"
51
51
  ]
52
52
  },
53
53
  {
@@ -55,7 +55,7 @@
55
55
  "title": "Logger",
56
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
57
  "lines": [
58
- "33"
58
+ "34"
59
59
  ]
60
60
  },
61
61
  {
@@ -63,7 +63,7 @@
63
63
  "title": "HTTP Response",
64
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
65
  "lines": [
66
- "49"
66
+ "52"
67
67
  ]
68
68
  }
69
69
  ]
@@ -39,13 +39,13 @@ async def handler(req, context):
39
39
  new_pet_record = await pet_store_service.create_pet(pet)
40
40
 
41
41
  if food_order:
42
- food_order = FoodOrder(**food_order)
43
42
  await context.emit({
44
43
  "topic": "python-process-food-order",
45
44
  "data": {
46
- **food_order.model_dump(),
45
+ "id": food_order.get("id"),
46
+ "quantity": food_order.get("quantity"),
47
47
  "email": "test@test.com", # sample email
48
- "pet_id": new_pet_record.id,
48
+ "pet_id": new_pet_record.get("id"),
49
49
  },
50
50
  })
51
51
 
@@ -24,7 +24,11 @@ async def handler(input_data, context):
24
24
 
25
25
  redacted_email = re.sub(r'(?<=.{2}).(?=.*@)', '*', email)
26
26
 
27
- context.logger.info("Processing Notification", {"template_id": template_id, "template_data": template_data, "email": redacted_email})
27
+ context.logger.info("Processing Notification", {
28
+ "template_id": template_id,
29
+ "template_data": template_data,
30
+ "email": redacted_email,
31
+ })
28
32
 
29
33
  # This represents a call to some sort of
30
34
  # notification service to indicate that a
@@ -4,7 +4,7 @@
4
4
  "title": "Step Configuration",
5
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
6
  "lines": [
7
- "5-18"
7
+ "5-19"
8
8
  ]
9
9
  },
10
10
  {
@@ -12,7 +12,7 @@
12
12
  "title": "Event Step",
13
13
  "description": "Definition of an event step that subscribes to specific topics",
14
14
  "lines": [
15
- "11",
15
+ "12",
16
16
  "15-16"
17
17
  ]
18
18
  },
@@ -21,7 +21,7 @@
21
21
  "title": "Input Schema",
22
22
  "description": "Definition of the expected input data structure from the subscribed topic. Motia will automatically generate types based on this schema.",
23
23
  "lines": [
24
- "5-8",
24
+ "5-9",
25
25
  "17"
26
26
  ]
27
27
  },
@@ -30,7 +30,7 @@
30
30
  "title": "Emits",
31
31
  "description": "We can define the events that this step will emit, triggering other Motia Steps.",
32
32
  "lines": [
33
- "16"
33
+ "17"
34
34
  ]
35
35
  },
36
36
  {
@@ -38,7 +38,7 @@
38
38
  "title": "Handler",
39
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
40
  "lines": [
41
- "20-44"
41
+ "21-50"
42
42
  ]
43
43
  },
44
44
  {
@@ -46,7 +46,7 @@
46
46
  "title": "State Management",
47
47
  "description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
48
48
  "lines": [
49
- "29"
49
+ "35"
50
50
  ]
51
51
  },
52
52
  {
@@ -54,7 +54,7 @@
54
54
  "title": "Event Emission",
55
55
  "description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
56
56
  "lines": [
57
- "31-44"
57
+ "37-50"
58
58
  ]
59
59
  },
60
60
  {
@@ -62,7 +62,7 @@
62
62
  "title": "Logger",
63
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
64
  "lines": [
65
- "21"
65
+ "22"
66
66
  ]
67
67
  }
68
68
  ]
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  from .services.pet_store import pet_store_service
4
4
 
5
5
  class InputSchema(BaseModel):
6
+ id: str
6
7
  email: str
7
8
  quantity: int
8
9
  pet_id: int
@@ -21,12 +22,17 @@ async def handler(input_data, context):
21
22
  context.logger.info("Step 02 – Process food order", {"input": input_data})
22
23
 
23
24
  order = await pet_store_service.create_order({
24
- **input_data,
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"),
25
29
  "ship_date": datetime.now().isoformat(),
26
30
  "status": "placed",
27
31
  })
28
32
 
29
- await context.state.set("orders_python", order.id, order)
33
+ context.logger.info("Order created", {"order": order})
34
+
35
+ await context.state.set("orders_python", order.get("id"), order)
30
36
 
31
37
  await context.emit({
32
38
  "topic": "python-notification",
@@ -34,11 +40,11 @@ async def handler(input_data, context):
34
40
  "email": input_data["email"],
35
41
  "template_id": "new-order",
36
42
  "template_data": {
37
- "status": order.status,
38
- "ship_date": order.ship_date,
39
- "id": order.id,
40
- "pet_id": order.pet_id,
41
- "quantity": order.quantity,
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"),
42
48
  },
43
49
  },
44
50
  })
@@ -20,9 +20,16 @@ class PetStoreService:
20
20
 
21
21
  async def create_order(self, order: Dict[str, Any]) -> Order:
22
22
  async with httpx.AsyncClient() as client:
23
+ order_data = {
24
+ "quantity": order.get("quantity"),
25
+ "petId": 1,
26
+ "shipDate": order.get("ship_date"),
27
+ "status": order.get("status"),
28
+ }
29
+
23
30
  response = await client.post(
24
31
  'https://petstore.swagger.io/v2/store/order',
25
- json=order,
32
+ json=order_data,
26
33
  headers={'Content-Type': 'application/json'}
27
34
  )
28
35
  return response.json()
@@ -15,12 +15,12 @@ async def handler(context):
15
15
  for item in state_value:
16
16
  # check if current date is after item.ship_date
17
17
  current_date = datetime.now()
18
- ship_date = datetime.fromisoformat(item["ship_date"].replace('Z', '+00:00'))
18
+ ship_date = datetime.fromisoformat(item.get("shipDate", "").replace('Z', '+00:00'))
19
19
 
20
20
  if not item.get("complete", False) and current_date > ship_date:
21
21
  context.logger.warn("Order is not complete and ship date is past", {
22
- "order_id": item["id"],
23
- "ship_date": item["ship_date"],
22
+ "order_id": item.get("id"),
23
+ "ship_date": item.get("shipDate"),
24
24
  "complete": item.get("complete", False),
25
25
  })
26
26
 
@@ -30,9 +30,9 @@ async def handler(context):
30
30
  "email": "test@test.com",
31
31
  "template_id": "order-audit-warning",
32
32
  "template_data": {
33
- "order_id": item["id"],
34
- "status": item["status"],
35
- "ship_date": item["ship_date"],
33
+ "order_id": item.get("id"),
34
+ "status": item.get("status"),
35
+ "ship_date": item.get("shipDate"),
36
36
  "message": "Order is not complete and ship date is past",
37
37
  },
38
38
  },
@@ -54,14 +54,16 @@ export const steps: TutorialStep[] = [
54
54
  elementXpath: workbenchXPath.sidebarContainer,
55
55
  title: 'Step Config',
56
56
  description: () => (
57
- <p>
58
- All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
59
- the programming language).
60
- <br />
61
- <br />
62
- Let's start with the configuration, the common config attributes are
63
- <i>type, name, description, and flows</i>.<br />
64
- <br />
57
+ <div>
58
+ <p>
59
+ All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
60
+ the programming language).
61
+ <br />
62
+ <br />
63
+ Let's start with the configuration, the common config attributes are
64
+ <i>type, name, description, and flows</i>.<br />
65
+ <br />
66
+ </p>
65
67
  <ul>
66
68
  <li>
67
69
  The <b>type</b> attribute is important since it declares the type of Step primitive
@@ -74,7 +76,7 @@ export const steps: TutorialStep[] = [
74
76
  observability tools.
75
77
  </li>
76
78
  </ul>
77
- </p>
79
+ </div>
78
80
  ),
79
81
  before: [
80
82
  { type: 'click', selector: workbenchXPath.flows.previewButton('pythonapitrigger') },
@@ -16,7 +16,12 @@ export const petStoreService = {
16
16
  createOrder: async (order: Omit<Order, 'id'>): Promise<Order> => {
17
17
  const response = await fetch('https://petstore.swagger.io/v2/store/order', {
18
18
  method: 'POST',
19
- body: JSON.stringify(order),
19
+ body: JSON.stringify({
20
+ quantity: order.quantity,
21
+ petId: 1,
22
+ shipDate: order.shipDate,
23
+ status: order.status,
24
+ }),
20
25
  headers: { 'Content-Type': 'application/json' },
21
26
  })
22
27
  return response.json()
@@ -54,14 +54,16 @@ export const steps: TutorialStep[] = [
54
54
  elementXpath: workbenchXPath.sidebarContainer,
55
55
  title: 'Step Config',
56
56
  description: () => (
57
- <p>
58
- All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
59
- the programming language).
60
- <br />
61
- <br />
62
- Let's start with the configuration, the common config attributes are
63
- <i>type, name, description, and flows</i>.<br />
64
- <br />
57
+ <div>
58
+ <p>
59
+ All Steps are defined by two main components, the <b>configuration</b> and the <b>handler</b> (disregarding of
60
+ the programming language).
61
+ <br />
62
+ <br />
63
+ Let's start with the configuration, the common config attributes are
64
+ <i>type, name, description, and flows</i>.<br />
65
+ <br />
66
+ </p>
65
67
  <ul>
66
68
  <li>
67
69
  The <b>type</b> attribute is important since it declares the type of Step primitive
@@ -74,7 +76,7 @@ export const steps: TutorialStep[] = [
74
76
  observability tools.
75
77
  </li>
76
78
  </ul>
77
- </p>
79
+ </div>
78
80
  ),
79
81
  before: [
80
82
  { type: 'click', selector: workbenchXPath.flows.previewButton('apitrigger') },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "motia",
3
3
  "description": "A Modern Unified Backend Framework for APIs, Events and Agents",
4
- "version": "0.5.11-beta.120-598191",
4
+ "version": "0.5.11-beta.120-167191",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -43,9 +43,9 @@
43
43
  "inquirer": "^8.2.5",
44
44
  "table": "^6.9.0",
45
45
  "ts-node": "^10.9.2",
46
- "@motiadev/core": "0.5.11-beta.120-598191",
47
- "@motiadev/stream-client-node": "0.5.11-beta.120-598191",
48
- "@motiadev/workbench": "0.5.11-beta.120-598191"
46
+ "@motiadev/core": "0.5.11-beta.120-167191",
47
+ "@motiadev/stream-client-node": "0.5.11-beta.120-167191",
48
+ "@motiadev/workbench": "0.5.11-beta.120-167191"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@amplitude/analytics-types": "^2.9.2",