motia 0.15.4-beta.170-484948 → 0.15.4-beta.170-803755

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.
@@ -22,6 +22,21 @@ export default config({
22
22
  })
23
23
  ```
24
24
 
25
+ ## Creating Projects Without Embedded Redis
26
+
27
+ By default, Motia includes an embedded Redis binary for easy local development. However, if you prefer to use your own external Redis instance, you can use the `--skip-redis` flag when creating a new project:
28
+
29
+ ```bash
30
+ npx motia create my-app --skip-redis
31
+ ```
32
+
33
+ This flag:
34
+ - Skips the embedded Redis binary installation
35
+ - Creates a `motia.config.ts` pre-configured for external Redis connection
36
+ - Requires you to provide your own Redis instance before running Motia
37
+
38
+ When using `--skip-redis`, you'll need to ensure Redis is running and properly configured in your `motia.config.ts` file (see Redis Configuration section below).
39
+
25
40
  ## Type Definitions
26
41
 
27
42
  ```typescript
@@ -51,6 +66,12 @@ export type Config = {
51
66
  * Use this to secure real-time stream subscriptions.
52
67
  */
53
68
  streamAuth?: StreamAuthConfig
69
+
70
+ /**
71
+ * Optional: Redis configuration.
72
+ * Configure Redis connection or use the built-in in-memory Redis server.
73
+ */
74
+ redis?: RedisConfig
54
75
  }
55
76
 
