@pikku/redis 0.11.0

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,18 @@
1
+ ## 0.11.0
2
+
3
+ ### Minor Changes
4
+
5
+ - Initial release with Redis-backed channel, eventhub, and workflow stores
6
+
7
+
8
+ # @pikku-workflows/redis
9
+
10
+ ## 0.10.0
11
+
12
+ ### Major Changes
13
+
14
+ - Initial release of @pikku-workflows/redis
15
+ - Redis-based WorkflowStateService implementation
16
+ - Distributed locking with Redis SET NX
17
+ - Support for shared or owned Redis connections
18
+ - Configurable key prefixes
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # @pikku-workflows/redis
2
+
3
+ Redis-based implementation of WorkflowStateService for Pikku Workflows.
4
+
5
+ ## Features
6
+
7
+ - **RedisWorkflowService**: Redis implementation of WorkflowStateService for persistent workflow execution state
8
+ - **Fast in-memory storage**: Leverage Redis for high-performance workflow state management
9
+ - **Configurable key prefix**: Use custom key prefixes for namespace isolation (default: 'workflows')
10
+ - **Distributed locking**: Uses Redis SET NX with TTL for concurrent run safety
11
+ - **Type-safe**: Full TypeScript support
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @pikku-workflows/redis ioredis
17
+ # or
18
+ yarn add @pikku-workflows/redis ioredis
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Basic Setup (Remote Mode)
24
+
25
+ ```typescript
26
+ import Redis from 'ioredis'
27
+ import { RedisWorkflowService } from '@pikku-workflows/redis'
28
+ import { BullQueueService } from '@pikku/queue-bullmq'
29
+
30
+ // Create Redis connection
31
+ const redis = new Redis({
32
+ host: 'localhost',
33
+ port: 6379,
34
+ })
35
+
36
+ // Create queue service for remote mode
37
+ const queueService = new BullQueueService('redis://localhost:6379')
38
+
39
+ // Create workflow state service
40
+ const workflowService = new RedisWorkflowService(
41
+ redis,
42
+ queueService,
43
+ 'workflows'
44
+ )
45
+
46
+ // Initialize (verifies connection)
47
+ await workflowService.init()
48
+ ```
49
+
50
+ ### Inline Mode Setup (Testing)
51
+
52
+ For testing, pass `undefined` as the queue service to enable inline mode:
53
+
54
+ ```typescript
55
+ // Create workflow state service without queue = inline mode
56
+ const workflowService = new RedisWorkflowService(
57
+ redis,
58
+ undefined, // No queue service = inline mode
59
+ 'workflows'
60
+ )
61
+
62
+ await workflowService.init()
63
+ ```
64
+
65
+ ### Custom Key Prefix
66
+
67
+ ```typescript
68
+ // Use a custom key prefix
69
+ const workflowService = new RedisWorkflowService(
70
+ redis,
71
+ queueService,
72
+ 'myapp_workflows'
73
+ )
74
+ await workflowService.init()
75
+ ```
76
+
77
+ ### With Connection String
78
+
79
+ ```typescript
80
+ // Create service with Redis connection string
81
+ const workflowService = new RedisWorkflowService(
82
+ 'redis://localhost:6379',
83
+ queueService
84
+ )
85
+ await workflowService.init()
86
+ ```
87
+
88
+ ### With Existing Connection
89
+
90
+ ```typescript
91
+ // Share connection with other services
92
+ const redis = new Redis('redis://localhost:6379')
93
+ const workflowService = new RedisWorkflowService(redis, queueService)
94
+ // Connection is shared, won't be closed by workflowService.close()
95
+ ```
96
+
97
+ ### With Config (Owned Connection)
98
+
99
+ ```typescript
100
+ // Let service create its own connection
101
+ const workflowService = new RedisWorkflowService(
102
+ { host, port, password },
103
+ queueService
104
+ )
105
+ await workflowService.init()
106
+ // Later...
107
+ await workflowService.close() // Closes the connection
108
+ ```
109
+
110
+ ## Redis Data Structure
111
+
112
+ The service uses the following Redis data structures:
113
+
114
+ ### Workflow Runs
115
+
116
+ - **Hash**: `{keyPrefix}:run:{runId}` - Stores workflow run data
117
+ - `id`: Run ID
118
+ - `workflow`: Workflow name
119
+ - `status`: Current status ('running', 'completed', 'failed', 'cancelled')
120
+ - `input`: JSON string of input data
121
+ - `output`: JSON string of output data (if completed)
122
+ - `error`: JSON string of error (if failed)
123
+ - `createdAt`: Timestamp
124
+ - `updatedAt`: Timestamp
125
+
126
+ ### Workflow Steps
127
+
128
+ - **Hash**: `{keyPrefix}:step:{runId}:{stepName}` - Stores step execution data
129
+ - `stepId`: Unique step ID
130
+ - `status`: Step status ('pending', 'scheduled', 'succeeded', 'failed')
131
+ - `rpcName`: RPC function name (if RPC step)
132
+ - `data`: JSON string of step input data
133
+ - `result`: JSON string of result (if succeeded)
134
+ - `error`: JSON string of error (if failed)
135
+ - `retries`: Number of retry attempts allowed
136
+ - `retryDelay`: Delay between retries
137
+ - `attemptCount`: Current attempt number
138
+ - `createdAt`: Timestamp
139
+ - `updatedAt`: Timestamp
140
+
141
+ ### Workflow Step History
142
+
143
+ - **List**: `{keyPrefix}:history:{runId}` - Stores all step attempts in chronological order
144
+ - Each entry contains complete step state for that attempt
145
+
146
+ ### Locking
147
+
148
+ - **String**: `{keyPrefix}:lock:{runId}` - Distributed lock with TTL (30 seconds)
149
+
150
+ ## API
151
+
152
+ ### RedisWorkflowService
153
+
154
+ Extends `WorkflowStateService` from `@pikku/core/workflow`.
155
+
156
+ #### Constructor
157
+
158
+ ```typescript
159
+ new RedisWorkflowService(
160
+ connectionOrConfig: Redis | RedisOptions | string,
161
+ queue?: any,
162
+ keyPrefix?: string
163
+ )
164
+ ```
165
+
166
+ - `connectionOrConfig`: ioredis Redis instance, RedisOptions config, or connection string
167
+ - `queue`: Optional queue service for remote workflow execution
168
+ - `keyPrefix`: Redis key prefix (default: 'workflows')
169
+
170
+ #### Methods
171
+
172
+ - `init()`: Initialize the service (verifies Redis connection)
173
+ - `createRun(workflowName, input)`: Create a new workflow run
174
+ - `getRun(id)`: Get workflow run by ID
175
+ - `getRunHistory(runId)`: Get all step attempts in chronological order
176
+ - `updateRunStatus(id, status, output?, error?)`: Update run status
177
+ - `insertStepState(runId, stepName, rpcName, data, stepOptions?)`: Insert initial step state
178
+ - `getStepState(runId, stepName)`: Get step state with attempt count
179
+ - `setStepScheduled(stepId)`: Mark step as scheduled
180
+ - `setStepRunning(stepId)`: Mark step as running
181
+ - `setStepResult(stepId, result)`: Store step result and mark as succeeded
182
+ - `setStepError(stepId, error)`: Store step error and mark as failed
183
+ - `createRetryAttempt(failedStepId)`: Create a new retry attempt for a failed step
184
+ - `withRunLock(id, fn)`: Execute function with distributed lock
185
+ - `close()`: Close Redis connection (if owned)
186
+
187
+ ## Documentation
188
+
189
+ For complete workflow documentation, see [pikku.dev/docs/workflows](https://pikku.dev/docs/workflows)
190
+
191
+ ## Locking Behavior
192
+
193
+ The `withRunLock` method uses Redis SET NX with a 30-second TTL for distributed locking:
194
+
195
+ - Retries up to 10 times with 100ms delay between attempts
196
+ - Automatically releases lock after function execution
197
+ - Uses Lua script to ensure only the lock owner can release it
198
+ - Lock automatically expires after 30 seconds if not released
199
+
200
+ ## License
201
+
202
+ MIT
@@ -0,0 +1,3 @@
1
+ export { RedisChannelStore } from './redis-channel-store.js';
2
+ export { RedisEventHubStore } from './redis-eventhub-store.js';
3
+ export { RedisWorkflowService } from './redis-workflow-service.js';
@@ -0,0 +1,4 @@
1
+ export { RedisChannelStore } from './redis-channel-store.js';
2
+ export { RedisEventHubStore } from './redis-eventhub-store.js';
3
+ export { RedisWorkflowService } from './redis-workflow-service.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA"}
@@ -0,0 +1,18 @@
1
+ import { CoreUserSession } from '@pikku/core';
2
+ import { Channel, ChannelStore } from '@pikku/core/channel';
3
+ import { Redis, type RedisOptions } from 'ioredis';
4
+ export declare class RedisChannelStore extends ChannelStore {
5
+ private redis;
6
+ private keyPrefix;
7
+ private ownsConnection;
8
+ constructor(connectionOrConfig: Redis | RedisOptions | string, keyPrefix?: string);
9
+ init(): Promise<void>;
10
+ private channelKey;
11
+ addChannel({ channelId, channelName, openingData, }: Channel): Promise<void>;
12
+ removeChannels(channelIds: string[]): Promise<void>;
13
+ setUserSession(channelId: string, session: CoreUserSession | null): Promise<void>;
14
+ getChannelAndSession(channelId: string): Promise<Channel & {
15
+ session: CoreUserSession;
16
+ }>;
17
+ close(): Promise<void>;
18
+ }
@@ -0,0 +1,73 @@
1
+ import { ChannelStore } from '@pikku/core/channel';
2
+ import { Redis } from 'ioredis';
3
+ export class RedisChannelStore extends ChannelStore {
4
+ redis;
5
+ keyPrefix;
6
+ ownsConnection;
7
+ constructor(connectionOrConfig, keyPrefix = 'pikku') {
8
+ super();
9
+ this.keyPrefix = keyPrefix;
10
+ if (connectionOrConfig instanceof Redis) {
11
+ this.redis = connectionOrConfig;
12
+ this.ownsConnection = false;
13
+ }
14
+ else if (typeof connectionOrConfig === 'string') {
15
+ this.redis = new Redis(connectionOrConfig);
16
+ this.ownsConnection = true;
17
+ }
18
+ else {
19
+ this.redis = new Redis(connectionOrConfig);
20
+ this.ownsConnection = true;
21
+ }
22
+ }
23
+ async init() {
24
+ await this.redis.ping();
25
+ }
26
+ channelKey(channelId) {
27
+ return `${this.keyPrefix}:channel:${channelId}`;
28
+ }
29
+ async addChannel({ channelId, channelName, openingData, }) {
30
+ const key = this.channelKey(channelId);
31
+ await this.redis.hmset(key, 'channelId', channelId, 'channelName', channelName, 'openingData', JSON.stringify(openingData || {}), 'createdAt', Date.now().toString());
32
+ }
33
+ async removeChannels(channelIds) {
34
+ if (channelIds.length === 0) {
35
+ return;
36
+ }
37
+ const pipeline = this.redis.pipeline();
38
+ for (const channelId of channelIds) {
39
+ const key = this.channelKey(channelId);
40
+ pipeline.del(key);
41
+ pipeline.del(`${this.keyPrefix}:subs:${channelId}`);
42
+ }
43
+ await pipeline.exec();
44
+ }
45
+ async setUserSession(channelId, session) {
46
+ const key = this.channelKey(channelId);
47
+ if (session) {
48
+ await this.redis.hset(key, 'userSession', JSON.stringify(session));
49
+ }
50
+ else {
51
+ await this.redis.hdel(key, 'userSession');
52
+ }
53
+ }
54
+ async getChannelAndSession(channelId) {
55
+ const key = this.channelKey(channelId);
56
+ const data = await this.redis.hgetall(key);
57
+ if (!data.channelId) {
58
+ throw new Error(`Channel not found: ${channelId}`);
59
+ }
60
+ return {
61
+ channelId: data.channelId,
62
+ channelName: data.channelName,
63
+ openingData: data.openingData ? JSON.parse(data.openingData) : {},
64
+ session: data.userSession ? JSON.parse(data.userSession) : {},
65
+ };
66
+ }
67
+ async close() {
68
+ if (this.ownsConnection) {
69
+ await this.redis.quit();
70
+ }
71
+ }
72
+ }
73
+ //# sourceMappingURL=redis-channel-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-channel-store.js","sourceRoot":"","sources":["../../src/redis-channel-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,KAAK,EAAqB,MAAM,SAAS,CAAA;AAalD,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IACzC,KAAK,CAAO;IACZ,SAAS,CAAQ;IACjB,cAAc,CAAS;IAM/B,YACE,kBAAiD,EACjD,SAAS,GAAG,OAAO;QAEnB,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAG1B,IAAI,kBAAkB,YAAY,KAAK,EAAE,CAAC;YACxC,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAA;YAC/B,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;QAC7B,CAAC;aAAM,IAAI,OAAO,kBAAkB,KAAK,QAAQ,EAAE,CAAC;YAElD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;YAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;aAAM,CAAC;YAEN,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;YAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;IACH,CAAC;IAKM,KAAK,CAAC,IAAI;QAGf,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IACzB,CAAC;IAEO,UAAU,CAAC,SAAiB;QAClC,OAAO,GAAG,IAAI,CAAC,SAAS,YAAY,SAAS,EAAE,CAAA;IACjD,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,EACtB,SAAS,EACT,WAAW,EACX,WAAW,GACH;QACR,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAEtC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CACpB,GAAG,EACH,WAAW,EACX,SAAS,EACT,aAAa,EACb,WAAW,EACX,aAAa,EACb,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,EACjC,WAAW,EACX,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CACtB,CAAA;IACH,CAAC;IAEM,KAAK,CAAC,cAAc,CAAC,UAAoB;QAC9C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QAEtC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;YACtC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAEjB,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,SAAS,SAAS,EAAE,CAAC,CAAA;QACrD,CAAC;QAED,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IACvB,CAAC;IAEM,KAAK,CAAC,cAAc,CACzB,SAAiB,EACjB,OAA+B;QAE/B,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAEtC,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;QACpE,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAC/B,SAAiB;QAEjB,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QACtC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAE1C,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAA;QACpD,CAAC;QAED,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,SAAU;YAC1B,WAAW,EAAE,IAAI,CAAC,WAAY;YAC9B,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;YACjE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;SAC9D,CAAA;IACH,CAAC;IAKM,KAAK,CAAC,KAAK;QAChB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACzB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ import { EventHubStore } from '@pikku/core/channel';
2
+ import { Redis, type RedisOptions } from 'ioredis';
3
+ export declare class RedisEventHubStore implements EventHubStore {
4
+ private redis;
5
+ private keyPrefix;
6
+ private ownsConnection;
7
+ constructor(connectionOrConfig: Redis | RedisOptions | string, keyPrefix?: string);
8
+ init(): Promise<void>;
9
+ private topicKey;
10
+ private channelSubsKey;
11
+ getChannelIdsForTopic(topic: string): Promise<string[]>;
12
+ subscribe(topic: string, channelId: string): Promise<boolean>;
13
+ unsubscribe(topic: string, channelId: string): Promise<boolean>;
14
+ close(): Promise<void>;
15
+ }
@@ -0,0 +1,58 @@
1
+ import { Redis } from 'ioredis';
2
+ export class RedisEventHubStore {
3
+ redis;
4
+ keyPrefix;
5
+ ownsConnection;
6
+ constructor(connectionOrConfig, keyPrefix = 'pikku') {
7
+ this.keyPrefix = keyPrefix;
8
+ if (connectionOrConfig instanceof Redis) {
9
+ this.redis = connectionOrConfig;
10
+ this.ownsConnection = false;
11
+ }
12
+ else if (typeof connectionOrConfig === 'string') {
13
+ this.redis = new Redis(connectionOrConfig);
14
+ this.ownsConnection = true;
15
+ }
16
+ else {
17
+ this.redis = new Redis(connectionOrConfig);
18
+ this.ownsConnection = true;
19
+ }
20
+ }
21
+ async init() {
22
+ await this.redis.ping();
23
+ }
24
+ topicKey(topic) {
25
+ return `${this.keyPrefix}:topic:${topic}`;
26
+ }
27
+ channelSubsKey(channelId) {
28
+ return `${this.keyPrefix}:subs:${channelId}`;
29
+ }
30
+ async getChannelIdsForTopic(topic) {
31
+ const key = this.topicKey(topic);
32
+ return await this.redis.smembers(key);
33
+ }
34
+ async subscribe(topic, channelId) {
35
+ const topicKey = this.topicKey(topic);
36
+ const subsKey = this.channelSubsKey(channelId);
37
+ const pipeline = this.redis.pipeline();
38
+ pipeline.sadd(topicKey, channelId);
39
+ pipeline.sadd(subsKey, topic);
40
+ await pipeline.exec();
41
+ return true;
42
+ }
43
+ async unsubscribe(topic, channelId) {
44
+ const topicKey = this.topicKey(topic);
45
+ const subsKey = this.channelSubsKey(channelId);
46
+ const pipeline = this.redis.pipeline();
47
+ pipeline.srem(topicKey, channelId);
48
+ pipeline.srem(subsKey, topic);
49
+ const results = await pipeline.exec();
50
+ return results[0][1] > 0;
51
+ }
52
+ async close() {
53
+ if (this.ownsConnection) {
54
+ await this.redis.quit();
55
+ }
56
+ }
57
+ }
58
+ //# sourceMappingURL=redis-eventhub-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-eventhub-store.js","sourceRoot":"","sources":["../../src/redis-eventhub-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAqB,MAAM,SAAS,CAAA;AAclD,MAAM,OAAO,kBAAkB;IACrB,KAAK,CAAO;IACZ,SAAS,CAAQ;IACjB,cAAc,CAAS;IAM/B,YACE,kBAAiD,EACjD,SAAS,GAAG,OAAO;QAEnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAG1B,IAAI,kBAAkB,YAAY,KAAK,EAAE,CAAC;YACxC,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAA;YAC/B,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;QAC7B,CAAC;aAAM,IAAI,OAAO,kBAAkB,KAAK,QAAQ,EAAE,CAAC;YAElD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;YAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;aAAM,CAAC;YAEN,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;YAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;IACH,CAAC;IAKM,KAAK,CAAC,IAAI;QAEf,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IACzB,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,OAAO,GAAG,IAAI,CAAC,SAAS,UAAU,KAAK,EAAE,CAAA;IAC3C,CAAC;IAEO,cAAc,CAAC,SAAiB;QACtC,OAAO,GAAG,IAAI,CAAC,SAAS,SAAS,SAAS,EAAE,CAAA;IAC9C,CAAC;IAEM,KAAK,CAAC,qBAAqB,CAAC,KAAa;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAChC,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IACvC,CAAC;IAEM,KAAK,CAAC,SAAS,CAAC,KAAa,EAAE,SAAiB;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;QAG9C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QACtC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;QAClC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE7B,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAErB,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,SAAiB;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;QAG9C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QACtC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;QAClC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE7B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAGrC,OAAQ,OAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,CAAY,GAAG,CAAC,CAAA;IACxC,CAAC;IAKM,KAAK,CAAC,KAAK;QAChB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACzB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ import type { SerializedError } from '@pikku/core';
2
+ import { PikkuWorkflowService, type WorkflowRun, type StepState, type WorkflowStatus } from '@pikku/core/workflow';
3
+ import { Redis, type RedisOptions } from 'ioredis';
4
+ export declare class RedisWorkflowService extends PikkuWorkflowService {
5
+ private redis;
6
+ private keyPrefix;
7
+ private ownsConnection;
8
+ private lockTTL;
9
+ constructor(connectionOrConfig: Redis | RedisOptions | string | undefined, keyPrefix?: string);
10
+ init(): Promise<void>;
11
+ private runKey;
12
+ private stepKey;
13
+ private stepHistoryKey;
14
+ private lockKey;
15
+ private saveStepHistory;
16
+ private updateCurrentHistoryRecord;
17
+ createRun(workflowName: string, input: any): Promise<string>;
18
+ getRun(id: string): Promise<WorkflowRun | null>;
19
+ updateRunStatus(id: string, status: WorkflowStatus, output?: any, error?: SerializedError): Promise<void>;
20
+ insertStepState(runId: string, stepName: string, rpcName: string, data: any, stepOptions?: {
21
+ retries?: number;
22
+ retryDelay?: string | number;
23
+ }): Promise<StepState>;
24
+ getStepState(runId: string, stepName: string): Promise<StepState>;
25
+ getRunHistory(runId: string): Promise<Array<StepState & {
26
+ stepName: string;
27
+ }>>;
28
+ setStepRunning(stepId: string): Promise<void>;
29
+ setStepScheduled(stepId: string): Promise<void>;
30
+ setStepResult(stepId: string, result: any): Promise<void>;
31
+ setStepError(stepId: string, error: Error): Promise<void>;
32
+ withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>;
33
+ createRetryAttempt(stepId: string, status: 'pending' | 'running'): Promise<StepState>;
34
+ close(): Promise<void>;
35
+ }