motia 0.5.11-beta.120-742949 → 0.5.11-beta.120-110250
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.
- package/dist/cjs/cli.js +0 -8
- package/dist/cjs/cloud/build/builders/python/index.js +2 -21
- package/dist/cjs/cloud/build/builders/python/python-builder.py +28 -120
- package/dist/cjs/create/templates/default/motia-workbench.json +15 -16
- package/dist/cjs/create/templates/default/services/pet-store.ts.txt +24 -0
- package/dist/cjs/create/templates/default/services/types.ts.txt +21 -0
- package/dist/cjs/create/templates/default/steps/01-api.step.ts-features.json.txt +67 -0
- package/dist/cjs/create/templates/{basic-tutorial → default/steps}/01-api.step.ts.txt +5 -13
- package/dist/cjs/create/templates/default/steps/02-process-food-order.step.ts-features.json.txt +67 -0
- package/dist/{esm/create/templates/basic-tutorial → cjs/create/templates/default/steps}/02-process-food-order.step.ts.txt +21 -7
- package/dist/cjs/create/templates/default/steps/03-state-audit-cron.step.ts-features.json.txt +26 -0
- package/dist/cjs/create/templates/default/steps/03-state-audit-cron.step.ts.txt +51 -0
- package/dist/cjs/create/templates/default/steps/04-notification.step.ts.txt +35 -0
- package/dist/cjs/create/templates/default/tutorial.tsx.txt +643 -0
- package/dist/cjs/create/templates/generate.js +0 -4
- package/dist/cjs/create/templates/generate.ts +0 -5
- package/dist/esm/cli.js +0 -8
- package/dist/esm/cloud/build/builders/python/index.js +2 -21
- package/dist/esm/cloud/build/builders/python/python-builder.py +28 -120
- package/dist/esm/create/templates/default/motia-workbench.json +15 -16
- package/dist/esm/create/templates/default/services/pet-store.ts.txt +24 -0
- package/dist/esm/create/templates/default/services/types.ts.txt +21 -0
- package/dist/esm/create/templates/default/steps/01-api.step.ts-features.json.txt +67 -0
- package/dist/esm/create/templates/{basic-tutorial → default/steps}/01-api.step.ts.txt +5 -13
- package/dist/esm/create/templates/default/steps/02-process-food-order.step.ts-features.json.txt +67 -0
- package/dist/{cjs/create/templates/basic-tutorial → esm/create/templates/default/steps}/02-process-food-order.step.ts.txt +21 -7
- package/dist/esm/create/templates/default/steps/03-state-audit-cron.step.ts-features.json.txt +26 -0
- package/dist/esm/create/templates/default/steps/03-state-audit-cron.step.ts.txt +51 -0
- package/dist/esm/create/templates/default/steps/04-notification.step.ts.txt +35 -0
- package/dist/esm/create/templates/default/tutorial.tsx.txt +643 -0
- package/dist/esm/create/templates/generate.js +0 -4
- package/dist/esm/create/templates/generate.ts +0 -5
- package/package.json +4 -4
- package/dist/cjs/create/setup-tutorial-flow.d.ts +0 -6
- package/dist/cjs/create/setup-tutorial-flow.js +0 -30
- package/dist/cjs/create/templates/basic-tutorial/03-state-audit-cron.step.ts.txt +0 -42
- package/dist/cjs/create/templates/basic-tutorial/04_new_order_notifications.step.py.txt +0 -27
- package/dist/cjs/create/templates/basic-tutorial/motia-workbench.json +0 -28
- package/dist/cjs/create/templates/basic-tutorial/services/pet-store.ts.txt +0 -32
- package/dist/cjs/create/templates/default/00-noop.step.ts.txt +0 -33
- package/dist/cjs/create/templates/default/00-noop.step.tsx.txt +0 -18
- package/dist/cjs/create/templates/default/01-api.step.ts.txt +0 -70
- package/dist/cjs/create/templates/default/02-test-state.step.ts.txt +0 -53
- package/dist/cjs/create/templates/default/03-check-state-change.step.ts.txt +0 -54
- package/dist/esm/create/setup-tutorial-flow.d.ts +0 -6
- package/dist/esm/create/setup-tutorial-flow.js +0 -23
- package/dist/esm/create/templates/basic-tutorial/03-state-audit-cron.step.ts.txt +0 -42
- package/dist/esm/create/templates/basic-tutorial/04_new_order_notifications.step.py.txt +0 -27
- package/dist/esm/create/templates/basic-tutorial/motia-workbench.json +0 -28
- package/dist/esm/create/templates/basic-tutorial/services/pet-store.ts.txt +0 -32
- package/dist/esm/create/templates/default/00-noop.step.ts.txt +0 -33
- package/dist/esm/create/templates/default/00-noop.step.tsx.txt +0 -18
- package/dist/esm/create/templates/default/01-api.step.ts.txt +0 -70
- package/dist/esm/create/templates/default/02-test-state.step.ts.txt +0 -53
- package/dist/esm/create/templates/default/03-check-state-change.step.ts.txt +0 -54
- package/dist/types/create/setup-tutorial-flow.d.ts +0 -6
package/dist/cjs/cli.js
CHANGED
|
@@ -156,14 +156,6 @@ generate
|
|
|
156
156
|
stepFilePath: arg.dir,
|
|
157
157
|
});
|
|
158
158
|
});
|
|
159
|
-
generate
|
|
160
|
-
.command('tutorial-flow')
|
|
161
|
-
.description('Download the tutorial flow into an existing motia project')
|
|
162
|
-
.action((0, config_utils_1.handler)(async (_, context) => {
|
|
163
|
-
const { createTutorialFlow } = require('./create/setup-tutorial-flow');
|
|
164
|
-
await createTutorialFlow({ context });
|
|
165
|
-
process.exit(0);
|
|
166
|
-
}));
|
|
167
159
|
const docker = commander_1.program.command('docker').description('Motia docker commands');
|
|
168
160
|
docker
|
|
169
161
|
.command('setup')
|
|
@@ -23,21 +23,12 @@ class PythonBuilder {
|
|
|
23
23
|
const normalizedEntrypointPath = entrypointPath.replace(/[.]step.py$/, '_step.py');
|
|
24
24
|
const sitePackagesDir = `${process.env.PYTHON_SITE_PACKAGES}-lambda`;
|
|
25
25
|
// Get Python builder response
|
|
26
|
-
const { packages
|
|
26
|
+
const { packages } = await this.getPythonBuilderData(step);
|
|
27
27
|
// Add main file to archive
|
|
28
28
|
if (!fs_1.default.existsSync(step.filePath)) {
|
|
29
29
|
throw new Error(`Source file not found: ${step.filePath}`);
|
|
30
30
|
}
|
|
31
31
|
archive.append(fs_1.default.createReadStream(step.filePath), path_1.default.relative(this.builder.projectDir, normalizedEntrypointPath));
|
|
32
|
-
// Add local Python files to archive
|
|
33
|
-
if (local_files && local_files.length > 0) {
|
|
34
|
-
local_files.forEach((localFile) => {
|
|
35
|
-
const fullPath = path_1.default.join(this.builder.projectDir, localFile);
|
|
36
|
-
if (fs_1.default.existsSync(fullPath)) {
|
|
37
|
-
archive.append(fs_1.default.createReadStream(fullPath), localFile);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
32
|
await Promise.all(packages.map(async (packageName) => (0, add_package_to_archive_1.addPackageToArchive)(archive, sitePackagesDir, packageName)));
|
|
42
33
|
return normalizedEntrypointPath;
|
|
43
34
|
}
|
|
@@ -51,7 +42,7 @@ class PythonBuilder {
|
|
|
51
42
|
fs_1.default.mkdirSync(path_1.default.dirname(outfile), { recursive: true });
|
|
52
43
|
this.listener.onBuildStart(step);
|
|
53
44
|
// Get Python builder response
|
|
54
|
-
const { packages
|
|
45
|
+
const { packages } = await this.getPythonBuilderData(step);
|
|
55
46
|
const stepArchiver = new archiver_1.Archiver(outfile);
|
|
56
47
|
const stepPath = await this.buildStep(step, stepArchiver);
|
|
57
48
|
// Add main file to archive
|
|
@@ -62,16 +53,6 @@ class PythonBuilder {
|
|
|
62
53
|
// Add all imported files to archive
|
|
63
54
|
this.listener.onBuildProgress(step, 'Adding imported files to archive...');
|
|
64
55
|
const sitePackagesDir = `${process.env.PYTHON_SITE_PACKAGES}-lambda`;
|
|
65
|
-
// Add local Python files to archive
|
|
66
|
-
if (local_files && local_files.length > 0) {
|
|
67
|
-
local_files.forEach((localFile) => {
|
|
68
|
-
const fullPath = path_1.default.join(this.builder.projectDir, localFile);
|
|
69
|
-
if (fs_1.default.existsSync(fullPath)) {
|
|
70
|
-
stepArchiver.append(fs_1.default.createReadStream(fullPath), localFile);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
this.listener.onBuildProgress(step, `Added ${local_files.length} local Python files to archive`);
|
|
74
|
-
}
|
|
75
56
|
(0, include_static_files_1.includeStaticFiles)([step], this.builder, stepArchiver);
|
|
76
57
|
if (packages.length > 0) {
|
|
77
58
|
await Promise.all(packages.map(async (packageName) => (0, add_package_to_archive_1.addPackageToArchive)(stepArchiver, sitePackagesDir, packageName)));
|
|
@@ -82,23 +82,6 @@ def is_builtin_module(module_name: str) -> bool:
|
|
|
82
82
|
"""Check if a module is a Python built-in module."""
|
|
83
83
|
if module_name in _builtin_modules_cache:
|
|
84
84
|
return True
|
|
85
|
-
|
|
86
|
-
# First check if it's a known built-in module name
|
|
87
|
-
builtin_modules = {
|
|
88
|
-
'os', 'sys', 'json', 'math', 'random', 'datetime', 'time', 'urllib', 'http',
|
|
89
|
-
'pathlib', 're', 'collections', 'itertools', 'functools', 'operator', 'typing',
|
|
90
|
-
'io', 'csv', 'xml', 'html', 'email', 'base64', 'hashlib', 'hmac', 'uuid',
|
|
91
|
-
'pickle', 'sqlite3', 'logging', 'unittest', 'argparse', 'configparser',
|
|
92
|
-
'tempfile', 'shutil', 'glob', 'fnmatch', 'subprocess', 'threading', 'queue',
|
|
93
|
-
'multiprocessing', 'concurrent', 'asyncio', 'socket', 'ssl', 'gzip', 'zipfile',
|
|
94
|
-
'tarfile', 'zlib', 'bz2', 'lzma', 'struct', 'array', 'ctypes', 'mmap',
|
|
95
|
-
'weakref', 'gc', 'inspect', 'dis', 'ast', 'token', 'tokenize', 'keyword',
|
|
96
|
-
'builtins', '__main__', 'site', 'sysconfig', 'platform', 'warnings'
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if module_name in builtin_modules:
|
|
100
|
-
_builtin_modules_cache.add(module_name)
|
|
101
|
-
return True
|
|
102
85
|
|
|
103
86
|
try:
|
|
104
87
|
module = importlib.import_module(module_name)
|
|
@@ -117,8 +100,6 @@ def is_builtin_module(module_name: str) -> bool:
|
|
|
117
100
|
_builtin_modules_cache.add(module_name)
|
|
118
101
|
return is_builtin
|
|
119
102
|
except ImportError:
|
|
120
|
-
# If we can't import it, assume it's not a built-in module
|
|
121
|
-
# This handles local modules that aren't in the current Python path
|
|
122
103
|
return False
|
|
123
104
|
|
|
124
105
|
def get_direct_imports(file_path: str) -> Set[str]:
|
|
@@ -146,101 +127,6 @@ def get_direct_imports(file_path: str) -> Set[str]:
|
|
|
146
127
|
|
|
147
128
|
return direct_imports
|
|
148
129
|
|
|
149
|
-
def get_all_python_files(project_root: str) -> List[str]:
|
|
150
|
-
"""Get all Python files in the project."""
|
|
151
|
-
python_files = []
|
|
152
|
-
for root, dirs, files in os.walk(project_root):
|
|
153
|
-
# Skip common directories
|
|
154
|
-
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in
|
|
155
|
-
{'__pycache__', 'node_modules', 'dist', 'build', 'venv'}]
|
|
156
|
-
|
|
157
|
-
for file in files:
|
|
158
|
-
if file.endswith('.py') and not file.startswith('.'):
|
|
159
|
-
full_path = os.path.join(root, file)
|
|
160
|
-
relative_path = os.path.relpath(full_path, project_root)
|
|
161
|
-
python_files.append(relative_path)
|
|
162
|
-
|
|
163
|
-
return python_files
|
|
164
|
-
|
|
165
|
-
def get_imports_from_file(file_path: str) -> Set[str]:
|
|
166
|
-
"""Get all import module names from a Python file."""
|
|
167
|
-
imports = set()
|
|
168
|
-
|
|
169
|
-
try:
|
|
170
|
-
with open(file_path, 'r') as f:
|
|
171
|
-
content = f.read()
|
|
172
|
-
|
|
173
|
-
tree = ast.parse(content)
|
|
174
|
-
for node in ast.walk(tree):
|
|
175
|
-
if isinstance(node, ast.Import):
|
|
176
|
-
for name in node.names:
|
|
177
|
-
imports.add(name.name)
|
|
178
|
-
elif isinstance(node, ast.ImportFrom):
|
|
179
|
-
if node.module:
|
|
180
|
-
imports.add(node.module)
|
|
181
|
-
except Exception as e:
|
|
182
|
-
print(f"Warning: Could not parse imports from {file_path}: {str(e)}")
|
|
183
|
-
|
|
184
|
-
return imports
|
|
185
|
-
|
|
186
|
-
def get_local_files_for_entry(entry_file: str) -> List[str]:
|
|
187
|
-
"""Get local Python files that are imported by the entry file."""
|
|
188
|
-
# Find project root
|
|
189
|
-
project_root = os.path.dirname(entry_file)
|
|
190
|
-
while project_root != os.path.dirname(project_root):
|
|
191
|
-
if any(os.path.exists(os.path.join(project_root, f))
|
|
192
|
-
for f in ['package.json', 'requirements.txt']):
|
|
193
|
-
break
|
|
194
|
-
project_root = os.path.dirname(project_root)
|
|
195
|
-
|
|
196
|
-
# Get all Python files in the project
|
|
197
|
-
all_python_files = get_all_python_files(project_root)
|
|
198
|
-
|
|
199
|
-
# Get imports from the entry file
|
|
200
|
-
imports = get_imports_from_file(entry_file)
|
|
201
|
-
|
|
202
|
-
# Check which imports match local Python files
|
|
203
|
-
local_files = []
|
|
204
|
-
for import_name in imports:
|
|
205
|
-
for py_file in all_python_files:
|
|
206
|
-
# Convert file path to module name (e.g., 'utils/example.py' -> 'utils.example')
|
|
207
|
-
module_name = py_file.replace(os.sep, '.').replace('.py', '')
|
|
208
|
-
if import_name == module_name:
|
|
209
|
-
local_files.append(py_file)
|
|
210
|
-
|
|
211
|
-
return sorted(local_files)
|
|
212
|
-
|
|
213
|
-
def trace_imports(entry_file: str) -> List[str]:
|
|
214
|
-
"""Find all imported Python packages from entry file and its local imports."""
|
|
215
|
-
entry_file = os.path.abspath(entry_file)
|
|
216
|
-
|
|
217
|
-
# Get local files that are imported
|
|
218
|
-
local_files = get_local_files_for_entry(entry_file)
|
|
219
|
-
|
|
220
|
-
# Get project root
|
|
221
|
-
project_root = os.path.dirname(entry_file)
|
|
222
|
-
while project_root != os.path.dirname(project_root):
|
|
223
|
-
if any(os.path.exists(os.path.join(project_root, f))
|
|
224
|
-
for f in ['package.json', 'requirements.txt']):
|
|
225
|
-
break
|
|
226
|
-
project_root = os.path.dirname(project_root)
|
|
227
|
-
|
|
228
|
-
# Get imports from entry file and local files
|
|
229
|
-
all_packages = set()
|
|
230
|
-
processed_packages = set()
|
|
231
|
-
files_to_process = [entry_file] + [os.path.join(project_root, f) for f in local_files]
|
|
232
|
-
|
|
233
|
-
for python_file in files_to_process:
|
|
234
|
-
if os.path.exists(python_file):
|
|
235
|
-
direct_imports = get_direct_imports(python_file)
|
|
236
|
-
for package_name in direct_imports:
|
|
237
|
-
if is_valid_package_name(package_name) and not is_builtin_module(package_name):
|
|
238
|
-
all_packages.add(package_name)
|
|
239
|
-
# Get all dependencies including sub-dependencies
|
|
240
|
-
all_packages.update(get_package_dependencies(package_name, processed_packages))
|
|
241
|
-
|
|
242
|
-
return sorted(list(all_packages))
|
|
243
|
-
|
|
244
130
|
@lru_cache(maxsize=1024)
|
|
245
131
|
def is_optional_dependency(req: str) -> bool:
|
|
246
132
|
"""Check if a dependency is an optional dependency."""
|
|
@@ -289,6 +175,33 @@ def get_package_dependencies(package_name: str, processed: Set[str] = None) -> S
|
|
|
289
175
|
|
|
290
176
|
return all_dependencies
|
|
291
177
|
|
|
178
|
+
def trace_imports(entry_file: str) -> List[str]:
|
|
179
|
+
"""Find all imported Python packages and files starting from an entry file."""
|
|
180
|
+
entry_file = os.path.abspath(entry_file)
|
|
181
|
+
module_dir = os.path.dirname(entry_file)
|
|
182
|
+
|
|
183
|
+
if module_dir not in sys.path:
|
|
184
|
+
sys.path.insert(0, module_dir)
|
|
185
|
+
|
|
186
|
+
# Get direct imports from the entry file
|
|
187
|
+
direct_imports = get_direct_imports(entry_file)
|
|
188
|
+
|
|
189
|
+
# Initialize sets to track packages
|
|
190
|
+
all_packages = set()
|
|
191
|
+
processed_packages = set()
|
|
192
|
+
|
|
193
|
+
# Process each direct import and its dependencies
|
|
194
|
+
for package_name in direct_imports:
|
|
195
|
+
if is_valid_package_name(package_name):
|
|
196
|
+
all_packages.add(package_name)
|
|
197
|
+
# Get all dependencies including sub-dependencies
|
|
198
|
+
all_packages.update(get_package_dependencies(package_name, processed_packages))
|
|
199
|
+
|
|
200
|
+
# Filter out built-in packages
|
|
201
|
+
non_builtin_packages = {pkg for pkg in all_packages if not is_builtin_module(pkg)}
|
|
202
|
+
|
|
203
|
+
return sorted(list(non_builtin_packages))
|
|
204
|
+
|
|
292
205
|
def main() -> None:
|
|
293
206
|
"""Main entry point for the script."""
|
|
294
207
|
if len(sys.argv) != 2:
|
|
@@ -298,13 +211,8 @@ def main() -> None:
|
|
|
298
211
|
entry_file = sys.argv[1]
|
|
299
212
|
try:
|
|
300
213
|
packages = trace_imports(entry_file)
|
|
301
|
-
local_files = get_local_files_for_entry(entry_file)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
214
|
output = {
|
|
306
|
-
'packages': packages
|
|
307
|
-
'local_files': local_files
|
|
215
|
+
'packages': packages
|
|
308
216
|
}
|
|
309
217
|
bytes_message = (json.dumps(output) + '\n').encode('utf-8')
|
|
310
218
|
os.write(NODEIPCFD, bytes_message)
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
|
-
"id": "
|
|
3
|
+
"id": "basic-tutorial",
|
|
4
4
|
"config": {
|
|
5
|
-
"steps/
|
|
6
|
-
"x": -
|
|
7
|
-
"y":
|
|
5
|
+
"steps/basic-tutorial/03-state-audit-cron.step.ts": {
|
|
6
|
+
"x": -165,
|
|
7
|
+
"y": 217,
|
|
8
8
|
"sourceHandlePosition": "right"
|
|
9
9
|
},
|
|
10
|
-
"steps/
|
|
11
|
-
"x":
|
|
12
|
-
"y":
|
|
13
|
-
"sourceHandlePosition": "
|
|
10
|
+
"steps/basic-tutorial/02-process-food-order.step.ts": {
|
|
11
|
+
"x": 211,
|
|
12
|
+
"y": 17,
|
|
13
|
+
"sourceHandlePosition": "bottom",
|
|
14
14
|
"targetHandlePosition": "left"
|
|
15
15
|
},
|
|
16
|
-
"steps/
|
|
17
|
-
"x":
|
|
18
|
-
"y":
|
|
19
|
-
"sourceHandlePosition": "
|
|
16
|
+
"steps/basic-tutorial/01-api.step.ts": {
|
|
17
|
+
"x": -100,
|
|
18
|
+
"y": 3,
|
|
19
|
+
"sourceHandlePosition": "right",
|
|
20
20
|
"targetHandlePosition": "left"
|
|
21
21
|
},
|
|
22
|
-
"steps/
|
|
23
|
-
"x":
|
|
24
|
-
"y":
|
|
25
|
-
"targetHandlePosition": "top"
|
|
22
|
+
"steps/basic-tutorial/04-notification.step.ts": {
|
|
23
|
+
"x": 300,
|
|
24
|
+
"y": 264
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Order, Pet } from './types'
|
|
2
|
+
|
|
3
|
+
export const petStoreService = {
|
|
4
|
+
createPet: async (pet: Omit<Pet, 'id'>): Promise<Pet> => {
|
|
5
|
+
const response = await fetch('https://petstore.swagger.io/v2/pet', {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
body: JSON.stringify({
|
|
8
|
+
name: pet.name,
|
|
9
|
+
photoUrls: [pet.photoUrl],
|
|
10
|
+
status: 'available',
|
|
11
|
+
}),
|
|
12
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13
|
+
})
|
|
14
|
+
return response.json()
|
|
15
|
+
},
|
|
16
|
+
createOrder: async (order: Omit<Order, 'id'>): Promise<Order> => {
|
|
17
|
+
const response = await fetch('https://petstore.swagger.io/v2/store/order', {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: JSON.stringify(order),
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
})
|
|
22
|
+
return response.json()
|
|
23
|
+
},
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const petSchema = z.object({
|
|
4
|
+
id: z.number(),
|
|
5
|
+
name: z.string(),
|
|
6
|
+
photoUrl: z.string(),
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export const orderStatusSchema = z.enum(['placed', 'approved', 'delivered'])
|
|
10
|
+
|
|
11
|
+
export const orderSchema = z.object({
|
|
12
|
+
id: z.string(),
|
|
13
|
+
quantity: z.number(),
|
|
14
|
+
petId: z.number(),
|
|
15
|
+
shipDate: z.string(),
|
|
16
|
+
status: orderStatusSchema,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export type Pet = z.infer<typeof petSchema>
|
|
20
|
+
export type Order = z.infer<typeof orderSchema>
|
|
21
|
+
export type OrderStatus = z.infer<typeof orderStatusSchema>
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
"12-13"
|
|
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
|
+
"14-25"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "response-payload",
|
|
28
|
+
"title": "Response Payload",
|
|
29
|
+
"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.",
|
|
30
|
+
"lines": [
|
|
31
|
+
"26-28"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "event-driven-architecture",
|
|
36
|
+
"title": "Emits",
|
|
37
|
+
"description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
|
|
38
|
+
"lines": [
|
|
39
|
+
"29",
|
|
40
|
+
"39-46"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "handler",
|
|
45
|
+
"title": "Handler",
|
|
46
|
+
"description": "The handler is the function that will be executed when the step is triggered. This one receives the request body and emits events.",
|
|
47
|
+
"lines": [
|
|
48
|
+
"32-50"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "logger",
|
|
53
|
+
"title": "Logger",
|
|
54
|
+
"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.",
|
|
55
|
+
"lines": [
|
|
56
|
+
"33"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "http-response",
|
|
61
|
+
"title": "HTTP Response",
|
|
62
|
+
"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.",
|
|
63
|
+
"lines": [
|
|
64
|
+
"49"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApiRouteConfig, Handlers } from 'motia'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import { petStoreService } from '
|
|
3
|
+
import { petStoreService } from '../services/pet-store'
|
|
4
|
+
import { petSchema } from '../services/types'
|
|
4
5
|
|
|
5
6
|
export const config: ApiRouteConfig = {
|
|
6
7
|
type: 'api',
|
|
@@ -23,10 +24,7 @@ export const config: ApiRouteConfig = {
|
|
|
23
24
|
.optional(),
|
|
24
25
|
}),
|
|
25
26
|
responseSchema: {
|
|
26
|
-
200:
|
|
27
|
-
message: z.string(),
|
|
28
|
-
traceId: z.string(),
|
|
29
|
-
}),
|
|
27
|
+
200: petSchema,
|
|
30
28
|
},
|
|
31
29
|
emits: ['process-food-order'],
|
|
32
30
|
}
|
|
@@ -35,7 +33,6 @@ export const handler: Handlers['ApiTrigger'] = async (req, { logger, emit, trace
|
|
|
35
33
|
logger.info('Step 01 – Processing API Step', { body: req.body })
|
|
36
34
|
|
|
37
35
|
const { pet, foodOrder } = req.body
|
|
38
|
-
|
|
39
36
|
const newPetRecord = await petStoreService.createPet(pet)
|
|
40
37
|
|
|
41
38
|
if (foodOrder) {
|
|
@@ -43,16 +40,11 @@ export const handler: Handlers['ApiTrigger'] = async (req, { logger, emit, trace
|
|
|
43
40
|
topic: 'process-food-order',
|
|
44
41
|
data: {
|
|
45
42
|
...foodOrder,
|
|
43
|
+
email: 'test@test.com', // sample email
|
|
46
44
|
petId: newPetRecord.id,
|
|
47
45
|
},
|
|
48
46
|
})
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
return {
|
|
52
|
-
status: 200,
|
|
53
|
-
body: {
|
|
54
|
-
traceId,
|
|
55
|
-
message: 'Your pet has been registered and your order is being processed',
|
|
56
|
-
},
|
|
57
|
-
}
|
|
49
|
+
return { status: 200, body: newPetRecord }
|
|
58
50
|
}
|
package/dist/cjs/create/templates/default/steps/02-process-food-order.step.ts-features.json.txt
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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-17"
|
|
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
|
+
"6",
|
|
16
|
+
"10-11"
|
|
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
|
+
"12-16"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "event-emits",
|
|
29
|
+
"title": "Emits",
|
|
30
|
+
"description": "We can define the events that this step will emit, triggering other Motia Steps.",
|
|
31
|
+
"lines": [
|
|
32
|
+
"11"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "handler",
|
|
37
|
+
"title": "Handler",
|
|
38
|
+
"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.",
|
|
39
|
+
"lines": [
|
|
40
|
+
"19-44"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "state",
|
|
45
|
+
"title": "State Management",
|
|
46
|
+
"description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
|
|
47
|
+
"lines": [
|
|
48
|
+
"28"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "event-emission",
|
|
53
|
+
"title": "Event Emission",
|
|
54
|
+
"description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
|
|
55
|
+
"lines": [
|
|
56
|
+
"30-43"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "logger",
|
|
61
|
+
"title": "Logger",
|
|
62
|
+
"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.",
|
|
63
|
+
"lines": [
|
|
64
|
+
"20"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventConfig, Handlers } from 'motia'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import { petStoreService } from '
|
|
3
|
+
import { petStoreService } from '../services/pet-store'
|
|
4
4
|
|
|
5
5
|
export const config: EventConfig = {
|
|
6
6
|
type: 'event',
|
|
@@ -8,9 +8,9 @@ export const config: EventConfig = {
|
|
|
8
8
|
description: 'basic-tutorial event step, demonstrates how to consume an event from a topic and persist data in state',
|
|
9
9
|
flows: ['basic-tutorial'],
|
|
10
10
|
subscribes: ['process-food-order'],
|
|
11
|
-
emits: ['
|
|
11
|
+
emits: ['notification'],
|
|
12
12
|
input: z.object({
|
|
13
|
-
|
|
13
|
+
email: z.string(),
|
|
14
14
|
quantity: z.number(),
|
|
15
15
|
petId: z.number(),
|
|
16
16
|
}),
|
|
@@ -19,12 +19,26 @@ export const config: EventConfig = {
|
|
|
19
19
|
export const handler: Handlers['ProcessFoodOrder'] = async (input, { traceId, logger, state, emit }) => {
|
|
20
20
|
logger.info('Step 02 – Process food order', { input, traceId })
|
|
21
21
|
|
|
22
|
-
const order = await petStoreService.createOrder(
|
|
22
|
+
const order = await petStoreService.createOrder({
|
|
23
|
+
...input,
|
|
24
|
+
shipDate: new Date().toISOString(),
|
|
25
|
+
status: 'placed',
|
|
26
|
+
})
|
|
23
27
|
|
|
24
|
-
await state.set
|
|
28
|
+
await state.set('orders', order.id, order)
|
|
25
29
|
|
|
26
30
|
await emit({
|
|
27
|
-
topic: '
|
|
28
|
-
data: {
|
|
31
|
+
topic: 'notification',
|
|
32
|
+
data: {
|
|
33
|
+
email: input.email,
|
|
34
|
+
templateId: 'new-order',
|
|
35
|
+
templateData: {
|
|
36
|
+
status: order.status,
|
|
37
|
+
shipDate: order.shipDate,
|
|
38
|
+
id: order.id,
|
|
39
|
+
petId: order.petId,
|
|
40
|
+
quantity: order.quantity,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
29
43
|
})
|
|
30
44
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
"3-10"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "cron-configuration",
|
|
12
|
+
"title": "Cron Configuration",
|
|
13
|
+
"description": "Cron steps require a specific configuration structure with the 'type' field set to 'cron' and a valid cron expression.",
|
|
14
|
+
"lines": [
|
|
15
|
+
"4-5"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "handler",
|
|
20
|
+
"title": "Cron Step Handler",
|
|
21
|
+
"description": "The Cron step handler only receives one argument.",
|
|
22
|
+
"lines": [
|
|
23
|
+
"21-51"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CronConfig, Handlers } from 'motia'
|
|
2
|
+
|
|
3
|
+
export const config: CronConfig = {
|
|
4
|
+
type: 'cron',
|
|
5
|
+
cron: '*/5 * * * *', // run every 5 minutes
|
|
6
|
+
name: 'StateAuditJob',
|
|
7
|
+
description: 'Checks the state for orders that are not complete and have a ship date in the past',
|
|
8
|
+
emits: ['notification'],
|
|
9
|
+
flows: ['basic-tutorial'],
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Order = {
|
|
13
|
+
id: number
|
|
14
|
+
petId: number
|
|
15
|
+
quantity: number
|
|
16
|
+
shipDate: string
|
|
17
|
+
status: string
|
|
18
|
+
complete: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const handler: Handlers['StateAuditJob'] = async ({ logger, state, emit }) => {
|
|
22
|
+
const stateValue = await state.getGroup<Order>('orders')
|
|
23
|
+
|
|
24
|
+
for (const item of stateValue) {
|
|
25
|
+
// check if current date is after item.shipDate
|
|
26
|
+
const currentDate = new Date()
|
|
27
|
+
const shipDate = new Date(item.shipDate)
|
|
28
|
+
|
|
29
|
+
if (!item.complete && currentDate > shipDate) {
|
|
30
|
+
logger.warn('Order is not complete and ship date is past', {
|
|
31
|
+
orderId: item.id,
|
|
32
|
+
shipDate: item.shipDate,
|
|
33
|
+
complete: item.complete,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
await emit({
|
|
37
|
+
topic: 'notification',
|
|
38
|
+
data: {
|
|
39
|
+
email: 'test@test.com',
|
|
40
|
+
templateId: 'order-audit-warning',
|
|
41
|
+
templateData: {
|
|
42
|
+
orderId: item.id,
|
|
43
|
+
status: item.status,
|
|
44
|
+
shipDate: item.shipDate,
|
|
45
|
+
message: 'Order is not complete and ship date is past',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|