motia 0.5.12-beta.120 → 0.6.0-beta.120
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 +21 -0
- package/dist/cjs/create/index.d.ts +1 -0
- package/dist/cjs/create/index.js +21 -27
- package/dist/cjs/create/setup-template.d.ts +2 -0
- package/dist/cjs/create/setup-template.js +13 -0
- package/dist/cjs/create/setup-tutorial-flow.d.ts +6 -0
- package/dist/cjs/create/setup-tutorial-flow.js +30 -0
- package/dist/cjs/create/templates/basic-tutorial/01-api.step.ts.txt +58 -0
- package/dist/cjs/create/templates/basic-tutorial/02-process-food-order.step.ts.txt +30 -0
- package/dist/cjs/create/templates/basic-tutorial/03-state-audit-cron.step.ts.txt +42 -0
- package/dist/cjs/create/templates/basic-tutorial/04_new_order_notifications.step.py.txt +27 -0
- package/dist/cjs/create/templates/basic-tutorial/motia-workbench.json +28 -0
- package/dist/cjs/create/templates/basic-tutorial/services/pet-store.ts.txt +32 -0
- package/dist/cjs/create/templates/generate.d.ts +1 -1
- package/dist/cjs/create/templates/generate.js +25 -5
- package/dist/cjs/create/templates/generate.ts +33 -6
- package/dist/cjs/create/templates/index.js +1 -0
- package/dist/cjs/create/templates/index.ts +1 -0
- package/dist/cjs/create/utils.d.ts +2 -0
- package/dist/cjs/create/utils.js +21 -0
- package/dist/cjs/docker/utils/print-intro.js +0 -1
- package/dist/esm/cli.js +18 -0
- package/dist/esm/create/index.d.ts +1 -0
- package/dist/esm/create/index.js +11 -17
- package/dist/esm/create/setup-template.d.ts +2 -0
- package/dist/esm/create/setup-template.js +9 -0
- package/dist/esm/create/setup-tutorial-flow.d.ts +6 -0
- package/dist/esm/create/setup-tutorial-flow.js +23 -0
- package/dist/esm/create/templates/basic-tutorial/01-api.step.ts.txt +58 -0
- package/dist/esm/create/templates/basic-tutorial/02-process-food-order.step.ts.txt +30 -0
- package/dist/esm/create/templates/basic-tutorial/03-state-audit-cron.step.ts.txt +42 -0
- package/dist/esm/create/templates/basic-tutorial/04_new_order_notifications.step.py.txt +27 -0
- package/dist/esm/create/templates/basic-tutorial/motia-workbench.json +28 -0
- package/dist/esm/create/templates/basic-tutorial/services/pet-store.ts.txt +32 -0
- package/dist/esm/create/templates/generate.d.ts +1 -1
- package/dist/esm/create/templates/generate.js +26 -6
- package/dist/esm/create/templates/generate.ts +33 -6
- package/dist/esm/create/templates/index.js +1 -0
- package/dist/esm/create/templates/index.ts +1 -0
- package/dist/esm/create/utils.d.ts +2 -0
- package/dist/esm/create/utils.js +13 -0
- package/dist/esm/docker/utils/print-intro.js +0 -1
- package/dist/types/create/index.d.ts +1 -0
- package/dist/types/create/setup-template.d.ts +2 -0
- package/dist/types/create/setup-tutorial-flow.d.ts +6 -0
- package/dist/types/create/templates/generate.d.ts +1 -1
- package/dist/types/create/utils.d.ts +2 -0
- package/package.json +4 -4
- /package/dist/cjs/create/templates/default/{steps/00-noop.step.ts.txt → 00-noop.step.ts.txt} +0 -0
- /package/dist/cjs/create/templates/default/{steps/00-noop.step.tsx.txt → 00-noop.step.tsx.txt} +0 -0
- /package/dist/cjs/create/templates/default/{steps/01-api.step.ts.txt → 01-api.step.ts.txt} +0 -0
- /package/dist/cjs/create/templates/default/{steps/02-test-state.step.ts.txt → 02-test-state.step.ts.txt} +0 -0
- /package/dist/cjs/create/templates/default/{steps/03-check-state-change.step.ts.txt → 03-check-state-change.step.ts.txt} +0 -0
- /package/dist/cjs/create/templates/python/{steps/00_noop_step.py.txt → 00_noop_step.py.txt} +0 -0
- /package/dist/cjs/create/templates/python/{steps/00_noop_step.tsx.txt → 00_noop_step.tsx.txt} +0 -0
- /package/dist/cjs/create/templates/python/{steps/01_api_step.py.txt → 01_api_step.py.txt} +0 -0
- /package/dist/cjs/create/templates/python/{steps/02_test_state_step.py.txt → 02_test_state_step.py.txt} +0 -0
- /package/dist/cjs/create/templates/python/{steps/03_check_state_change_step.py.txt → 03_check_state_change_step.py.txt} +0 -0
- /package/dist/esm/create/templates/default/{steps/00-noop.step.ts.txt → 00-noop.step.ts.txt} +0 -0
- /package/dist/esm/create/templates/default/{steps/00-noop.step.tsx.txt → 00-noop.step.tsx.txt} +0 -0
- /package/dist/esm/create/templates/default/{steps/01-api.step.ts.txt → 01-api.step.ts.txt} +0 -0
- /package/dist/esm/create/templates/default/{steps/02-test-state.step.ts.txt → 02-test-state.step.ts.txt} +0 -0
- /package/dist/esm/create/templates/default/{steps/03-check-state-change.step.ts.txt → 03-check-state-change.step.ts.txt} +0 -0
- /package/dist/esm/create/templates/python/{steps/00_noop_step.py.txt → 00_noop_step.py.txt} +0 -0
- /package/dist/esm/create/templates/python/{steps/00_noop_step.tsx.txt → 00_noop_step.tsx.txt} +0 -0
- /package/dist/esm/create/templates/python/{steps/01_api_step.py.txt → 01_api_step.py.txt} +0 -0
- /package/dist/esm/create/templates/python/{steps/02_test_state_step.py.txt → 02_test_state_step.py.txt} +0 -0
- /package/dist/esm/create/templates/python/{steps/03_check_state_change_step.py.txt → 03_check_state_change_step.py.txt} +0 -0
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 {
|
|
@@ -143,6 +156,14 @@ generate
|
|
|
143
156
|
stepFilePath: arg.dir,
|
|
144
157
|
});
|
|
145
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
|
+
}));
|
|
146
167
|
const docker = commander_1.program.command('docker').description('Motia docker commands');
|
|
147
168
|
docker
|
|
148
169
|
.command('setup')
|
package/dist/cjs/create/index.js
CHANGED
|
@@ -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,24 +173,28 @@ 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
180
|
const stepsDir = path_1.default.join(rootDir, 'steps');
|
|
191
|
-
if (!checkIfDirectoryExists(stepsDir)) {
|
|
181
|
+
if (!(0, utils_1.checkIfDirectoryExists)(stepsDir)) {
|
|
192
182
|
fs_1.default.mkdirSync(stepsDir);
|
|
193
183
|
context.log('steps-directory-created', (message) => message.tag('success').append('Folder').append('steps', 'cyan').append('has been created.'));
|
|
194
184
|
}
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
185
|
+
if (!(0, utils_1.checkIfDirectoryExists)(path_1.default.join(stepsDir, 'basic-tutorial'))) {
|
|
186
|
+
fs_1.default.mkdirSync(path_1.default.join(stepsDir, 'basic-tutorial'));
|
|
187
|
+
}
|
|
188
|
+
await (0, setup_template_1.setupTemplate)('basic-tutorial', stepsDir, context);
|
|
189
|
+
if (template) {
|
|
190
|
+
if (!(0, utils_1.checkIfDirectoryExists)(path_1.default.join(stepsDir, template))) {
|
|
191
|
+
fs_1.default.mkdirSync(path_1.default.join(stepsDir, template));
|
|
192
|
+
}
|
|
193
|
+
await (0, setup_template_1.setupTemplate)(template, stepsDir, context);
|
|
199
194
|
}
|
|
200
|
-
await templates_1.templates[template](rootDir, context);
|
|
201
195
|
const packageManager = await installNodeDependencies(rootDir, context);
|
|
202
196
|
if (template === 'python') {
|
|
203
|
-
if (!checkIfFileExists(rootDir, 'requirements.txt')) {
|
|
197
|
+
if (!(0, utils_1.checkIfFileExists)(rootDir, 'requirements.txt')) {
|
|
204
198
|
const requirementsContent = [
|
|
205
199
|
// TODO: motia PyPi package
|
|
206
200
|
// Add other Python dependencies as needed
|
|
@@ -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;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTutorialFlow = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const setup_template_1 = require("./setup-template");
|
|
11
|
+
const createTutorialFlow = async ({ context }) => {
|
|
12
|
+
const rootDir = process.cwd();
|
|
13
|
+
if (!(0, utils_1.checkIfFileExists)(rootDir, 'motia-workbench.json')) {
|
|
14
|
+
context.log('invalid-project', (message) => message
|
|
15
|
+
.tag('failed')
|
|
16
|
+
.append('In order to setup the Motia tutorial you need to be in a valid Motia project, motia-workbench.json not found, if this is not a Motia project you can create one using the motia create cli command.'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const stepsDir = path_1.default.join(rootDir, 'steps');
|
|
20
|
+
if (!(0, utils_1.checkIfDirectoryExists)(stepsDir)) {
|
|
21
|
+
fs_1.default.mkdirSync(stepsDir);
|
|
22
|
+
context.log('steps-directory-created', (message) => message.tag('success').append('Folder').append('steps', 'cyan').append('has been created.'));
|
|
23
|
+
}
|
|
24
|
+
if (!(0, utils_1.checkIfDirectoryExists)(path_1.default.join(stepsDir, 'basic-tutorial'))) {
|
|
25
|
+
fs_1.default.mkdirSync(path_1.default.join(stepsDir, 'basic-tutorial'));
|
|
26
|
+
}
|
|
27
|
+
await (0, setup_template_1.setupTemplate)('basic-tutorial', stepsDir, context);
|
|
28
|
+
context.log('tutorial-flow-setup-completed', (message) => message.tag('success').append('Tutorial flow setup completed, you can now start the tutorial from Workbench.'));
|
|
29
|
+
};
|
|
30
|
+
exports.createTutorialFlow = createTutorialFlow;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ApiRouteConfig, Handlers } from 'motia'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { petStoreService } from './services/pet-store'
|
|
4
|
+
|
|
5
|
+
export const config: ApiRouteConfig = {
|
|
6
|
+
type: 'api',
|
|
7
|
+
name: 'ApiTrigger',
|
|
8
|
+
description: 'basic-tutorial api trigger',
|
|
9
|
+
flows: ['basic-tutorial'],
|
|
10
|
+
|
|
11
|
+
method: 'POST',
|
|
12
|
+
path: '/basic-tutorial',
|
|
13
|
+
bodySchema: z.object({
|
|
14
|
+
pet: z.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
photoUrl: z.string(),
|
|
17
|
+
}),
|
|
18
|
+
foodOrder: z
|
|
19
|
+
.object({
|
|
20
|
+
id: z.string(),
|
|
21
|
+
quantity: z.number(),
|
|
22
|
+
})
|
|
23
|
+
.optional(),
|
|
24
|
+
}),
|
|
25
|
+
responseSchema: {
|
|
26
|
+
200: z.object({
|
|
27
|
+
message: z.string(),
|
|
28
|
+
traceId: z.string(),
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
emits: ['process-food-order'],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const handler: Handlers['ApiTrigger'] = async (req, { logger, emit, traceId }) => {
|
|
35
|
+
logger.info('Step 01 – Processing API Step', { body: req.body })
|
|
36
|
+
|
|
37
|
+
const { pet, foodOrder } = req.body
|
|
38
|
+
|
|
39
|
+
const newPetRecord = await petStoreService.createPet(pet)
|
|
40
|
+
|
|
41
|
+
if (foodOrder) {
|
|
42
|
+
await emit({
|
|
43
|
+
topic: 'process-food-order',
|
|
44
|
+
data: {
|
|
45
|
+
...foodOrder,
|
|
46
|
+
petId: newPetRecord.id,
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
status: 200,
|
|
53
|
+
body: {
|
|
54
|
+
traceId,
|
|
55
|
+
message: 'Your pet has been registered and your order is being processed',
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EventConfig, Handlers } from 'motia'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { petStoreService } from './services/pet-store'
|
|
4
|
+
|
|
5
|
+
export const config: EventConfig = {
|
|
6
|
+
type: 'event',
|
|
7
|
+
name: 'ProcessFoodOrder',
|
|
8
|
+
description: 'basic-tutorial event step, demonstrates how to consume an event from a topic and persist data in state',
|
|
9
|
+
flows: ['basic-tutorial'],
|
|
10
|
+
subscribes: ['process-food-order'],
|
|
11
|
+
emits: ['new-order-notification'],
|
|
12
|
+
input: z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
quantity: z.number(),
|
|
15
|
+
petId: z.number(),
|
|
16
|
+
}),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const handler: Handlers['ProcessFoodOrder'] = async (input, { traceId, logger, state, emit }) => {
|
|
20
|
+
logger.info('Step 02 – Process food order', { input, traceId })
|
|
21
|
+
|
|
22
|
+
const order = await petStoreService.createOrder(input)
|
|
23
|
+
|
|
24
|
+
await state.set<string>('orders', order.id, order)
|
|
25
|
+
|
|
26
|
+
await emit({
|
|
27
|
+
topic: 'new-order-notification',
|
|
28
|
+
data: { order_id: order.id },
|
|
29
|
+
})
|
|
30
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CronConfig, Handlers } from 'motia'
|
|
2
|
+
|
|
3
|
+
export const config: CronConfig = {
|
|
4
|
+
type: 'cron' as const,
|
|
5
|
+
name: 'StateAuditJob',
|
|
6
|
+
description: 'Runs every minute and emits a timestamp',
|
|
7
|
+
cron: '*/5 * * * *', // run every hour at minute 0
|
|
8
|
+
emits: ['order-audit-error', 'order-audit-warning'],
|
|
9
|
+
flows: ['basic-tutorial'],
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const handler: Handlers['StateAuditJob'] = async ({ state, emit }) => {
|
|
13
|
+
const stateValue = await state.getGroup<{
|
|
14
|
+
id: number
|
|
15
|
+
petId: number
|
|
16
|
+
quantity: number
|
|
17
|
+
shipDate: string
|
|
18
|
+
status: string
|
|
19
|
+
complete: boolean
|
|
20
|
+
}>('orders')
|
|
21
|
+
|
|
22
|
+
if (!Array.isArray(stateValue)) {
|
|
23
|
+
await emit({
|
|
24
|
+
topic: 'state-audit-error',
|
|
25
|
+
data: { message: 'State value is not an array' },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const item of stateValue) {
|
|
32
|
+
// check if current date is after item.shipDate
|
|
33
|
+
const currentDate = new Date()
|
|
34
|
+
const shipDate = new Date(item.shipDate)
|
|
35
|
+
if (!item.complete && currentDate > shipDate) {
|
|
36
|
+
await emit({
|
|
37
|
+
topic: 'order-audit-warning',
|
|
38
|
+
data: { message: 'Order is not complete and ship date is past' },
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
class NewOrderNotificationInput(BaseModel):
|
|
4
|
+
order_id: str = Field(description="pet store order id")
|
|
5
|
+
|
|
6
|
+
config = {
|
|
7
|
+
"type": "event",
|
|
8
|
+
"name": "NewOrderNotifications",
|
|
9
|
+
"description": "Checks a state change using python",
|
|
10
|
+
"subscribes": ["new-order-notification"],
|
|
11
|
+
"emits": [],
|
|
12
|
+
"flows": ["basic-tutorial"],
|
|
13
|
+
"input": NewOrderNotificationInput.model_json_schema(),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async def handler(input, ctx):
|
|
17
|
+
ctx.logger.info('Processing NewOrderNotifications', input)
|
|
18
|
+
ctx.logger.info('[NewOrderNotifications] order id', input.get('order_id'))
|
|
19
|
+
|
|
20
|
+
order = await ctx.state.get('orders', input.get('order_id'))
|
|
21
|
+
|
|
22
|
+
# This represents a call to some sort of notification service ot indicate that a new order has been placed
|
|
23
|
+
ctx.logger.info('New order notification sent using Python: ', {
|
|
24
|
+
'order_id': input.get('order_id'),
|
|
25
|
+
'order': order,
|
|
26
|
+
'trace_id': ctx.trace_id
|
|
27
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "basic-tutorial",
|
|
4
|
+
"config": {
|
|
5
|
+
"steps/basic-tutorial/04_new_order_notifications.step.py": {
|
|
6
|
+
"x": 668,
|
|
7
|
+
"y": 266,
|
|
8
|
+
"targetHandlePosition": "left"
|
|
9
|
+
},
|
|
10
|
+
"steps/basic-tutorial/03-state-audit-cron.step.ts": {
|
|
11
|
+
"x": 224,
|
|
12
|
+
"y": 520
|
|
13
|
+
},
|
|
14
|
+
"steps/basic-tutorial/02-process-food-order.step.ts": {
|
|
15
|
+
"x": 220,
|
|
16
|
+
"y": 242,
|
|
17
|
+
"sourceHandlePosition": "right",
|
|
18
|
+
"targetHandlePosition": "left"
|
|
19
|
+
},
|
|
20
|
+
"steps/basic-tutorial/01-api.step.ts": {
|
|
21
|
+
"x": -243,
|
|
22
|
+
"y": 199,
|
|
23
|
+
"sourceHandlePosition": "right",
|
|
24
|
+
"targetHandlePosition": "left"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const petStoreService = {
|
|
2
|
+
createPet: async (pet: { name: string; photoUrl: string }) => {
|
|
3
|
+
const response = await fetch('https://petstore.swagger.io/v2/pet', {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
body: JSON.stringify({
|
|
6
|
+
name: pet.name,
|
|
7
|
+
photoUrls: [pet.photoUrl],
|
|
8
|
+
status: 'available',
|
|
9
|
+
}),
|
|
10
|
+
headers: {
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
return response.json()
|
|
15
|
+
},
|
|
16
|
+
createOrder: async (order: { id: string; quantity: number; petId: number }) => {
|
|
17
|
+
const response = await fetch('https://petstore.swagger.io/v2/store/order', {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
id: order.id,
|
|
21
|
+
quantity: order.quantity,
|
|
22
|
+
petId: order.petId,
|
|
23
|
+
shipDate: new Date().toISOString(),
|
|
24
|
+
status: 'placed',
|
|
25
|
+
}),
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
return response.json()
|
|
31
|
+
},
|
|
32
|
+
}
|
|
@@ -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: (
|
|
3
|
+
export declare const generateTemplateSteps: (templateFolder: string) => Generator;
|
|
@@ -37,20 +37,40 @@ 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 = (
|
|
40
|
+
const generateTemplateSteps = (templateFolder) => {
|
|
41
41
|
return async (rootDir, context) => {
|
|
42
|
-
const templatePath = path.join(__dirname,
|
|
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
|
-
if ((0, fs_1.statSync)(filePath).isDirectory()) {
|
|
47
|
+
if ((0, fs_1.statSync)(filePath).isDirectory() && !filePath.match(/services|utils|lib/)) {
|
|
48
48
|
// ignore folders
|
|
49
49
|
continue;
|
|
50
50
|
}
|
|
51
|
+
if ((0, fs_1.statSync)(filePath).isDirectory()) {
|
|
52
|
+
const folderPath = path.basename(filePath);
|
|
53
|
+
(0, fs_1.mkdirSync)(path.join(rootDir, templateFolder, folderPath));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
51
56
|
const sanitizedFileName = fileName.replace('.txt', '');
|
|
52
|
-
const
|
|
53
|
-
const
|
|
57
|
+
const isWorkbenchConfig = fileName.match('motia-workbench.json');
|
|
58
|
+
const generateFilePath = path.join(...(isWorkbenchConfig
|
|
59
|
+
? [rootDir.match(/steps/) ? path.join(rootDir, '..') : rootDir, sanitizedFileName]
|
|
60
|
+
: [rootDir, templateFolder, sanitizedFileName]));
|
|
61
|
+
let content = await fs_1.promises.readFile(filePath, 'utf8');
|
|
62
|
+
// Make sure statSync doesn't break the execution if the file doesn't exist
|
|
63
|
+
try {
|
|
64
|
+
if (isWorkbenchConfig && (0, fs_1.statSync)(generateFilePath).isFile()) {
|
|
65
|
+
const existingWorkbenchConfig = await fs_1.promises.readFile(generateFilePath, 'utf8');
|
|
66
|
+
const workbenchContent = JSON.parse(content);
|
|
67
|
+
content = JSON.stringify([...JSON.parse(existingWorkbenchConfig), ...workbenchContent], null, 2);
|
|
68
|
+
context.log('workbench-config-updated', (message) => message.tag('success').append('Workbench config').append('has been updated.'));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
void 0;
|
|
73
|
+
}
|
|
54
74
|
await fs_1.promises.writeFile(generateFilePath, content, 'utf8');
|
|
55
75
|
context.log(sanitizedFileName, (message) => {
|
|
56
76
|
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 = (
|
|
8
|
+
export const generateTemplateSteps = (templateFolder: string): Generator => {
|
|
9
9
|
return async (rootDir: string, context: CliContext): Promise<void> => {
|
|
10
|
-
const templatePath = path.join(__dirname,
|
|
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
16
|
|
|
17
|
-
if (statSync(filePath).isDirectory()) {
|
|
17
|
+
if (statSync(filePath).isDirectory() && !filePath.match(/services|utils|lib/)) {
|
|
18
18
|
// ignore folders
|
|
19
19
|
continue
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
if (statSync(filePath).isDirectory()) {
|
|
23
|
+
const folderPath = path.basename(filePath)
|
|
24
|
+
mkdirSync(path.join(rootDir, templateFolder, folderPath))
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
const sanitizedFileName = fileName.replace('.txt', '')
|
|
23
|
-
const
|
|
24
|
-
const
|
|
29
|
+
const isWorkbenchConfig = fileName.match('motia-workbench.json')
|
|
30
|
+
const generateFilePath = path.join(
|
|
31
|
+
...(isWorkbenchConfig
|
|
32
|
+
? [rootDir.match(/steps/) ? path.join(rootDir, '..') : rootDir, sanitizedFileName]
|
|
33
|
+
: [rootDir, templateFolder, sanitizedFileName]),
|
|
34
|
+
)
|
|
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) => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkIfDirectoryExists = exports.checkIfFileExists = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const checkIfFileExists = (dir, fileName) => {
|
|
10
|
+
return fs_1.default.existsSync(path_1.default.join(dir, fileName));
|
|
11
|
+
};
|
|
12
|
+
exports.checkIfFileExists = checkIfFileExists;
|
|
13
|
+
const checkIfDirectoryExists = (dir) => {
|
|
14
|
+
try {
|
|
15
|
+
return fs_1.default.statSync(dir).isDirectory();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.checkIfDirectoryExists = checkIfDirectoryExists;
|