@motiadev/adapter-redis-cron 0.8.2-beta.140-709523

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/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2025-10-22
4
+
5
+ ### Added
6
+ - Initial release of Redis cron adapter for Motia
7
+ - Distributed locking to prevent duplicate cron job executions
8
+ - Automatic TTL for lock expiration
9
+ - Lock renewal support for long-running jobs
10
+ - Health check functionality
11
+ - Configurable retry logic for lock acquisition
12
+ - Instance tracking for monitoring
13
+ - Graceful shutdown with automatic lock cleanup
14
+ - Full TypeScript support with type definitions
15
+ - Comprehensive documentation and examples
16
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Motia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # @motiadev/adapter-redis-cron
2
+
3
+ Redis cron adapter for Motia framework, enabling distributed cron job coordination to prevent duplicate executions across multiple instances.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @motiadev/adapter-redis-cron
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Configure the Redis cron adapter in your `motia.config.ts`:
14
+
15
+ ```typescript
16
+ import { config } from '@motiadev/core'
17
+ import { RedisCronAdapter } from '@motiadev/adapter-redis-cron'
18
+
19
+ export default config({
20
+ adapters: {
21
+ cron: new RedisCronAdapter({
22
+ host: process.env.REDIS_HOST || 'localhost',
23
+ port: parseInt(process.env.REDIS_PORT || '6379'),
24
+ password: process.env.REDIS_PASSWORD,
25
+ keyPrefix: 'motia:cron:lock:',
26
+ lockTTL: 300000,
27
+ instanceId: process.env.INSTANCE_ID || undefined,
28
+ }),
29
+ },
30
+ })
31
+ ```
32
+
33
+ ## Configuration Options
34
+
35
+ ### RedisCronAdapterConfig
36
+
37
+ | Option | Type | Default | Description |
38
+ |--------|------|---------|-------------|
39
+ | `host` | `string` | `'localhost'` | Redis server host |
40
+ | `port` | `number` | `6379` | Redis server port |
41
+ | `password` | `string` | `undefined` | Redis authentication password |
42
+ | `username` | `string` | `undefined` | Redis authentication username |
43
+ | `database` | `number` | `0` | Redis database number |
44
+ | `keyPrefix` | `string` | `'motia:cron:lock:'` | Prefix for all lock keys |
45
+ | `lockTTL` | `number` | `300000` | Lock time-to-live in milliseconds (5 minutes) |
46
+ | `lockRetryDelay` | `number` | `1000` | Delay between lock retry attempts in milliseconds |
47
+ | `lockRetryAttempts` | `number` | `0` | Number of times to retry acquiring a lock |
48
+ | `instanceId` | `string` | Auto-generated UUID | Unique identifier for this instance |
49
+ | `enableHealthCheck` | `boolean` | `true` | Whether to perform periodic health checks |
50
+ | `socket.reconnectStrategy` | `function` | Auto-retry | Custom reconnection strategy |
51
+ | `socket.connectTimeout` | `number` | `10000` | Connection timeout in milliseconds |
52
+
53
+ ## How It Works
54
+
55
+ When running multiple instances of a Motia application, each instance schedules the same cron jobs. Without coordination, this leads to duplicate executions. The Redis Cron Adapter solves this using distributed locking:
56
+
57
+ 1. **Job Scheduling**: All instances schedule cron jobs normally
58
+ 2. **Lock Acquisition**: When a cron job triggers, the instance attempts to acquire a distributed lock
59
+ 3. **Execution**: Only the instance that successfully acquires the lock executes the job
60
+ 4. **Lock Release**: After execution completes (or fails), the lock is released
61
+ 5. **TTL Protection**: Locks have a TTL to prevent deadlocks if an instance crashes
62
+
63
+ ## Execution Flow
64
+
65
+ ```
66
+ Instance 1 Instance 2 Instance 3
67
+ | | |
68
+ | Cron triggers | Cron triggers | Cron triggers
69
+ | (9:00 AM) | (9:00 AM) | (9:00 AM)
70
+ | | |
71
+ v v v
72
+ acquireLock() acquireLock() acquireLock()
73
+ | | |
74
+ v v v
75
+ ┌─────────────────────────────────────────────────────┐
76
+ │ Distributed Lock Store (Redis) │
77
+ │ │
78
+ │ Lock: daily-report │
79
+ │ Owner: instance-1 │
80
+ │ Acquired: 2025-10-22 09:00:00 │
81
+ │ Expires: 2025-10-22 09:05:00 │
82
+ └─────────────────────────────────────────────────────┘
83
+ | | |
84
+ v v v
85
+ Lock acquired ✓ Lock failed ✗ Lock failed ✗
86
+ | | |
87
+ v | |
88
+ Execute job Skip execution Skip execution
89
+ | | |
90
+ v | |
91
+ releaseLock() | |
92
+ ```
93
+
94
+ ## Features
95
+
96
+ - **Distributed Locking**: Prevents duplicate cron job executions across instances
97
+ - **Automatic TTL**: Locks expire automatically to prevent deadlocks
98
+ - **Lock Renewal**: Support for renewing locks for long-running jobs
99
+ - **Health Checks**: Monitor Redis connection health
100
+ - **Retry Logic**: Configurable retry attempts for lock acquisition
101
+ - **Instance Tracking**: Track which instance holds each lock
102
+ - **Graceful Shutdown**: Automatically releases locks on shutdown
103
+
104
+ ## Key Namespacing
105
+
106
+ The adapter uses the following key pattern:
107
+ ```
108
+ {keyPrefix}{jobName}
109
+ ```
110
+
111
+ For example:
112
+ ```
113
+ motia:cron:lock:daily-report
114
+ motia:cron:lock:cleanup-task
115
+ motia:cron:lock:send-notifications
116
+ ```
117
+
118
+ ## Example Cron Step
119
+
120
+ ```typescript
121
+ // steps/dailyReport/dailyReport.step.ts
122
+ import { type Handlers } from './types'
123
+
124
+ export const config = {
125
+ name: 'DailyReport',
126
+ cron: '0 9 * * *', // Run at 9 AM daily
127
+ }
128
+
129
+ export const handler: Handlers['DailyReport'] = async ({ logger }) => {
130
+ logger.info('Generating daily report')
131
+
132
+ // This will only execute on ONE instance
133
+ // even if you have 10 instances running
134
+
135
+ await generateReport()
136
+ await sendReport()
137
+
138
+ logger.info('Daily report sent successfully')
139
+ }
140
+ ```
141
+
142
+ ## Lock Renewal for Long-Running Jobs
143
+
144
+ For cron jobs that may take longer than the lock TTL, implement lock renewal:
145
+
146
+ ```typescript
147
+ export const handler: Handlers['LongRunningJob'] = async ({ logger }) => {
148
+ // Note: Lock renewal is handled internally by the cron handler
149
+ // You can configure a longer lockTTL in the adapter config
150
+
151
+ logger.info('Starting long-running job')
152
+
153
+ // Your long-running logic here
154
+ await processLargeDataset()
155
+
156
+ logger.info('Long-running job completed')
157
+ }
158
+ ```
159
+
160
+ ## Monitoring Active Locks
161
+
162
+ To monitor which instances are executing cron jobs:
163
+
164
+ ```typescript
165
+ import { RedisCronAdapter } from '@motiadev/adapter-redis-cron'
166
+
167
+ const adapter = new RedisCronAdapter({
168
+ host: 'localhost',
169
+ port: 6379,
170
+ })
171
+
172
+ const activeLocks = await adapter.getActiveLocks()
173
+ console.log('Active cron jobs:', activeLocks)
174
+ ```
175
+
176
+ ## Environment Variables
177
+
178
+ ```bash
179
+ REDIS_HOST=localhost
180
+ REDIS_PORT=6379
181
+ REDIS_PASSWORD=your-password
182
+ REDIS_DATABASE=0
183
+ CRON_KEY_PREFIX=motia:cron:lock:
184
+ CRON_LOCK_TTL=300000
185
+ INSTANCE_ID=instance-1
186
+ ```
187
+
188
+ ## Lock TTL Selection
189
+
190
+ Choose the lock TTL based on your job execution times:
191
+
192
+ - **Short Jobs** (< 1 minute): Use 60000ms (1 minute) TTL
193
+ - **Medium Jobs** (1-5 minutes): Use 300000ms (5 minutes) TTL
194
+ - **Long Jobs** (> 5 minutes): Use 600000ms+ (10+ minutes) TTL
195
+
196
+ ## Performance Considerations
197
+
198
+ - **Lock Overhead**: Redis lock acquisition adds ~2-5ms overhead per cron trigger (negligible)
199
+ - **Scalability**: Scales linearly with number of unique cron jobs, not number of instances
200
+ - **Network Latency**: Consider network latency between app and Redis
201
+ - **TTL Settings**: Set appropriate TTL values based on job execution times
202
+
203
+ ## Edge Cases
204
+
205
+ ### Instance Crashes
206
+
207
+ If an instance crashes while holding a lock, the lock will expire after the TTL, allowing another instance to execute the job. This provides automatic recovery.
208
+
209
+ ### Clock Skew
210
+
211
+ Different instances might have slightly different system clocks, causing cron jobs to trigger at slightly different times. The lock mechanism ensures only one execution regardless of timing differences.
212
+
213
+ ### Redis Unavailability
214
+
215
+ If Redis is unavailable, all instances will fail to acquire locks and no cron jobs will execute. This is a fail-safe behavior to prevent duplicates.
216
+
217
+ ## Development vs Production
218
+
219
+ ### Development
220
+
221
+ For development environments where horizontal scaling is not needed:
222
+ - Omit the `cron` adapter from configuration
223
+ - Cron jobs will execute normally without distributed locking
224
+ - Reduces external dependencies during development
225
+
226
+ ### Production
227
+
228
+ For production with multiple instances:
229
+ - Always configure a cron adapter
230
+ - Use managed Redis services (AWS ElastiCache, etc.) for high availability
231
+ - Set appropriate lock TTLs based on job execution times
232
+ - Monitor active locks to ensure proper coordination
233
+
234
+ ## Troubleshooting
235
+
236
+ ### Cron Jobs Not Executing
237
+
238
+ If no cron jobs are executing:
239
+ 1. Verify Redis is accessible from all instances
240
+ 2. Check Redis connection credentials
241
+ 3. Review instance logs for lock acquisition errors
242
+ 4. Verify cron expressions are valid
243
+ 5. Check Redis memory and connection limits
244
+
245
+ ### Duplicate Executions
246
+
247
+ If you see duplicate executions:
248
+ 1. Verify all instances use the same Redis instance
249
+ 2. Check that cron adapter is properly configured
250
+ 3. Review lock TTL settings (may be too short)
251
+ 4. Check for clock skew between instances
252
+ 5. Monitor Redis connection stability
253
+
254
+ ### Lock Contention
255
+
256
+ If locks are frequently contended:
257
+ 1. Review cron schedules to avoid overlapping jobs
258
+ 2. Consider staggering job execution times
259
+ 3. Increase lock retry attempts if appropriate
260
+ 4. Monitor Redis performance
261
+
262
+ ## License
263
+
264
+ MIT
265
+
@@ -0,0 +1,3 @@
1
+ export { RedisCronAdapter } from './redis-cron-adapter';
2
+ export type { RedisCronAdapterConfig } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisCronAdapter = void 0;
4
+ var redis_cron_adapter_1 = require("./redis-cron-adapter");
5
+ Object.defineProperty(exports, "RedisCronAdapter", { enumerable: true, get: function () { return redis_cron_adapter_1.RedisCronAdapter; } });
@@ -0,0 +1,24 @@
1
+ import type { CronAdapter, CronLock, CronLockInfo } from '@motiadev/core';
2
+ import type { RedisCronAdapterConfig } from './types';
3
+ export declare class RedisCronAdapter implements CronAdapter {
4
+ private client;
5
+ private keyPrefix;
6
+ private lockTTL;
7
+ private lockRetryDelay;
8
+ private lockRetryAttempts;
9
+ private instanceId;
10
+ private enableHealthCheck;
11
+ private connected;
12
+ constructor(config: RedisCronAdapterConfig);
13
+ private connect;
14
+ private ensureConnected;
15
+ private makeKey;
16
+ acquireLock(jobName: string, ttl?: number): Promise<CronLock | null>;
17
+ releaseLock(lock: CronLock): Promise<void>;
18
+ renewLock(lock: CronLock, ttl: number): Promise<boolean>;
19
+ isHealthy(): Promise<boolean>;
20
+ shutdown(): Promise<void>;
21
+ getActiveLocks(): Promise<CronLockInfo[]>;
22
+ private scanKeys;
23
+ }
24
+ //# sourceMappingURL=redis-cron-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cron-adapter.d.ts","sourceRoot":"","sources":["../src/redis-cron-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAGzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAErD,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,SAAS,CAAQ;gBAEb,MAAM,EAAE,sBAAsB;YA+C5B,OAAO;YAWP,eAAe;IAM7B,OAAO,CAAC,OAAO;IAIT,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IA8CpE,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B1C,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwCxD,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAc7B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBzB,cAAc,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YA2BjC,QAAQ;CAevB"}
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisCronAdapter = void 0;
4
+ const redis_1 = require("redis");
5
+ const uuid_1 = require("uuid");
6
+ class RedisCronAdapter {
7
+ constructor(config) {
8
+ this.connected = false;
9
+ this.keyPrefix = config.keyPrefix || 'motia:cron:lock:';
10
+ this.lockTTL = config.lockTTL || 300000;
11
+ this.lockRetryDelay = config.lockRetryDelay || 1000;
12
+ this.lockRetryAttempts = config.lockRetryAttempts || 0;
13
+ this.instanceId = config.instanceId || `motia-${(0, uuid_1.v4)()}`;
14
+ this.enableHealthCheck = config.enableHealthCheck ?? true;
15
+ this.client = (0, redis_1.createClient)({
16
+ socket: {
17
+ host: config.host || 'localhost',
18
+ port: config.port || 6379,
19
+ reconnectStrategy: config.socket?.reconnectStrategy ||
20
+ ((retries) => {
21
+ if (retries > 10) {
22
+ return new Error('Redis connection retry limit exceeded');
23
+ }
24
+ return Math.min(retries * 100, 3000);
25
+ }),
26
+ connectTimeout: config.socket?.connectTimeout || 10000,
27
+ },
28
+ password: config.password,
29
+ username: config.username,
30
+ database: config.database || 0,
31
+ });
32
+ this.client.on('error', (err) => {
33
+ console.error('[Redis Cron] Client error:', err);
34
+ });
35
+ this.client.on('connect', () => {
36
+ this.connected = true;
37
+ });
38
+ this.client.on('disconnect', () => {
39
+ console.warn('[Redis Cron] Disconnected');
40
+ this.connected = false;
41
+ });
42
+ this.client.on('reconnecting', () => {
43
+ console.log('[Redis Cron] Reconnecting...');
44
+ });
45
+ this.connect();
46
+ }
47
+ async connect() {
48
+ if (!this.connected && !this.client.isOpen) {
49
+ try {
50
+ await this.client.connect();
51
+ }
52
+ catch (error) {
53
+ console.error('[Redis Cron] Failed to connect:', error);
54
+ throw error;
55
+ }
56
+ }
57
+ }
58
+ async ensureConnected() {
59
+ if (!this.client.isOpen) {
60
+ await this.connect();
61
+ }
62
+ }
63
+ makeKey(jobName) {
64
+ return `${this.keyPrefix}${jobName}`;
65
+ }
66
+ async acquireLock(jobName, ttl) {
67
+ await this.ensureConnected();
68
+ const lockTTL = ttl || this.lockTTL;
69
+ const lockId = (0, uuid_1.v4)();
70
+ const key = this.makeKey(jobName);
71
+ const now = Date.now();
72
+ const expiresAt = now + lockTTL;
73
+ const lock = {
74
+ jobName,
75
+ lockId,
76
+ acquiredAt: now,
77
+ expiresAt,
78
+ instanceId: this.instanceId,
79
+ };
80
+ const lockData = JSON.stringify(lock);
81
+ const result = await this.client.set(key, lockData, {
82
+ PX: lockTTL,
83
+ NX: true,
84
+ });
85
+ if (result === 'OK') {
86
+ return lock;
87
+ }
88
+ if (this.lockRetryAttempts > 0) {
89
+ for (let attempt = 0; attempt < this.lockRetryAttempts; attempt++) {
90
+ await new Promise((resolve) => setTimeout(resolve, this.lockRetryDelay));
91
+ const retryResult = await this.client.set(key, lockData, {
92
+ PX: lockTTL,
93
+ NX: true,
94
+ });
95
+ if (retryResult === 'OK') {
96
+ return lock;
97
+ }
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ async releaseLock(lock) {
103
+ await this.ensureConnected();
104
+ const key = this.makeKey(lock.jobName);
105
+ const luaScript = `
106
+ local current = redis.call('GET', KEYS[1])
107
+ if not current then
108
+ return 0
109
+ end
110
+
111
+ local lock = cjson.decode(current)
112
+ if lock.lockId == ARGV[1] and lock.instanceId == ARGV[2] then
113
+ return redis.call('DEL', KEYS[1])
114
+ end
115
+
116
+ return 0
117
+ `;
118
+ try {
119
+ await this.client.eval(luaScript, {
120
+ keys: [key],
121
+ arguments: [lock.lockId, lock.instanceId],
122
+ });
123
+ }
124
+ catch (error) {
125
+ console.error('[Redis Cron] Error releasing lock:', error);
126
+ }
127
+ }
128
+ async renewLock(lock, ttl) {
129
+ await this.ensureConnected();
130
+ const key = this.makeKey(lock.jobName);
131
+ const now = Date.now();
132
+ const expiresAt = now + ttl;
133
+ const renewedLock = {
134
+ ...lock,
135
+ expiresAt,
136
+ };
137
+ const luaScript = `
138
+ local current = redis.call('GET', KEYS[1])
139
+ if not current then
140
+ return 0
141
+ end
142
+
143
+ local lock = cjson.decode(current)
144
+ if lock.lockId == ARGV[1] and lock.instanceId == ARGV[2] then
145
+ redis.call('SET', KEYS[1], ARGV[3], 'PX', ARGV[4])
146
+ return 1
147
+ end
148
+
149
+ return 0
150
+ `;
151
+ try {
152
+ const result = await this.client.eval(luaScript, {
153
+ keys: [key],
154
+ arguments: [lock.lockId, lock.instanceId, JSON.stringify(renewedLock), ttl.toString()],
155
+ });
156
+ return result === 1;
157
+ }
158
+ catch (error) {
159
+ console.error('[Redis Cron] Error renewing lock:', error);
160
+ return false;
161
+ }
162
+ }
163
+ async isHealthy() {
164
+ if (!this.enableHealthCheck) {
165
+ return true;
166
+ }
167
+ try {
168
+ await this.ensureConnected();
169
+ const result = await this.client.ping();
170
+ return result === 'PONG';
171
+ }
172
+ catch (error) {
173
+ return false;
174
+ }
175
+ }
176
+ async shutdown() {
177
+ await this.ensureConnected();
178
+ const pattern = `${this.keyPrefix}*`;
179
+ const keys = await this.scanKeys(pattern);
180
+ for (const key of keys) {
181
+ const lockData = await this.client.get(key);
182
+ if (lockData) {
183
+ try {
184
+ const lock = JSON.parse(lockData);
185
+ if (lock.instanceId === this.instanceId) {
186
+ await this.client.del(key);
187
+ }
188
+ }
189
+ catch (error) {
190
+ console.error('[Redis Cron] Error cleaning up lock during shutdown:', error);
191
+ }
192
+ }
193
+ }
194
+ if (this.client.isOpen) {
195
+ await this.client.quit();
196
+ }
197
+ }
198
+ async getActiveLocks() {
199
+ await this.ensureConnected();
200
+ const pattern = `${this.keyPrefix}*`;
201
+ const keys = await this.scanKeys(pattern);
202
+ const locks = [];
203
+ for (const key of keys) {
204
+ const lockData = await this.client.get(key);
205
+ if (lockData) {
206
+ try {
207
+ const lock = JSON.parse(lockData);
208
+ locks.push({
209
+ jobName: lock.jobName,
210
+ instanceId: lock.instanceId,
211
+ acquiredAt: lock.acquiredAt,
212
+ expiresAt: lock.expiresAt,
213
+ });
214
+ }
215
+ catch (error) {
216
+ console.error('[Redis Cron] Error parsing lock data:', error);
217
+ }
218
+ }
219
+ }
220
+ return locks;
221
+ }
222
+ async scanKeys(pattern) {
223
+ const keys = [];
224
+ let cursor = 0;
225
+ do {
226
+ const result = await this.client.scan(cursor, {
227
+ MATCH: pattern,
228
+ COUNT: 100,
229
+ });
230
+ cursor = result.cursor;
231
+ keys.push(...result.keys);
232
+ } while (cursor !== 0);
233
+ return keys;
234
+ }
235
+ }
236
+ exports.RedisCronAdapter = RedisCronAdapter;
@@ -0,0 +1,18 @@
1
+ export interface RedisCronAdapterConfig {
2
+ host?: string;
3
+ port?: number;
4
+ password?: string;
5
+ username?: string;
6
+ database?: number;
7
+ keyPrefix?: string;
8
+ lockTTL?: number;
9
+ lockRetryDelay?: number;
10
+ lockRetryAttempts?: number;
11
+ instanceId?: string;
12
+ enableHealthCheck?: boolean;
13
+ socket?: {
14
+ reconnectStrategy?: (retries: number) => number | Error;
15
+ connectTimeout?: number;
16
+ };
17
+ }
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,MAAM,CAAC,EAAE;QACP,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,KAAK,CAAA;QACvD,cAAc,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;CACF"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@motiadev/adapter-redis-cron",
3
+ "description": "Redis cron adapter for Motia framework, enabling distributed cron job coordination to prevent duplicate executions across multiple instances.",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "version": "0.8.2-beta.140-709523",
7
+ "dependencies": {
8
+ "redis": "^4.7.0",
9
+ "uuid": "^11.1.0",
10
+ "@motiadev/core": "0.8.2-beta.140-709523"
11
+ },
12
+ "devDependencies": {
13
+ "@types/node": "^22.10.2",
14
+ "typescript": "^5.7.2"
15
+ },
16
+ "peerDependencies": {
17
+ "@motiadev/core": "^0.8.0"
18
+ },
19
+ "scripts": {
20
+ "build": "rm -rf dist && tsc",
21
+ "lint": "biome check .",
22
+ "watch": "tsc --watch"
23
+ }
24
+ }