56
77
  export type AdapterConfig = {
@@ -78,6 +99,19 @@ export type StreamAuthConfig<TSchema extends z.ZodTypeAny = z.ZodTypeAny> = {
78
99
  */
79
100
  authenticate: (request: StreamAuthRequest) => Promise<z.infer<TSchema> | null> | (z.infer<TSchema> | null)
80
101
  }
102
+
103
+ export type RedisConfig =
104
+ | {
105
+ useMemoryServer?: false
106
+ host: string
107
+ port: number
108
+ password?: string
109
+ username?: string
110
+ db?: number
111
+ }
112
+ | {
113
+ useMemoryServer: true
114
+ }
81
115
  ```
82
116
 
83
117
  ## Plugins
@@ -300,6 +334,81 @@ export default config({
300
334
  })
301
335
  ```
302
336
 
337
+ ## Redis Configuration
338
+
339
+ Motia uses Redis for state management, caching, and real-time features. By default, Motia automatically starts an in-memory Redis server for local development, eliminating the need for external Redis installation.
340
+
341
+ ### Default Behavior (In-Memory Redis)
342
+
343
+ When no `redis` configuration is provided, Motia uses an embedded in-memory Redis server:
344
+
345
+ ```typescript
346
+ import { config } from 'motia'
347
+
348
+ export default config({})
349
+ ```
350
+
351
+ You can also explicitly enable the in-memory server:
352
+
353
+ ```typescript
354
+ export default config({
355
+ redis: {
356
+ useMemoryServer: true,
357
+ },
358
+ })
359
+ ```
360
+
361
+ ### Using External Redis
362
+
363
+ To connect to an external Redis instance (useful for production or when you already have Redis running), configure the connection settings:
364
+
365
+ ```typescript
366
+ import { config } from 'motia'
367
+
368
+ export default config({
369
+ redis: {
370
+ useMemoryServer: false,
371
+ host: 'localhost',
372
+ port: 6379,
373
+ },
374
+ })
375
+ ```
376
+
377
+ For production environments with authentication:
378
+
379
+ ```typescript
380
+ export default config({
381
+ redis: {
382
+ useMemoryServer: false,
383
+ host: process.env.REDIS_HOST || 'localhost',
384
+ port: parseInt(process.env.REDIS_PORT || '6379'),
385
+ password: process.env.REDIS_PASSWORD,
386
+ username: process.env.REDIS_USERNAME,
387
+ db: parseInt(process.env.REDIS_DB || '0'),
388
+ },
389
+ })
390
+ ```
391
+
392
+ The optional Redis configuration fields include:
393
+ - `password`: Redis password for authentication
394
+ - `username`: Redis username (Redis 6.0+)
395
+ - `db`: Database number to select (default: 0)
396
+
397
+ You can also use environment variables directly:
398
+ - `MOTIA_REDIS_PASSWORD`: Redis password
399
+ - `MOTIA_REDIS_USERNAME`: Redis username
400
+ - `MOTIA_REDIS_DB`: Database number
401
+
402
+ ### Creating Projects with External Redis
403
+
404
+ When creating a new project, you can skip the embedded Redis binary installation using the `--skip-redis` flag:
405
+
406
+ ```bash
407
+ npx motia create my-app --skip-redis
408
+ ```
409
+
410
+ This will create a project with `motia.config.ts` pre-configured for external Redis, and you'll need to ensure Redis is running before starting your application.
411
+
303
412
  ## Complete Example
304
413
 
305
414
  ```typescript
package/dist/dev.mjs CHANGED
@@ -8,7 +8,8 @@ import { isTutorialDisabled, workbenchBase } from "./constants.mjs";
8
8
  import { createDevWatchers } from "./dev-watchers.mjs";
9
9
  import { processPlugins } from "./plugins/process-plugins.mjs";
10
10
  import "./plugins/index.mjs";
11
- import { getRedisClient, stopRedisConnection } from "./redis/connection.mjs";
11
+ import { getRedisClient, getRedisConnectionInfo, stopRedisConnection } from "./redis/connection.mjs";
12
+ import { validatePythonEnvironment } from "./utils/validate-python-environment.mjs";
12
13
  import { createMermaidGenerator, createServer, getProjectIdentifier, trackEvent } from "@motiadev/core";
13
14
  import { flush } from "@amplitude/analytics-node";
14
15
  import { BullMQEventAdapter } from "@motiadev/adapter-bullmq-events";
@@ -31,7 +32,12 @@ const dev = async (port, hostname, disableVerbose, enableMermaid, motiaFileStora
31
32
  total_step_files: stepFiles.length,
32
33
  project_name: getProjectIdentifier(baseDir)
33
34
  });
34
- if (hasPythonFiles) {
35
+ const pythonValidation = await validatePythonEnvironment({
36
+ baseDir,
37
+ hasPythonFiles
38
+ });
39
+ if (!pythonValidation.success) process.exit(1);
40
+ if (pythonValidation.hasPythonFiles) {
35
41
  activatePythonVenv({
36
42
  baseDir,
37
43
  isVerbose
@@ -43,10 +49,7 @@ const dev = async (port, hostname, disableVerbose, enableMermaid, motiaFileStora
43
49
  const redisClient = await getRedisClient(motiaFileStoragePath, appConfig);
44
50
  const adapters = {
45
51
  eventAdapter: appConfig.adapters?.events || new BullMQEventAdapter({
46
- connection: {
47
- host: redisClient.options.socket?.host || "localhost",
48
- port: redisClient.options.socket?.port || 6379
49
- },
52
+ connection: getRedisConnectionInfo(),
50
53
  prefix: "motia:events"
51
54
  }),
52
55
  cronAdapter: appConfig.adapters?.cron || new RedisCronAdapter(redisClient),
package/dist/dev.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dev.mjs","names":["redisClient: RedisClientType","plugins: MotiaPlugin[]"],"sources":["../src/dev.ts"],"sourcesContent":["import { flush } from '@amplitude/analytics-node'\nimport { BullMQEventAdapter } from '@motiadev/adapter-bullmq-events'\nimport { RedisCronAdapter } from '@motiadev/adapter-redis-cron'\nimport { RedisStateAdapter } from '@motiadev/adapter-redis-state'\nimport { RedisStreamAdapterManager } from '@motiadev/adapter-redis-streams'\nimport {\n createMermaidGenerator,\n createServer,\n getProjectIdentifier,\n type MotiaPlugin,\n trackEvent,\n} from '@motiadev/core'\nimport type { RedisClientType } from 'redis'\nimport { deployEndpoints } from './cloud/endpoints'\nimport { isTutorialDisabled, workbenchBase } from './constants'\nimport { createDevWatchers } from './dev-watchers'\nimport { generateLockedData, getStepFiles, getStreamFiles } from './generate-locked-data'\nimport { loadMotiaConfig } from './load-motia-config'\nimport { processPlugins } from './plugins'\nimport { getRedisClient, stopRedisConnection } from './redis/connection'\nimport { activatePythonVenv } from './utils/activate-python-env'\nimport { identifyUser } from './utils/analytics'\nimport { version } from './version'\n\nexport const dev = async (\n port: number,\n hostname: string,\n disableVerbose: boolean,\n enableMermaid: boolean,\n motiaFileStorageDir?: string,\n): Promise<void> => {\n const baseDir = process.cwd()\n const isVerbose = !disableVerbose\n\n identifyUser()\n\n const stepFiles = [...getStepFiles(baseDir), ...getStreamFiles(baseDir)]\n const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'))\n\n trackEvent('dev_server_started', {\n port,\n verbose_mode: isVerbose,\n mermaid_enabled: enableMermaid,\n has_python_files: hasPythonFiles,\n total_step_files: stepFiles.length,\n project_name: getProjectIdentifier(baseDir),\n })\n\n if (hasPythonFiles) {\n activatePythonVenv({ baseDir, isVerbose })\n trackEvent('python_environment_activated')\n }\n\n const motiaFileStoragePath = motiaFileStorageDir || '.motia'\n\n const appConfig = await loadMotiaConfig(baseDir)\n\n const redisClient: RedisClientType = await getRedisClient(motiaFileStoragePath, appConfig)\n\n const adapters = {\n eventAdapter:\n appConfig.adapters?.events ||\n new BullMQEventAdapter({\n connection: {\n host: (redisClient.options.socket as { host?: string })?.host || 'localhost',\n port: (redisClient.options.socket as { port?: number })?.port || 6379,\n },\n prefix: 'motia:events',\n }),\n cronAdapter: appConfig.adapters?.cron || new RedisCronAdapter(redisClient),\n streamAdapter: appConfig.adapters?.streams || new RedisStreamAdapterManager(redisClient),\n }\n\n const lockedData = await generateLockedData({\n projectDir: baseDir,\n streamAdapter: adapters.streamAdapter,\n redisClient,\n streamAuth: appConfig.streamAuth,\n })\n\n const state = appConfig.adapters?.state || new RedisStateAdapter(redisClient)\n\n const config = { isVerbose }\n\n const motiaServer = createServer(lockedData, state, config, adapters, appConfig.app)\n const watcher = createDevWatchers(lockedData, motiaServer, motiaServer.motiaEventManager, motiaServer.cronManager)\n const plugins: MotiaPlugin[] = await processPlugins(motiaServer)\n\n // Initialize mermaid generator\n if (enableMermaid) {\n const mermaidGenerator = createMermaidGenerator(baseDir)\n mermaidGenerator.initialize(lockedData)\n trackEvent('mermaid_generator_initialized')\n }\n\n deployEndpoints(motiaServer, lockedData)\n\n motiaServer.app.get('/__motia', (_, res) => {\n const meta = {\n version,\n isDev: true,\n isTutorialDisabled,\n workbenchBase,\n }\n\n res //\n .header('Access-Control-Allow-Origin', '*')\n .header('Access-Control-Allow-Private-Network', 'true')\n .status(200)\n .json(meta)\n })\n\n trackEvent('dev_server_ready', {\n port,\n flows_count: lockedData.flows?.length || 0,\n steps_count: lockedData.activeSteps?.length || 0,\n flows: Object.keys(lockedData.flows || {}),\n steps: lockedData.activeSteps.map((step) => step.config.name),\n streams: Object.keys(lockedData.getStreams() || {}),\n runtime_version: version,\n environment: process.env.NODE_ENV || 'development',\n })\n\n const { applyMiddleware } = await import('@motiadev/workbench/middleware')\n\n await applyMiddleware({\n app: motiaServer.app,\n port,\n workbenchBase,\n plugins: plugins.flatMap((item) => item.workbench),\n })\n\n motiaServer.server.listen(port, hostname)\n console.log('🚀 Server ready and listening on port', port)\n console.log(`🔗 Open http://localhost:${port}${workbenchBase} to open workbench 🛠️`)\n\n process.on('SIGTERM', async () => {\n trackEvent('dev_server_shutdown', { reason: 'SIGTERM' })\n motiaServer.server.close()\n await watcher.stop()\n await stopRedisConnection()\n await flush().promise\n process.exit(0)\n })\n\n process.on('SIGINT', async () => {\n trackEvent('dev_server_shutdown', { reason: 'SIGINT' })\n motiaServer.server.close()\n await watcher.stop()\n await stopRedisConnection()\n await flush().promise\n process.exit(0)\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dev.mjs","names":["redisClient: RedisClientType","plugins: MotiaPlugin[]"],"sources":["../src/dev.ts"],"sourcesContent":["import { flush } from '@amplitude/analytics-node'\nimport { BullMQEventAdapter } from '@motiadev/adapter-bullmq-events'\nimport { RedisCronAdapter } from '@motiadev/adapter-redis-cron'\nimport { RedisStateAdapter } from '@motiadev/adapter-redis-state'\nimport { RedisStreamAdapterManager } from '@motiadev/adapter-redis-streams'\nimport {\n createMermaidGenerator,\n createServer,\n getProjectIdentifier,\n type MotiaPlugin,\n trackEvent,\n} from '@motiadev/core'\nimport type { RedisClientType } from 'redis'\nimport { deployEndpoints } from './cloud/endpoints'\nimport { isTutorialDisabled, workbenchBase } from './constants'\nimport { createDevWatchers } from './dev-watchers'\nimport { generateLockedData, getStepFiles, getStreamFiles } from './generate-locked-data'\nimport { loadMotiaConfig } from './load-motia-config'\nimport { processPlugins } from './plugins'\nimport { getRedisClient, getRedisConnectionInfo, stopRedisConnection } from './redis/connection'\nimport { activatePythonVenv } from './utils/activate-python-env'\nimport { identifyUser } from './utils/analytics'\nimport { validatePythonEnvironment } from './utils/validate-python-environment'\nimport { version } from './version'\n\nexport const dev = async (\n port: number,\n hostname: string,\n disableVerbose: boolean,\n enableMermaid: boolean,\n motiaFileStorageDir?: string,\n): Promise<void> => {\n const baseDir = process.cwd()\n const isVerbose = !disableVerbose\n\n identifyUser()\n\n const stepFiles = [...getStepFiles(baseDir), ...getStreamFiles(baseDir)]\n const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'))\n\n trackEvent('dev_server_started', {\n port,\n verbose_mode: isVerbose,\n mermaid_enabled: enableMermaid,\n has_python_files: hasPythonFiles,\n total_step_files: stepFiles.length,\n project_name: getProjectIdentifier(baseDir),\n })\n\n const pythonValidation = await validatePythonEnvironment({ baseDir, hasPythonFiles })\n if (!pythonValidation.success) {\n process.exit(1)\n }\n\n if (pythonValidation.hasPythonFiles) {\n activatePythonVenv({ baseDir, isVerbose })\n trackEvent('python_environment_activated')\n }\n\n const motiaFileStoragePath = motiaFileStorageDir || '.motia'\n\n const appConfig = await loadMotiaConfig(baseDir)\n\n const redisClient: RedisClientType = await getRedisClient(motiaFileStoragePath, appConfig)\n\n const adapters = {\n eventAdapter:\n appConfig.adapters?.events ||\n new BullMQEventAdapter({\n connection: getRedisConnectionInfo(),\n prefix: 'motia:events',\n }),\n cronAdapter: appConfig.adapters?.cron || new RedisCronAdapter(redisClient),\n streamAdapter: appConfig.adapters?.streams || new RedisStreamAdapterManager(redisClient),\n }\n\n const lockedData = await generateLockedData({\n projectDir: baseDir,\n streamAdapter: adapters.streamAdapter,\n redisClient,\n streamAuth: appConfig.streamAuth,\n })\n\n const state = appConfig.adapters?.state || new RedisStateAdapter(redisClient)\n\n const config = { isVerbose }\n\n const motiaServer = createServer(lockedData, state, config, adapters, appConfig.app)\n const watcher = createDevWatchers(lockedData, motiaServer, motiaServer.motiaEventManager, motiaServer.cronManager)\n const plugins: MotiaPlugin[] = await processPlugins(motiaServer)\n\n // Initialize mermaid generator\n if (enableMermaid) {\n const mermaidGenerator = createMermaidGenerator(baseDir)\n mermaidGenerator.initialize(lockedData)\n trackEvent('mermaid_generator_initialized')\n }\n\n deployEndpoints(motiaServer, lockedData)\n\n motiaServer.app.get('/__motia', (_, res) => {\n const meta = {\n version,\n isDev: true,\n isTutorialDisabled,\n workbenchBase,\n }\n\n res //\n .header('Access-Control-Allow-Origin', '*')\n .header('Access-Control-Allow-Private-Network', 'true')\n .status(200)\n .json(meta)\n })\n\n trackEvent('dev_server_ready', {\n port,\n flows_count: lockedData.flows?.length || 0,\n steps_count: lockedData.activeSteps?.length || 0,\n flows: Object.keys(lockedData.flows || {}),\n steps: lockedData.activeSteps.map((step) => step.config.name),\n streams: Object.keys(lockedData.getStreams() || {}),\n runtime_version: version,\n environment: process.env.NODE_ENV || 'development',\n })\n\n const { applyMiddleware } = await import('@motiadev/workbench/middleware')\n\n await applyMiddleware({\n app: motiaServer.app,\n port,\n workbenchBase,\n plugins: plugins.flatMap((item) => item.workbench),\n })\n\n motiaServer.server.listen(port, hostname)\n console.log('🚀 Server ready and listening on port', port)\n console.log(`🔗 Open http://localhost:${port}${workbenchBase} to open workbench 🛠️`)\n\n process.on('SIGTERM', async () => {\n trackEvent('dev_server_shutdown', { reason: 'SIGTERM' })\n motiaServer.server.close()\n await watcher.stop()\n await stopRedisConnection()\n await flush().promise\n process.exit(0)\n })\n\n process.on('SIGINT', async () => {\n trackEvent('dev_server_shutdown', { reason: 'SIGINT' })\n motiaServer.server.close()\n await watcher.stop()\n await stopRedisConnection()\n await flush().promise\n process.exit(0)\n })\n}\n"],"mappings":"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"}
@@ -6,32 +6,44 @@ import { createClient } from "redis";
6
6
  var RedisConnectionManager = class {
7
7
  constructor() {
8
8
  this.client = null;
9
+ this.redisConnectionInfo = null;
9
10
  }
10
11
  async getConnectionInfo(baseDir, config) {
11
12
  if (shouldUseMemoryServer(config)) return await new RedisMemoryManager().startServer(baseDir);
12
13
  if (isExternalRedisConfig(config.redis)) return {
13
14
  host: config.redis.host,
14
- port: config.redis.port
15
+ port: config.redis.port,
16
+ password: config.redis.password,
17
+ username: config.redis.username,
18
+ db: config.redis.db
15
19
  };
16
20
  return {
17
21
  host: process.env.MOTIA_REDIS_HOST || "127.0.0.1",
18
- port: parseInt(process.env.MOTIA_REDIS_PORT || "6379")
22
+ port: parseInt(process.env.MOTIA_REDIS_PORT || "6379"),
23
+ password: process.env.MOTIA_REDIS_PASSWORD,
24
+ username: process.env.MOTIA_REDIS_USERNAME,
25
+ db: parseInt(process.env.MOTIA_REDIS_DB || "0")
19
26
  };
20
27
  }
21
28
  async connect(baseDir, config) {
22
29
  if (this.client) return this.client;
23
- const { host, port } = await this.getConnectionInfo(baseDir, config);
24
- this.client = createClient({ socket: {
25
- host,
26
- port,
27
- noDelay: true,
28
- keepAlive: true,
29
- reconnectStrategy: (retries) => {
30
- if (retries > 10) return /* @__PURE__ */ new Error("Redis connection retry limit exceeded");
31
- return Math.min(retries * 100, 3e3);
32
- },
33
- connectTimeout: 1e4
34
- } });
30
+ this.redisConnectionInfo = await this.getConnectionInfo(baseDir, config);
31
+ this.client = createClient({
32
+ password: this.redisConnectionInfo.password,
33
+ username: this.redisConnectionInfo.username,
34
+ database: this.redisConnectionInfo.db,
35
+ socket: {
36
+ host: this.redisConnectionInfo.host,
37
+ port: this.redisConnectionInfo.port,
38
+ noDelay: true,
39
+ keepAlive: true,
40
+ reconnectStrategy: (retries) => {
41
+ if (retries > 10) return /* @__PURE__ */ new Error("Redis connection retry limit exceeded");
42
+ return Math.min(retries * 100, 3e3);
43
+ },
44
+ connectTimeout: 1e4
45
+ }
46
+ });
35
47
  await this.client.connect();
36
48
  return this.client;
37
49
  }
@@ -49,11 +61,15 @@ var RedisConnectionManager = class {
49
61
  getClient() {
50
62
  return this.client;
51
63
  }
64
+ get connectionInfo() {
65
+ return this.redisConnectionInfo;
66
+ }
52
67
  };
53
68
  const manager = new RedisConnectionManager();
54
69
  const getRedisClient = (baseDir, config) => manager.connect(baseDir, config);
55
70
  const stopRedisConnection = () => manager.stop();
71
+ const getRedisConnectionInfo = () => manager.connectionInfo;
56
72
 
57
73
  //#endregion
58
- export { getRedisClient, stopRedisConnection };
74
+ export { getRedisClient, getRedisConnectionInfo, stopRedisConnection };
59
75
  //# sourceMappingURL=connection.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.mjs","names":[],"sources":["../../src/redis/connection.ts"],"sourcesContent":["import { createClient, type RedisClientType } from 'redis'\nimport type { LoadedMotiaConfig } from '../load-motia-config'\nimport { RedisMemoryManager } from './memory-manager'\nimport type { RedisConnectionInfo } from './types'\nimport { isExternalRedisConfig, shouldUseMemoryServer } from './utils'\n\nexport type { RedisConnectionInfo } from './types'\n\nclass RedisConnectionManager {\n private client: RedisClientType | null = null\n\n private async getConnectionInfo(baseDir: string, config: LoadedMotiaConfig): Promise<RedisConnectionInfo> {\n if (shouldUseMemoryServer(config)) {\n return await new RedisMemoryManager().startServer(baseDir)\n }\n if (isExternalRedisConfig(config.redis)) {\n return { host: config.redis.host, port: config.redis.port }\n }\n return { host: process.env.MOTIA_REDIS_HOST || '127.0.0.1', port: parseInt(process.env.MOTIA_REDIS_PORT || '6379') }\n }\n\n async connect(baseDir: string, config: LoadedMotiaConfig): Promise<RedisClientType> {\n if (this.client) {\n return this.client\n }\n\n const { host, port } = await this.getConnectionInfo(baseDir, config)\n\n this.client = createClient({\n socket: {\n host,\n port,\n noDelay: true,\n keepAlive: true,\n reconnectStrategy: (retries: number) => {\n if (retries > 10) {\n return new Error('Redis connection retry limit exceeded')\n }\n return Math.min(retries * 100, 3000)\n },\n connectTimeout: 10000,\n },\n })\n\n await this.client.connect()\n\n return this.client\n }\n\n async stop(): Promise<void> {\n if (this.client?.isOpen) {\n await this.client.quit().catch(() => {\n console.error('[Redis Connection] Redis client closed')\n })\n this.client = null\n }\n }\n\n isRunning(): boolean {\n return !!this.client?.isOpen\n }\n\n getClient(): RedisClientType | null {\n return this.client\n }\n}\n\nconst manager = new RedisConnectionManager()\n\nexport const getRedisClient = (baseDir: string, config: LoadedMotiaConfig): Promise<RedisClientType> =>\n manager.connect(baseDir, config)\n\nexport const stopRedisConnection = (): Promise<void> => manager.stop()\n"],"mappings":";;;;;AAQA,IAAM,yBAAN,MAA6B;;gBACc;;CAEzC,MAAc,kBAAkB,SAAiB,QAAyD;AACxG,MAAI,sBAAsB,OAAO,CAC/B,QAAO,MAAM,IAAI,oBAAoB,CAAC,YAAY,QAAQ;AAE5D,MAAI,sBAAsB,OAAO,MAAM,CACrC,QAAO;GAAE,MAAM,OAAO,MAAM;GAAM,MAAM,OAAO,MAAM;GAAM;AAE7D,SAAO;GAAE,MAAM,QAAQ,IAAI,oBAAoB;GAAa,MAAM,SAAS,QAAQ,IAAI,oBAAoB,OAAO;GAAE;;CAGtH,MAAM,QAAQ,SAAiB,QAAqD;AAClF,MAAI,KAAK,OACP,QAAO,KAAK;EAGd,MAAM,EAAE,MAAM,SAAS,MAAM,KAAK,kBAAkB,SAAS,OAAO;AAEpE,OAAK,SAAS,aAAa,EACzB,QAAQ;GACN;GACA;GACA,SAAS;GACT,WAAW;GACX,oBAAoB,YAAoB;AACtC,QAAI,UAAU,GACZ,wBAAO,IAAI,MAAM,wCAAwC;AAE3D,WAAO,KAAK,IAAI,UAAU,KAAK,IAAK;;GAEtC,gBAAgB;GACjB,EACF,CAAC;AAEF,QAAM,KAAK,OAAO,SAAS;AAE3B,SAAO,KAAK;;CAGd,MAAM,OAAsB;AAC1B,MAAI,KAAK,QAAQ,QAAQ;AACvB,SAAM,KAAK,OAAO,MAAM,CAAC,YAAY;AACnC,YAAQ,MAAM,yCAAyC;KACvD;AACF,QAAK,SAAS;;;CAIlB,YAAqB;AACnB,SAAO,CAAC,CAAC,KAAK,QAAQ;;CAGxB,YAAoC;AAClC,SAAO,KAAK;;;AAIhB,MAAM,UAAU,IAAI,wBAAwB;AAE5C,MAAa,kBAAkB,SAAiB,WAC9C,QAAQ,QAAQ,SAAS,OAAO;AAElC,MAAa,4BAA2C,QAAQ,MAAM"}
1
+ {"version":3,"file":"connection.mjs","names":[],"sources":["../../src/redis/connection.ts"],"sourcesContent":["import { createClient, type RedisClientType } from 'redis'\nimport type { LoadedMotiaConfig } from '../load-motia-config'\nimport { RedisMemoryManager } from './memory-manager'\nimport type { RedisConnectionInfo } from './types'\nimport { isExternalRedisConfig, shouldUseMemoryServer } from './utils'\n\nexport type { RedisConnectionInfo } from './types'\n\nclass RedisConnectionManager {\n private client: RedisClientType | null = null\n private redisConnectionInfo: RedisConnectionInfo | null = null\n\n private async getConnectionInfo(baseDir: string, config: LoadedMotiaConfig): Promise<RedisConnectionInfo> {\n if (shouldUseMemoryServer(config)) {\n return await new RedisMemoryManager().startServer(baseDir)\n }\n if (isExternalRedisConfig(config.redis)) {\n return {\n host: config.redis.host,\n port: config.redis.port,\n password: config.redis.password,\n username: config.redis.username,\n db: config.redis.db,\n }\n }\n return {\n host: process.env.MOTIA_REDIS_HOST || '127.0.0.1',\n port: parseInt(process.env.MOTIA_REDIS_PORT || '6379'),\n password: process.env.MOTIA_REDIS_PASSWORD,\n username: process.env.MOTIA_REDIS_USERNAME,\n db: parseInt(process.env.MOTIA_REDIS_DB || '0'),\n }\n }\n\n async connect(baseDir: string, config: LoadedMotiaConfig): Promise<RedisClientType> {\n if (this.client) {\n return this.client\n }\n\n this.redisConnectionInfo = await this.getConnectionInfo(baseDir, config)\n\n this.client = createClient({\n password: this.redisConnectionInfo.password,\n username: this.redisConnectionInfo.username,\n database: this.redisConnectionInfo.db,\n socket: {\n host: this.redisConnectionInfo.host,\n port: this.redisConnectionInfo.port,\n noDelay: true,\n keepAlive: true,\n reconnectStrategy: (retries: number) => {\n if (retries > 10) {\n return new Error('Redis connection retry limit exceeded')\n }\n return Math.min(retries * 100, 3000)\n },\n connectTimeout: 10000,\n },\n })\n\n await this.client.connect()\n\n return this.client\n }\n\n async stop(): Promise<void> {\n if (this.client?.isOpen) {\n await this.client.quit().catch(() => {\n console.error('[Redis Connection] Redis client closed')\n })\n this.client = null\n }\n }\n\n isRunning(): boolean {\n return !!this.client?.isOpen\n }\n\n getClient(): RedisClientType | null {\n return this.client\n }\n\n get connectionInfo(): RedisConnectionInfo {\n return this.redisConnectionInfo!\n }\n}\n\nconst manager = new RedisConnectionManager()\n\nexport const getRedisClient = (baseDir: string, config: LoadedMotiaConfig): Promise<RedisClientType> =>\n manager.connect(baseDir, config)\n\nexport const stopRedisConnection = (): Promise<void> => manager.stop()\n\nexport const getRedisConnectionInfo = (): RedisConnectionInfo => manager.connectionInfo\n"],"mappings":";;;;;AAQA,IAAM,yBAAN,MAA6B;;gBACc;6BACiB;;CAE1D,MAAc,kBAAkB,SAAiB,QAAyD;AACxG,MAAI,sBAAsB,OAAO,CAC/B,QAAO,MAAM,IAAI,oBAAoB,CAAC,YAAY,QAAQ;AAE5D,MAAI,sBAAsB,OAAO,MAAM,CACrC,QAAO;GACL,MAAM,OAAO,MAAM;GACnB,MAAM,OAAO,MAAM;GACnB,UAAU,OAAO,MAAM;GACvB,UAAU,OAAO,MAAM;GACvB,IAAI,OAAO,MAAM;GAClB;AAEH,SAAO;GACL,MAAM,QAAQ,IAAI,oBAAoB;GACtC,MAAM,SAAS,QAAQ,IAAI,oBAAoB,OAAO;GACtD,UAAU,QAAQ,IAAI;GACtB,UAAU,QAAQ,IAAI;GACtB,IAAI,SAAS,QAAQ,IAAI,kBAAkB,IAAI;GAChD;;CAGH,MAAM,QAAQ,SAAiB,QAAqD;AAClF,MAAI,KAAK,OACP,QAAO,KAAK;AAGd,OAAK,sBAAsB,MAAM,KAAK,kBAAkB,SAAS,OAAO;AAExE,OAAK,SAAS,aAAa;GACzB,UAAU,KAAK,oBAAoB;GACnC,UAAU,KAAK,oBAAoB;GACnC,UAAU,KAAK,oBAAoB;GACnC,QAAQ;IACN,MAAM,KAAK,oBAAoB;IAC/B,MAAM,KAAK,oBAAoB;IAC/B,SAAS;IACT,WAAW;IACX,oBAAoB,YAAoB;AACtC,SAAI,UAAU,GACZ,wBAAO,IAAI,MAAM,wCAAwC;AAE3D,YAAO,KAAK,IAAI,UAAU,KAAK,IAAK;;IAEtC,gBAAgB;IACjB;GACF,CAAC;AAEF,QAAM,KAAK,OAAO,SAAS;AAE3B,SAAO,KAAK;;CAGd,MAAM,OAAsB;AAC1B,MAAI,KAAK,QAAQ,QAAQ;AACvB,SAAM,KAAK,OAAO,MAAM,CAAC,YAAY;AACnC,YAAQ,MAAM,yCAAyC;KACvD;AACF,QAAK,SAAS;;;CAIlB,YAAqB;AACnB,SAAO,CAAC,CAAC,KAAK,QAAQ;;CAGxB,YAAoC;AAClC,SAAO,KAAK;;CAGd,IAAI,iBAAsC;AACxC,SAAO,KAAK;;;AAIhB,MAAM,UAAU,IAAI,wBAAwB;AAE5C,MAAa,kBAAkB,SAAiB,WAC9C,QAAQ,QAAQ,SAAS,OAAO;AAElC,MAAa,4BAA2C,QAAQ,MAAM;AAEtE,MAAa,+BAAoD,QAAQ"}
@@ -49,10 +49,10 @@ var RedisMemoryManager = class {
49
49
  async stop() {
50
50
  if (this.server && this.running) try {
51
51
  await this.server.stop();
52
- this.running = false;
53
52
  } catch (error) {
54
53
  console.error("[Redis Memory Server] Error stopping:", error?.message);
55
54
  } finally {
55
+ this.running = false;
56
56
  this.server = null;
57
57
  }
58
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-manager.mjs","names":["instance: RedisMemoryInstancePropT","error: unknown"],"sources":["../../src/redis/memory-manager.ts"],"sourcesContent":["import { mkdirSync } from 'fs'\nimport type { RedisClientType } from 'redis'\nimport { RedisMemoryServer } from 'redis-memory-server'\nimport type { RedisMemoryInstancePropT } from 'redis-memory-server/lib/types'\nimport type { RedisConnectionInfo } from './types'\n\nexport class RedisMemoryManager {\n private server: RedisMemoryServer | null = null\n private client: RedisClientType | null = null\n private running = false\n\n private registerCleanupHandlers(): void {\n process.on('exit', async () => {\n await this.stop()\n })\n process.on('SIGTERM', async () => {\n await this.stop()\n })\n\n process.on('SIGINT', async () => {\n await this.stop()\n })\n }\n\n async startServer(baseDir: string): Promise<RedisConnectionInfo> {\n if (!this.server) {\n try {\n mkdirSync(baseDir, { recursive: true })\n\n const instance: RedisMemoryInstancePropT = {\n args: ['--appendonly', 'yes', '--appendfsync', 'everysec', '--save', '\"\"', '--dir', baseDir],\n }\n\n if (process.env.MOTIA_REDIS_PORT) {\n instance.port = parseInt(process.env.MOTIA_REDIS_PORT || '6379')\n }\n\n this.server = new RedisMemoryServer({ instance })\n console.log('Redis Memory Server started')\n this.running = true\n this.registerCleanupHandlers()\n } catch (error) {\n console.error('[Redis Memory Server] Failed to start:', error)\n throw error\n }\n }\n\n const host = await this.server.getHost()\n const port = await this.server.getPort()\n\n return { host, port }\n }\n\n async stop(): Promise<void> {\n if (this.server && this.running) {\n try {\n await this.server.stop()\n this.running = false\n } catch (error: unknown) {\n console.error('[Redis Memory Server] Error stopping:', (error as Error)?.message)\n } finally {\n this.server = null\n }\n }\n }\n\n isRunning(): boolean {\n return this.running\n }\n\n getClient(): RedisClientType | null {\n return this.client\n }\n}\n"],"mappings":";;;;AAMA,IAAa,qBAAb,MAAgC;;gBACa;gBACF;iBACvB;;CAElB,AAAQ,0BAAgC;AACtC,UAAQ,GAAG,QAAQ,YAAY;AAC7B,SAAM,KAAK,MAAM;IACjB;AACF,UAAQ,GAAG,WAAW,YAAY;AAChC,SAAM,KAAK,MAAM;IACjB;AAEF,UAAQ,GAAG,UAAU,YAAY;AAC/B,SAAM,KAAK,MAAM;IACjB;;CAGJ,MAAM,YAAY,SAA+C;AAC/D,MAAI,CAAC,KAAK,OACR,KAAI;AACF,aAAU,SAAS,EAAE,WAAW,MAAM,CAAC;GAEvC,MAAMA,WAAqC,EACzC,MAAM;IAAC;IAAgB;IAAO;IAAiB;IAAY;IAAU;IAAM;IAAS;IAAQ,EAC7F;AAED,OAAI,QAAQ,IAAI,iBACd,UAAS,OAAO,SAAS,QAAQ,IAAI,oBAAoB,OAAO;AAGlE,QAAK,SAAS,IAAI,kBAAkB,EAAE,UAAU,CAAC;AACjD,WAAQ,IAAI,8BAA8B;AAC1C,QAAK,UAAU;AACf,QAAK,yBAAyB;WACvB,OAAO;AACd,WAAQ,MAAM,0CAA0C,MAAM;AAC9D,SAAM;;AAOV,SAAO;GAAE,MAHI,MAAM,KAAK,OAAO,SAAS;GAGzB,MAFF,MAAM,KAAK,OAAO,SAAS;GAEnB;;CAGvB,MAAM,OAAsB;AAC1B,MAAI,KAAK,UAAU,KAAK,QACtB,KAAI;AACF,SAAM,KAAK,OAAO,MAAM;AACxB,QAAK,UAAU;WACRC,OAAgB;AACvB,WAAQ,MAAM,yCAA0C,OAAiB,QAAQ;YACzE;AACR,QAAK,SAAS;;;CAKpB,YAAqB;AACnB,SAAO,KAAK;;CAGd,YAAoC;AAClC,SAAO,KAAK"}
1
+ {"version":3,"file":"memory-manager.mjs","names":["instance: RedisMemoryInstancePropT","error: unknown"],"sources":["../../src/redis/memory-manager.ts"],"sourcesContent":["import { mkdirSync } from 'fs'\nimport type { RedisClientType } from 'redis'\nimport { RedisMemoryServer } from 'redis-memory-server'\nimport type { RedisMemoryInstancePropT } from 'redis-memory-server/lib/types'\nimport type { RedisConnectionInfo } from './types'\n\nexport class RedisMemoryManager {\n private server: RedisMemoryServer | null = null\n private client: RedisClientType | null = null\n private running = false\n\n private registerCleanupHandlers(): void {\n process.on('exit', async () => {\n await this.stop()\n })\n process.on('SIGTERM', async () => {\n await this.stop()\n })\n\n process.on('SIGINT', async () => {\n await this.stop()\n })\n }\n\n async startServer(baseDir: string): Promise<RedisConnectionInfo> {\n if (!this.server) {\n try {\n mkdirSync(baseDir, { recursive: true })\n\n const instance: RedisMemoryInstancePropT = {\n args: ['--appendonly', 'yes', '--appendfsync', 'everysec', '--save', '\"\"', '--dir', baseDir],\n }\n\n if (process.env.MOTIA_REDIS_PORT) {\n instance.port = parseInt(process.env.MOTIA_REDIS_PORT || '6379')\n }\n\n this.server = new RedisMemoryServer({ instance })\n console.log('Redis Memory Server started')\n this.running = true\n this.registerCleanupHandlers()\n } catch (error) {\n console.error('[Redis Memory Server] Failed to start:', error)\n throw error\n }\n }\n\n const host = await this.server.getHost()\n const port = await this.server.getPort()\n\n return { host, port }\n }\n\n async stop(): Promise<void> {\n if (this.server && this.running) {\n try {\n await this.server.stop()\n } catch (error: unknown) {\n console.error('[Redis Memory Server] Error stopping:', (error as Error)?.message)\n } finally {\n this.running = false\n this.server = null\n }\n }\n }\n\n isRunning(): boolean {\n return this.running\n }\n\n getClient(): RedisClientType | null {\n return this.client\n }\n}\n"],"mappings":";;;;AAMA,IAAa,qBAAb,MAAgC;;gBACa;gBACF;iBACvB;;CAElB,AAAQ,0BAAgC;AACtC,UAAQ,GAAG,QAAQ,YAAY;AAC7B,SAAM,KAAK,MAAM;IACjB;AACF,UAAQ,GAAG,WAAW,YAAY;AAChC,SAAM,KAAK,MAAM;IACjB;AAEF,UAAQ,GAAG,UAAU,YAAY;AAC/B,SAAM,KAAK,MAAM;IACjB;;CAGJ,MAAM,YAAY,SAA+C;AAC/D,MAAI,CAAC,KAAK,OACR,KAAI;AACF,aAAU,SAAS,EAAE,WAAW,MAAM,CAAC;GAEvC,MAAMA,WAAqC,EACzC,MAAM;IAAC;IAAgB;IAAO;IAAiB;IAAY;IAAU;IAAM;IAAS;IAAQ,EAC7F;AAED,OAAI,QAAQ,IAAI,iBACd,UAAS,OAAO,SAAS,QAAQ,IAAI,oBAAoB,OAAO;AAGlE,QAAK,SAAS,IAAI,kBAAkB,EAAE,UAAU,CAAC;AACjD,WAAQ,IAAI,8BAA8B;AAC1C,QAAK,UAAU;AACf,QAAK,yBAAyB;WACvB,OAAO;AACd,WAAQ,MAAM,0CAA0C,MAAM;AAC9D,SAAM;;AAOV,SAAO;GAAE,MAHI,MAAM,KAAK,OAAO,SAAS;GAGzB,MAFF,MAAM,KAAK,OAAO,SAAS;GAEnB;;CAGvB,MAAM,OAAsB;AAC1B,MAAI,KAAK,UAAU,KAAK,QACtB,KAAI;AACF,SAAM,KAAK,OAAO,MAAM;WACjBC,OAAgB;AACvB,WAAQ,MAAM,yCAA0C,OAAiB,QAAQ;YACzE;AACR,QAAK,UAAU;AACf,QAAK,SAAS;;;CAKpB,YAAqB;AACnB,SAAO,KAAK;;CAGd,YAAoC;AAClC,SAAO,KAAK"}
package/dist/start.mjs CHANGED
@@ -5,7 +5,8 @@ import { loadMotiaConfig } from "./load-motia-config.mjs";
5
5
  import { workbenchBase } from "./constants.mjs";
6
6
  import { processPlugins } from "./plugins/process-plugins.mjs";
7
7
  import "./plugins/index.mjs";
8
- import { getRedisClient, stopRedisConnection } from "./redis/connection.mjs";
8
+ import { getRedisClient, getRedisConnectionInfo, stopRedisConnection } from "./redis/connection.mjs";
9
+ import { validatePythonEnvironment } from "./utils/validate-python-environment.mjs";
9
10
  import { createServer } from "@motiadev/core";
10
11
  import path from "path";
11
12
  import { BullMQEventAdapter } from "@motiadev/adapter-bullmq-events";
@@ -17,7 +18,12 @@ import { RedisStreamAdapterManager } from "@motiadev/adapter-redis-streams";
17
18
  const start = async (port, hostname, disableVerbose, motiaFileStorageDir) => {
18
19
  const baseDir = process.cwd();
19
20
  const isVerbose = !disableVerbose;
20
- if ([...getStepFiles(baseDir), ...getStreamFiles(baseDir)].some((file) => file.endsWith(".py"))) {
21
+ const pythonValidation = await validatePythonEnvironment({
22
+ baseDir,
23
+ hasPythonFiles: [...getStepFiles(baseDir), ...getStreamFiles(baseDir)].some((file) => file.endsWith(".py"))
24
+ });
25
+ if (!pythonValidation.success) process.exit(1);
26
+ if (pythonValidation.hasPythonFiles) {
21
27
  console.log("⚙️ Activating Python environment...");
22
28
  activatePythonVenv({
23
29
  baseDir,
@@ -29,10 +35,7 @@ const start = async (port, hostname, disableVerbose, motiaFileStorageDir) => {
29
35
  const appConfig = await loadMotiaConfig(baseDir);
30
36
  const redisClient = await getRedisClient(dotMotia, appConfig);
31
37
  const adapters = {
32
- eventAdapter: appConfig.adapters?.events || new BullMQEventAdapter({ connection: {
33
- host: redisClient.options.socket?.host || "localhost",
34
- port: redisClient.options.socket?.port || 6379
35
- } }),
38
+ eventAdapter: appConfig.adapters?.events || new BullMQEventAdapter({ connection: getRedisConnectionInfo() }),
36
39
  cronAdapter: appConfig.adapters?.cron || new RedisCronAdapter(redisClient),
37
40
  streamAdapter: appConfig.adapters?.streams || new RedisStreamAdapterManager(redisClient)
38
41
  };
@@ -1 +1 @@
1
- {"version":3,"file":"start.mjs","names":["redisClient: RedisClientType","plugins: MotiaPlugin[]"],"sources":["../src/start.ts"],"sourcesContent":["import { BullMQEventAdapter } from '@motiadev/adapter-bullmq-events'\nimport { RedisCronAdapter } from '@motiadev/adapter-redis-cron'\nimport { RedisStateAdapter } from '@motiadev/adapter-redis-state'\nimport { RedisStreamAdapterManager } from '@motiadev/adapter-redis-streams'\nimport { createServer, type MotiaPlugin } from '@motiadev/core'\nimport path from 'path'\nimport type { RedisClientType } from 'redis'\nimport { workbenchBase } from './constants'\nimport { generateLockedData, getStepFiles, getStreamFiles } from './generate-locked-data'\nimport { loadMotiaConfig } from './load-motia-config'\nimport { processPlugins } from './plugins/index'\nimport { getRedisClient, stopRedisConnection } from './redis/connection'\nimport { activatePythonVenv } from './utils/activate-python-env'\nimport { version } from './version'\n\nexport const start = async (\n port: number,\n hostname: string,\n disableVerbose: boolean,\n motiaFileStorageDir?: string,\n): Promise<void> => {\n const baseDir = process.cwd()\n const isVerbose = !disableVerbose\n\n const stepFiles = [...getStepFiles(baseDir), ...getStreamFiles(baseDir)]\n const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'))\n\n if (hasPythonFiles) {\n console.log('⚙️ Activating Python environment...')\n activatePythonVenv({ baseDir, isVerbose })\n }\n\n const motiaFileStoragePath = motiaFileStorageDir || '.motia'\n\n const dotMotia = path.join(baseDir, motiaFileStoragePath)\n const appConfig = await loadMotiaConfig(baseDir)\n\n const redisClient: RedisClientType = await getRedisClient(dotMotia, appConfig)\n\n const adapters = {\n eventAdapter:\n appConfig.adapters?.events ||\n new BullMQEventAdapter({\n connection: {\n host: (redisClient.options.socket as { host?: string })?.host || 'localhost',\n port: (redisClient.options.socket as { port?: number })?.port || 6379,\n },\n }),\n cronAdapter: appConfig.adapters?.cron || new RedisCronAdapter(redisClient),\n streamAdapter: appConfig.adapters?.streams || new RedisStreamAdapterManager(redisClient),\n }\n const lockedData = await generateLockedData({\n projectDir: baseDir,\n streamAdapter: adapters.streamAdapter,\n redisClient,\n streamAuth: appConfig.streamAuth,\n })\n\n const state = appConfig.adapters?.state || new RedisStateAdapter(redisClient)\n\n const config = { isVerbose, isDev: false, version }\n\n const motiaServer = createServer(lockedData, state, config, adapters, appConfig.app)\n const plugins: MotiaPlugin[] = await processPlugins(motiaServer)\n\n if (!process.env.MOTIA_DOCKER_DISABLE_WORKBENCH) {\n const { applyMiddleware } = await import('@motiadev/workbench/middleware')\n await applyMiddleware({\n app: motiaServer.app,\n port,\n workbenchBase,\n plugins: plugins.flatMap((item) => item.workbench),\n })\n }\n\n motiaServer.server.listen(port, hostname)\n console.log('🚀 Server ready and listening on port', port)\n console.log(`🔗 Open http://${hostname}:${port}${workbenchBase} to open workbench 🛠️`)\n\n process.on('SIGTERM', async () => {\n motiaServer.server.close()\n await stopRedisConnection()\n process.exit(0)\n })\n\n process.on('SIGINT', async () => {\n motiaServer.server.close()\n await stopRedisConnection()\n process.exit(0)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,MAAa,QAAQ,OACnB,MACA,UACA,gBACA,wBACkB;CAClB,MAAM,UAAU,QAAQ,KAAK;CAC7B,MAAM,YAAY,CAAC;AAKnB,KAHkB,CAAC,GAAG,aAAa,QAAQ,EAAE,GAAG,eAAe,QAAQ,CAAC,CACvC,MAAM,SAAS,KAAK,SAAS,MAAM,CAAC,EAEjD;AAClB,UAAQ,IAAI,sCAAsC;AAClD,qBAAmB;GAAE;GAAS;GAAW,CAAC;;CAG5C,MAAM,uBAAuB,uBAAuB;CAEpD,MAAM,WAAW,KAAK,KAAK,SAAS,qBAAqB;CACzD,MAAM,YAAY,MAAM,gBAAgB,QAAQ;CAEhD,MAAMA,cAA+B,MAAM,eAAe,UAAU,UAAU;CAE9E,MAAM,WAAW;EACf,cACE,UAAU,UAAU,UACpB,IAAI,mBAAmB,EACrB,YAAY;GACV,MAAO,YAAY,QAAQ,QAA8B,QAAQ;GACjE,MAAO,YAAY,QAAQ,QAA8B,QAAQ;GAClE,EACF,CAAC;EACJ,aAAa,UAAU,UAAU,QAAQ,IAAI,iBAAiB,YAAY;EAC1E,eAAe,UAAU,UAAU,WAAW,IAAI,0BAA0B,YAAY;EACzF;CAYD,MAAM,cAAc,aAXD,MAAM,mBAAmB;EAC1C,YAAY;EACZ,eAAe,SAAS;EACxB;EACA,YAAY,UAAU;EACvB,CAAC,EAEY,UAAU,UAAU,SAAS,IAAI,kBAAkB,YAAY,EAE9D;EAAE;EAAW,OAAO;EAAO;EAAS,EAES,UAAU,UAAU,IAAI;CACpF,MAAMC,UAAyB,MAAM,eAAe,YAAY;AAEhE,KAAI,CAAC,QAAQ,IAAI,gCAAgC;EAC/C,MAAM,EAAE,oBAAoB,MAAM,OAAO;AACzC,QAAM,gBAAgB;GACpB,KAAK,YAAY;GACjB;GACA;GACA,SAAS,QAAQ,SAAS,SAAS,KAAK,UAAU;GACnD,CAAC;;AAGJ,aAAY,OAAO,OAAO,MAAM,SAAS;AACzC,SAAQ,IAAI,yCAAyC,KAAK;AAC1D,SAAQ,IAAI,kBAAkB,SAAS,GAAG,OAAO,cAAc,wBAAwB;AAEvF,SAAQ,GAAG,WAAW,YAAY;AAChC,cAAY,OAAO,OAAO;AAC1B,QAAM,qBAAqB;AAC3B,UAAQ,KAAK,EAAE;GACf;AAEF,SAAQ,GAAG,UAAU,YAAY;AAC/B,cAAY,OAAO,OAAO;AAC1B,QAAM,qBAAqB;AAC3B,UAAQ,KAAK,EAAE;GACf"}
1
+ {"version":3,"file":"start.mjs","names":["redisClient: RedisClientType","plugins: MotiaPlugin[]"],"sources":["../src/start.ts"],"sourcesContent":["import { BullMQEventAdapter } from '@motiadev/adapter-bullmq-events'\nimport { RedisCronAdapter } from '@motiadev/adapter-redis-cron'\nimport { RedisStateAdapter } from '@motiadev/adapter-redis-state'\nimport { RedisStreamAdapterManager } from '@motiadev/adapter-redis-streams'\nimport { createServer, type MotiaPlugin } from '@motiadev/core'\nimport path from 'path'\nimport type { RedisClientType } from 'redis'\nimport { workbenchBase } from './constants'\nimport { generateLockedData, getStepFiles, getStreamFiles } from './generate-locked-data'\nimport { loadMotiaConfig } from './load-motia-config'\nimport { processPlugins } from './plugins/index'\nimport { getRedisClient, getRedisConnectionInfo, stopRedisConnection } from './redis/connection'\nimport { activatePythonVenv } from './utils/activate-python-env'\nimport { validatePythonEnvironment } from './utils/validate-python-environment'\nimport { version } from './version'\n\nexport const start = async (\n port: number,\n hostname: string,\n disableVerbose: boolean,\n motiaFileStorageDir?: string,\n): Promise<void> => {\n const baseDir = process.cwd()\n const isVerbose = !disableVerbose\n\n const stepFiles = [...getStepFiles(baseDir), ...getStreamFiles(baseDir)]\n const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'))\n\n const pythonValidation = await validatePythonEnvironment({ baseDir, hasPythonFiles })\n if (!pythonValidation.success) {\n process.exit(1)\n }\n\n if (pythonValidation.hasPythonFiles) {\n console.log('⚙️ Activating Python environment...')\n activatePythonVenv({ baseDir, isVerbose })\n }\n\n const motiaFileStoragePath = motiaFileStorageDir || '.motia'\n\n const dotMotia = path.join(baseDir, motiaFileStoragePath)\n const appConfig = await loadMotiaConfig(baseDir)\n\n const redisClient: RedisClientType = await getRedisClient(dotMotia, appConfig)\n\n const adapters = {\n eventAdapter:\n appConfig.adapters?.events ||\n new BullMQEventAdapter({\n connection: getRedisConnectionInfo(),\n }),\n cronAdapter: appConfig.adapters?.cron || new RedisCronAdapter(redisClient),\n streamAdapter: appConfig.adapters?.streams || new RedisStreamAdapterManager(redisClient),\n }\n const lockedData = await generateLockedData({\n projectDir: baseDir,\n streamAdapter: adapters.streamAdapter,\n redisClient,\n streamAuth: appConfig.streamAuth,\n })\n\n const state = appConfig.adapters?.state || new RedisStateAdapter(redisClient)\n\n const config = { isVerbose, isDev: false, version }\n\n const motiaServer = createServer(lockedData, state, config, adapters, appConfig.app)\n const plugins: MotiaPlugin[] = await processPlugins(motiaServer)\n\n if (!process.env.MOTIA_DOCKER_DISABLE_WORKBENCH) {\n const { applyMiddleware } = await import('@motiadev/workbench/middleware')\n await applyMiddleware({\n app: motiaServer.app,\n port,\n workbenchBase,\n plugins: plugins.flatMap((item) => item.workbench),\n })\n }\n\n motiaServer.server.listen(port, hostname)\n console.log('🚀 Server ready and listening on port', port)\n console.log(`🔗 Open http://${hostname}:${port}${workbenchBase} to open workbench 🛠️`)\n\n process.on('SIGTERM', async () => {\n motiaServer.server.close()\n await stopRedisConnection()\n process.exit(0)\n })\n\n process.on('SIGINT', async () => {\n motiaServer.server.close()\n await stopRedisConnection()\n process.exit(0)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,MAAa,QAAQ,OACnB,MACA,UACA,gBACA,wBACkB;CAClB,MAAM,UAAU,QAAQ,KAAK;CAC7B,MAAM,YAAY,CAAC;CAKnB,MAAM,mBAAmB,MAAM,0BAA0B;EAAE;EAAS,gBAHlD,CAAC,GAAG,aAAa,QAAQ,EAAE,GAAG,eAAe,QAAQ,CAAC,CACvC,MAAM,SAAS,KAAK,SAAS,MAAM,CAAC;EAEe,CAAC;AACrF,KAAI,CAAC,iBAAiB,QACpB,SAAQ,KAAK,EAAE;AAGjB,KAAI,iBAAiB,gBAAgB;AACnC,UAAQ,IAAI,sCAAsC;AAClD,qBAAmB;GAAE;GAAS;GAAW,CAAC;;CAG5C,MAAM,uBAAuB,uBAAuB;CAEpD,MAAM,WAAW,KAAK,KAAK,SAAS,qBAAqB;CACzD,MAAM,YAAY,MAAM,gBAAgB,QAAQ;CAEhD,MAAMA,cAA+B,MAAM,eAAe,UAAU,UAAU;CAE9E,MAAM,WAAW;EACf,cACE,UAAU,UAAU,UACpB,IAAI,mBAAmB,EACrB,YAAY,wBAAwB,EACrC,CAAC;EACJ,aAAa,UAAU,UAAU,QAAQ,IAAI,iBAAiB,YAAY;EAC1E,eAAe,UAAU,UAAU,WAAW,IAAI,0BAA0B,YAAY;EACzF;CAYD,MAAM,cAAc,aAXD,MAAM,mBAAmB;EAC1C,YAAY;EACZ,eAAe,SAAS;EACxB;EACA,YAAY,UAAU;EACvB,CAAC,EAEY,UAAU,UAAU,SAAS,IAAI,kBAAkB,YAAY,EAE9D;EAAE;EAAW,OAAO;EAAO;EAAS,EAES,UAAU,UAAU,IAAI;CACpF,MAAMC,UAAyB,MAAM,eAAe,YAAY;AAEhE,KAAI,CAAC,QAAQ,IAAI,gCAAgC;EAC/C,MAAM,EAAE,oBAAoB,MAAM,OAAO;AACzC,QAAM,gBAAgB;GACpB,KAAK,YAAY;GACjB;GACA;GACA,SAAS,QAAQ,SAAS,SAAS,KAAK,UAAU;GACnD,CAAC;;AAGJ,aAAY,OAAO,OAAO,MAAM,SAAS;AACzC,SAAQ,IAAI,yCAAyC,KAAK;AAC1D,SAAQ,IAAI,kBAAkB,SAAS,GAAG,OAAO,cAAc,wBAAwB;AAEvF,SAAQ,GAAG,WAAW,YAAY;AAChC,cAAY,OAAO,OAAO;AAC1B,QAAM,qBAAqB;AAC3B,UAAQ,KAAK,EAAE;GACf;AAEF,SAAQ,GAAG,UAAU,YAAY;AAC/B,cAAY,OAAO,OAAO;AAC1B,QAAM,qBAAqB;AAC3B,UAAQ,KAAK,EAAE;GACf"}
@@ -1,11 +1,17 @@
1
1
  import { checkIfFileExists } from "../create/utils.mjs";
2
+ import path from "path";
2
3
 
3
4
  //#region src/utils/get-package-manager.ts
4
5
  const getPackageManager = (dir) => {
5
- if (checkIfFileExists(dir, "yarn.lock")) return "yarn";
6
- else if (checkIfFileExists(dir, "pnpm-lock.yaml")) return "pnpm";
7
- else if (checkIfFileExists(dir, "package-lock.json")) return "npm";
8
- else return "unknown";
6
+ let currentDir = dir;
7
+ while (currentDir !== path.dirname(currentDir)) {
8
+ if (checkIfFileExists(currentDir, "yarn.lock")) return "yarn";
9
+ else if (checkIfFileExists(currentDir, "pnpm-lock.yaml")) return "pnpm";
10
+ else if (checkIfFileExists(currentDir, "package-lock.json")) return "npm";
11
+ else if (checkIfFileExists(currentDir, "bun.lockb") || checkIfFileExists(currentDir, "bun.lock")) return "bun";
12
+ currentDir = path.dirname(currentDir);
13
+ }
14
+ return "npm";
9
15
  };
10
16
 
11
17
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"get-package-manager.mjs","names":[],"sources":["../../src/utils/get-package-manager.ts"],"sourcesContent":["import { checkIfFileExists } from '../create/utils'\n\nexport const getPackageManager = (dir: string): string => {\n if (checkIfFileExists(dir, 'yarn.lock')) {\n return 'yarn'\n } else if (checkIfFileExists(dir, 'pnpm-lock.yaml')) {\n return 'pnpm'\n } else if (checkIfFileExists(dir, 'package-lock.json')) {\n return 'npm'\n } else {\n return 'unknown'\n }\n}\n"],"mappings":";;;AAEA,MAAa,qBAAqB,QAAwB;AACxD,KAAI,kBAAkB,KAAK,YAAY,CACrC,QAAO;UACE,kBAAkB,KAAK,iBAAiB,CACjD,QAAO;UACE,kBAAkB,KAAK,oBAAoB,CACpD,QAAO;KAEP,QAAO"}
1
+ {"version":3,"file":"get-package-manager.mjs","names":[],"sources":["../../src/utils/get-package-manager.ts"],"sourcesContent":["import path from 'path'\nimport { checkIfFileExists } from '../create/utils'\n\nexport const getPackageManager = (dir: string): string => {\n let currentDir = dir\n\n while (currentDir !== path.dirname(currentDir)) {\n if (checkIfFileExists(currentDir, 'yarn.lock')) {\n return 'yarn'\n } else if (checkIfFileExists(currentDir, 'pnpm-lock.yaml')) {\n return 'pnpm'\n } else if (checkIfFileExists(currentDir, 'package-lock.json')) {\n return 'npm'\n } else if (checkIfFileExists(currentDir, 'bun.lockb') || checkIfFileExists(currentDir, 'bun.lock')) {\n return 'bun'\n }\n currentDir = path.dirname(currentDir)\n }\n\n return 'npm'\n}\n"],"mappings":";;;;AAGA,MAAa,qBAAqB,QAAwB;CACxD,IAAI,aAAa;AAEjB,QAAO,eAAe,KAAK,QAAQ,WAAW,EAAE;AAC9C,MAAI,kBAAkB,YAAY,YAAY,CAC5C,QAAO;WACE,kBAAkB,YAAY,iBAAiB,CACxD,QAAO;WACE,kBAAkB,YAAY,oBAAoB,CAC3D,QAAO;WACE,kBAAkB,YAAY,YAAY,IAAI,kBAAkB,YAAY,WAAW,CAChG,QAAO;AAET,eAAa,KAAK,QAAQ,WAAW;;AAGvC,QAAO"}
@@ -0,0 +1,63 @@
1
+ import { internalLogger } from "./internal-logger.mjs";
2
+ import { getPythonCommand } from "./python-version-utils.mjs";
3
+ import { getPackageManager } from "./get-package-manager.mjs";
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ //#region src/utils/validate-python-environment.ts
8
+ function getInstallCommand(baseDir) {
9
+ switch (getPackageManager(baseDir)) {
10
+ case "yarn": return "yarn install";
11
+ case "pnpm": return "pnpm install";
12
+ case "bun": return "bun install";
13
+ case "npm":
14
+ default: return "npm install";
15
+ }
16
+ }
17
+ async function validatePythonEnvironment({ baseDir, hasPythonFiles, pythonVersion = "3.13" }) {
18
+ if (!hasPythonFiles) return {
19
+ success: true,
20
+ hasPythonFiles: false
21
+ };
22
+ try {
23
+ await getPythonCommand(pythonVersion, baseDir);
24
+ } catch {
25
+ internalLogger.error("Python is not installed");
26
+ internalLogger.info("Python files were detected in your project but Python 3 is not available");
27
+ internalLogger.info("Please install Python 3.10 or higher: https://www.python.org/downloads/");
28
+ return {
29
+ success: false,
30
+ hasPythonFiles: true
31
+ };
32
+ }
33
+ const venvPath = path.join(baseDir, "python_modules");
34
+ if (!fs.existsSync(venvPath)) {
35
+ const installCmd = getInstallCommand(baseDir);
36
+ internalLogger.error("Python environment not configured");
37
+ internalLogger.info("The python_modules directory was not found");
38
+ internalLogger.info(`Run '${installCmd}' to set up your Python environment`);
39
+ return {
40
+ success: false,
41
+ hasPythonFiles: true
42
+ };
43
+ }
44
+ const libPath = path.join(venvPath, "lib");
45
+ if (!fs.existsSync(libPath)) {
46
+ const installCmd = getInstallCommand(baseDir);
47
+ internalLogger.error("Python environment is incomplete");
48
+ internalLogger.info("The python_modules directory exists but appears to be corrupted");
49
+ internalLogger.info(`Run '${installCmd}' to recreate your Python environment`);
50
+ return {
51
+ success: false,
52
+ hasPythonFiles: true
53
+ };
54
+ }
55
+ return {
56
+ success: true,
57
+ hasPythonFiles: true
58
+ };
59
+ }
60
+
61
+ //#endregion
62
+ export { validatePythonEnvironment };
63
+ //# sourceMappingURL=validate-python-environment.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-python-environment.mjs","names":[],"sources":["../../src/utils/validate-python-environment.ts"],"sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport { getPackageManager } from './get-package-manager'\nimport { internalLogger } from './internal-logger'\nimport { getPythonCommand } from './python-version-utils'\n\nexport interface ValidationResult {\n success: boolean\n hasPythonFiles: boolean\n}\n\ninterface ValidateConfig {\n baseDir: string\n hasPythonFiles: boolean\n pythonVersion?: string\n}\n\nfunction getInstallCommand(baseDir: string): string {\n const pm = getPackageManager(baseDir)\n switch (pm) {\n case 'yarn':\n return 'yarn install'\n case 'pnpm':\n return 'pnpm install'\n case 'bun':\n return 'bun install'\n case 'npm':\n default:\n return 'npm install'\n }\n}\n\nexport async function validatePythonEnvironment({\n baseDir,\n hasPythonFiles,\n pythonVersion = '3.13',\n}: ValidateConfig): Promise<ValidationResult> {\n if (!hasPythonFiles) {\n return { success: true, hasPythonFiles: false }\n }\n\n try {\n await getPythonCommand(pythonVersion, baseDir)\n } catch {\n internalLogger.error('Python is not installed')\n internalLogger.info('Python files were detected in your project but Python 3 is not available')\n internalLogger.info('Please install Python 3.10 or higher: https://www.python.org/downloads/')\n return { success: false, hasPythonFiles: true }\n }\n\n const venvPath = path.join(baseDir, 'python_modules')\n if (!fs.existsSync(venvPath)) {\n const installCmd = getInstallCommand(baseDir)\n internalLogger.error('Python environment not configured')\n internalLogger.info('The python_modules directory was not found')\n internalLogger.info(`Run '${installCmd}' to set up your Python environment`)\n return { success: false, hasPythonFiles: true }\n }\n\n const libPath = path.join(venvPath, 'lib')\n if (!fs.existsSync(libPath)) {\n const installCmd = getInstallCommand(baseDir)\n internalLogger.error('Python environment is incomplete')\n internalLogger.info('The python_modules directory exists but appears to be corrupted')\n internalLogger.info(`Run '${installCmd}' to recreate your Python environment`)\n return { success: false, hasPythonFiles: true }\n }\n\n return { success: true, hasPythonFiles: true }\n}\n"],"mappings":";;;;;;;AAiBA,SAAS,kBAAkB,SAAyB;AAElD,SADW,kBAAkB,QAAQ,EACrC;EACE,KAAK,OACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,KAAK;EACL,QACE,QAAO;;;AAIb,eAAsB,0BAA0B,EAC9C,SACA,gBACA,gBAAgB,UAC4B;AAC5C,KAAI,CAAC,eACH,QAAO;EAAE,SAAS;EAAM,gBAAgB;EAAO;AAGjD,KAAI;AACF,QAAM,iBAAiB,eAAe,QAAQ;SACxC;AACN,iBAAe,MAAM,0BAA0B;AAC/C,iBAAe,KAAK,2EAA2E;AAC/F,iBAAe,KAAK,0EAA0E;AAC9F,SAAO;GAAE,SAAS;GAAO,gBAAgB;GAAM;;CAGjD,MAAM,WAAW,KAAK,KAAK,SAAS,iBAAiB;AACrD,KAAI,CAAC,GAAG,WAAW,SAAS,EAAE;EAC5B,MAAM,aAAa,kBAAkB,QAAQ;AAC7C,iBAAe,MAAM,oCAAoC;AACzD,iBAAe,KAAK,6CAA6C;AACjE,iBAAe,KAAK,QAAQ,WAAW,qCAAqC;AAC5E,SAAO;GAAE,SAAS;GAAO,gBAAgB;GAAM;;CAGjD,MAAM,UAAU,KAAK,KAAK,UAAU,MAAM;AAC1C,KAAI,CAAC,GAAG,WAAW,QAAQ,EAAE;EAC3B,MAAM,aAAa,kBAAkB,QAAQ;AAC7C,iBAAe,MAAM,mCAAmC;AACxD,iBAAe,KAAK,kEAAkE;AACtF,iBAAe,KAAK,QAAQ,WAAW,uCAAuC;AAC9E,SAAO;GAAE,SAAS;GAAO,gBAAgB;GAAM;;AAGjD,QAAO;EAAE,SAAS;EAAM,gBAAgB;EAAM"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "motia",
3
3
  "description": "Build production-grade backends with a single primitive. APIs, background jobs, Queues, Workflows, and AI agents - unified in one system with built-in State management, Streaming, and Observability.",
4
- "version": "0.15.4-beta.170-484948",
4
+ "version": "0.15.4-beta.170-803755",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
7
7
  "repository": {
@@ -46,13 +46,13 @@
46
46
  "table": "^6.9.0",
47
47
  "ts-node": "^10.9.2",
48
48
  "zod": "^4.1.12",
49
- "@motiadev/adapter-bullmq-events": "0.15.4-beta.170-484948",
50
- "@motiadev/adapter-redis-cron": "0.15.4-beta.170-484948",
51
- "@motiadev/adapter-redis-streams": "0.15.4-beta.170-484948",
52
- "@motiadev/adapter-redis-state": "0.15.4-beta.170-484948",
53
- "@motiadev/core": "0.15.4-beta.170-484948",
54
- "@motiadev/stream-client-node": "0.15.4-beta.170-484948",
55
- "@motiadev/workbench": "0.15.4-beta.170-484948"
49
+ "@motiadev/adapter-bullmq-events": "0.15.4-beta.170-803755",
50
+ "@motiadev/adapter-redis-cron": "0.15.4-beta.170-803755",
51
+ "@motiadev/adapter-redis-state": "0.15.4-beta.170-803755",
52
+ "@motiadev/adapter-redis-streams": "0.15.4-beta.170-803755",
53
+ "@motiadev/stream-client-node": "0.15.4-beta.170-803755",
54
+ "@motiadev/workbench": "0.15.4-beta.170-803755",
55
+ "@motiadev/core": "0.15.4-beta.170-803755"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@amplitude/analytics-types": "^2.9.2",