ioredis-streams 1.2.0 → 2.0.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.
Files changed (41) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/coverage.js +11 -0
  3. package/dist/consumer-buffer.d.ts +16 -0
  4. package/dist/consumer-buffer.js +53 -0
  5. package/dist/consumer-buffer.js.map +1 -0
  6. package/dist/events.d.ts +15 -0
  7. package/dist/events.js +30 -0
  8. package/dist/events.js.map +1 -0
  9. package/dist/index.d.ts +5 -134
  10. package/dist/index.js +9 -452
  11. package/dist/index.js.map +1 -1
  12. package/dist/redis-client.d.ts +32 -0
  13. package/dist/redis-client.js +46 -0
  14. package/dist/redis-client.js.map +1 -0
  15. package/dist/redis-streams.d.ts +19 -0
  16. package/dist/redis-streams.js +198 -0
  17. package/dist/redis-streams.js.map +1 -0
  18. package/dist/stream-consumer.d.ts +26 -0
  19. package/dist/stream-consumer.js +162 -0
  20. package/dist/stream-consumer.js.map +1 -0
  21. package/dist/types.d.ts +112 -0
  22. package/dist/types.js +3 -0
  23. package/dist/types.js.map +1 -0
  24. package/docs/superpowers/specs/2026-04-04-refactor-design.md +60 -0
  25. package/package.json +26 -4
  26. package/src/consumer-buffer.ts +60 -0
  27. package/src/events.ts +35 -0
  28. package/src/index.ts +18 -609
  29. package/src/redis-client.ts +64 -0
  30. package/src/redis-streams.ts +249 -0
  31. package/src/stream-consumer.ts +218 -0
  32. package/src/types.ts +115 -0
  33. package/test/consumer-buffer.spec.ts +154 -0
  34. package/test/events.spec.ts +104 -0
  35. package/test/helpers/fake-redis.ts +60 -0
  36. package/test/mocha.opts +2 -1
  37. package/test/redis-client.spec.ts +122 -0
  38. package/test/redis-streams.spec.ts +475 -0
  39. package/test/setup.ts +3 -0
  40. package/test/stream-consumer.spec.ts +452 -0
  41. package/.nycrc +0 -20
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Skill(code-review:code-review)",
5
+ "Skill(code-review:code-review:*)"
6
+ ]
7
+ }
8
+ }
package/coverage.js ADDED
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+ // Generates coverage report after nyc has collected data.
3
+ // The nyc 15 CLI report is broken with Node 22, so we use the programmatic API.
4
+ const NYC = require('./node_modules/nyc');
5
+ const nyc = new NYC({
6
+ cwd: __dirname,
7
+ include: ['dist-test/src/**/*.js'],
8
+ exclude: ['dist-test/src/example.js', 'dist-test/src/index.js'],
9
+ reporter: ['text', 'html'],
10
+ });
11
+ nyc.report().catch(err => { console.error(err); process.exit(1); });
@@ -0,0 +1,16 @@
1
+ export declare type ConsumerBufferConfigs = {
2
+ ack: (...ids: string[]) => Promise<void>;
3
+ process: (id: string, message: string[]) => Promise<any>;
4
+ error: (id: string, message: string[], error: Error) => Promise<void>;
5
+ mode: 'parallel' | 'serial';
6
+ size: number;
7
+ };
8
+ export declare class ConsumerBuffer {
9
+ private readonly configs;
10
+ private buffer;
11
+ private isDoing;
12
+ constructor(configs: ConsumerBufferConfigs);
13
+ get size(): number;
14
+ add(...rawMessages: [string, string[]][]): Promise<void>;
15
+ private do;
16
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConsumerBuffer = void 0;
4
+ class ConsumerBuffer {
5
+ configs;
6
+ buffer = [];
7
+ isDoing = false;
8
+ constructor(configs) {
9
+ this.configs = configs;
10
+ }
11
+ get size() {
12
+ return this.configs.size;
13
+ }
14
+ async add(...rawMessages) {
15
+ this.buffer.push(...rawMessages);
16
+ return this.do();
17
+ }
18
+ async do() {
19
+ if (this.isDoing)
20
+ return;
21
+ this.isDoing = true;
22
+ const toBeAck = [];
23
+ const operations = [];
24
+ while (this.buffer.length) {
25
+ const [id, message] = this.buffer.shift();
26
+ if (this.configs.mode === 'serial') {
27
+ const succeeded = await this.configs.process(id, message)
28
+ .then(() => true)
29
+ .catch(async (err) => {
30
+ await this.configs.error(id, message, err).catch(() => { });
31
+ return false;
32
+ });
33
+ if (succeeded)
34
+ await this.configs.ack(id);
35
+ }
36
+ else {
37
+ const operation = this.configs.process(id, message)
38
+ .then(() => { toBeAck.push(id); })
39
+ .catch(err => this.configs.error(id, message, err).catch(() => { }));
40
+ operations.push(operation);
41
+ }
42
+ }
43
+ this.isDoing = false;
44
+ if (this.configs.mode === 'parallel') {
45
+ await Promise.all(operations);
46
+ if (toBeAck.length) {
47
+ await this.configs.ack(...toBeAck);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ exports.ConsumerBuffer = ConsumerBuffer;
53
+ //# sourceMappingURL=consumer-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumer-buffer.js","sourceRoot":"","sources":["../src/consumer-buffer.ts"],"names":[],"mappings":";;;AAQA,MAAa,cAAc;IAKI;IAHrB,MAAM,GAAyB,EAAE,CAAC;IAClC,OAAO,GAAG,KAAK,CAAC;IAExB,YAA6B,OAA8B;QAA9B,YAAO,GAAP,OAAO,CAAuB;IAAG,CAAC;IAE/D,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAG,WAAiC;QAC5C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC;IACnB,CAAC;IAEO,KAAK,CAAC,EAAE;QACd,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEpB,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAoB,EAAE,CAAC;QAEvC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YACzB,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAG,CAAC;YAE3C,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE;gBAClC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC;qBACtD,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;qBAChB,KAAK,CAAC,KAAK,EAAC,GAAG,EAAC,EAAE;oBACjB,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBAC3D,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBACL,IAAI,SAAS;oBAAE,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;aAC3C;iBAAM;gBACL,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC;qBAChD,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;qBACjC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC;gBACtE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;aAC5B;SACF;QAED,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QAErB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE;YACpC,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,OAAO,CAAC,MAAM,EAAE;gBAClB,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;aACpC;SACF;IACH,CAAC;CACF;AAnDD,wCAmDC"}
@@ -0,0 +1,15 @@
1
+ import { AllowedFactories, IEvent, IWaitEvent, StreamGroupConsumer } from './types';
2
+ declare type NamedEvent<T, N extends string> = IEvent<T> & {
3
+ name: N;
4
+ };
5
+ declare type NamedWaitEvent<T, N extends string, ReplyType = any> = IWaitEvent<T> & {
6
+ name: N;
7
+ };
8
+ export declare const event: <N extends string>(name: N, v?: string) => {
9
+ of: <T>() => { [P in N]: (data: T, time?: number | undefined) => NamedEvent<T, N>; };
10
+ };
11
+ export declare const eventWithReply: <N extends string>(name: N, source: string, v?: string) => {
12
+ of: <T, ReplyType = any>() => { [P in N]: (data: T, time?: number | undefined) => NamedWaitEvent<T, N, ReplyType>; };
13
+ };
14
+ export declare const augmentEvents: <T extends AllowedFactories<R>, R>(events: T, stream: StreamGroupConsumer) => any;
15
+ export {};
package/dist/events.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.augmentEvents = exports.eventWithReply = exports.event = void 0;
4
+ const event = (name, v = '1.0.0') => {
5
+ return {
6
+ of: () => ({
7
+ [name]: (data, time = Date.now()) => ({ name, v, data, time }),
8
+ }),
9
+ };
10
+ };
11
+ exports.event = event;
12
+ const eventWithReply = (name, source, v = '1.0.0') => {
13
+ return {
14
+ of: () => ({
15
+ [name]: (data, time = Date.now()) => ({
16
+ name, v, data, time, wait: { source },
17
+ }),
18
+ }),
19
+ };
20
+ };
21
+ exports.eventWithReply = eventWithReply;
22
+ const augmentEvents = (events, stream) => {
23
+ return Object.keys(events).reduce((acc, key) => {
24
+ const factory = events[key];
25
+ acc[key] = (...args) => stream.produce(factory(...args));
26
+ return acc;
27
+ }, { ...events, ...stream });
28
+ };
29
+ exports.augmentEvents = augmentEvents;
30
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":";;;AAKO,MAAM,KAAK,GAAG,CAAmB,IAAO,EAAE,CAAC,GAAG,OAAO,EAAE,EAAE;IAE9D,OAAO;QACL,EAAE,EAAE,GAAM,EAAE,CAAC,CAAC;YACZ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAO,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SACjD,CAAA;KACnB,CAAC;AACJ,CAAC,CAAC;AAPW,QAAA,KAAK,SAOhB;AAEK,MAAM,cAAc,GAAG,CAAmB,IAAO,EAAE,MAAc,EAAE,CAAC,GAAG,OAAO,EAAE,EAAE;IAEvF,OAAO;QACL,EAAE,EAAE,GAAuB,EAAE,CAAC,CAAC;YAC7B,CAAC,IAAI,CAAC,EAAE,CAAC,IAAO,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,EAAiB,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE;aACtC,CAAC;SACyB,CAAA;KAC9B,CAAC;AACJ,CAAC,CAAC;AATW,QAAA,cAAc,kBASzB;AAEK,MAAM,aAAa,GAAG,CAC3B,MAAS,EACT,MAA2B,EAC3B,EAAE;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAc,CAAC,CAAC;QACvC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAChE,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAS,CAAC,CAAC;AACtC,CAAC,CAAC;AATW,QAAA,aAAa,iBASxB"}
package/dist/index.d.ts CHANGED
@@ -1,134 +1,5 @@
1
- import loggerFactory from '@log4js-node/log4js-api';
2
- import Redis from 'ioredis';
3
- export declare const getExistingRedisClient: (config?: {
4
- host: string;
5
- port: number;
6
- }[] | {
7
- host: string;
8
- port: number;
9
- }) => Redis.Redis | Redis.Cluster;
10
- export declare const getNewRedisClient: (config?: {
11
- host: string;
12
- port: number;
13
- }[] | {
14
- host: string;
15
- port: number;
16
- }) => Redis.Redis | Redis.Cluster;
17
- export interface IEvent<T> {
18
- time: number;
19
- name: string;
20
- v: string;
21
- data: T;
22
- }
23
- export interface IWaitEvent<T> extends IEvent<T> {
24
- wait: {
25
- source: string;
26
- };
27
- }
28
- export declare class RedisStreams {
29
- protected peerName: string;
30
- protected groups: Map<string, Map<string, StreamGroupConsumer>>;
31
- protected config: RedisStreamsConfig;
32
- constructor(peerName: string, config?: Partial<RedisStreamsInputConfig>);
33
- private getConsumerRedis;
34
- private getProducerRedis;
35
- private static getDefaultConfigs;
36
- private buildConsumerConfigs;
37
- private register;
38
- private getStreamsMap;
39
- group(groupName: string): ConsumerGroup;
40
- private stream;
41
- private _currentPipeline;
42
- private getRedisPipeline;
43
- private doProduce;
44
- }
45
- export declare type DeadLetterEvent = IEvent<{
46
- id: string;
47
- message: string[];
48
- stream: string;
49
- group: string;
50
- }>;
51
- export declare type RedisStreamsConfig = {
52
- redis: {
53
- host: string;
54
- port: number;
55
- };
56
- logger: loggerFactory.Logger;
57
- deadLetters?: {
58
- stream: string;
59
- maxRetries: number;
60
- maxSize: number;
61
- };
62
- };
63
- export declare type RedisStreamsInputConfig = Omit<RedisStreamsConfig, 'redis'> & {
64
- redis: {
65
- host: string;
66
- port: number;
67
- } | {
68
- cluster: string;
69
- };
70
- };
71
- export declare type StreamConfigs = {
72
- readBlockTime: number;
73
- claimIdleTime: number;
74
- batchSize: number;
75
- mode: 'parallel' | 'serial';
76
- maxLen: number;
77
- };
78
- export declare type ProduceFunc = (...events: IEvent<any>[]) => {
79
- produceMany: ProduceFunc;
80
- flush: () => Promise<void>;
81
- };
82
- export declare type StreamGroupConsumer = ConsumeFunctions & {
83
- handle: HandleFunction<any>;
84
- produce: (...events: (IEvent<any> | IWaitEvent<any>)[]) => Promise<void> | {
85
- wait: (timeout: number) => Promise<any>;
86
- };
87
- produceMany: ProduceFunc;
88
- with: <O extends AllowedFactories<O>>(events: O) => WithTypedHandlers<O>;
89
- };
90
- export declare type ConsumerGroup = {
91
- stream: (streamName: string, config?: Partial<StreamConfigs>) => StreamGroupConsumer;
92
- };
93
- declare type NamedEvent<T, N extends string> = IEvent<T> & {
94
- name: N;
95
- };
96
- declare type NamedWaitEvent<T, N extends string, ReplyType = any> = IWaitEvent<T> & {
97
- name: N;
98
- };
99
- declare type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
100
- declare type ReplyTypeOf<T> = T extends NamedWaitEvent<any, string, infer Reply> ? Reply : any;
101
- declare type DataOfHandler<T> = T extends (...args: any[]) => IEvent<infer R> ? R : any;
102
- declare type ArgsOf<T> = T extends (...args: infer Args) => any ? Args : never;
103
- declare type AllowedFactories<T> = {
104
- [name in (keyof T)]: (...args: any[]) => IEvent<DataOfHandler<T[name]>> | IWaitEvent<DataOfHandler<T[name]>>;
105
- };
106
- export declare type NamedEventHandler<E = IEvent<any>> = (id: string, event: E) => Promise<any>;
107
- export declare type ConsumeFunctions = {
108
- consume: () => Promise<{
109
- stop: () => void;
110
- continue: () => void;
111
- }>;
112
- };
113
- export declare type HandleFunction<T> = <N extends (keyof T | '*')>(event: N, handler: NamedEventHandler<N extends keyof T ? ReturnType<T[N]> : IEvent<any>>) => {
114
- handle: HandleFunction<T>;
115
- } & ConsumeFunctions;
116
- declare type WithTypedHandlers<T> = PromisifiedFunctionsMap<T> & {
117
- handle: HandleFunction<T>;
118
- } & Omit<StreamGroupConsumer, 'handle'>;
119
- declare type PromisifiedFunctionsMap<T> = {
120
- [func in keyof T]: (...args: ArgsOf<T[func]>) => ReturnType<T[func]> extends {
121
- wait: {
122
- source: string;
123
- };
124
- } ? {
125
- wait: (timeout: number) => Promise<ReplyTypeOf<ReturnType<T[func]>>>;
126
- } : Promise<void>;
127
- };
128
- export declare const event: <N extends string>(name: N, v?: string) => {
129
- of: <T>() => { [K in N]: (data: T, time?: number | undefined) => NamedEvent<T, N>; };
130
- };
131
- export declare const eventWithReply: <N extends string>(name: N, source: string, v?: string) => {
132
- of: <T, ReplyType = any>() => { [K in N]: (data: T, time?: number | undefined) => NamedWaitEvent<T, N, ReplyType>; };
133
- };
134
- export {};
1
+ export { RedisStreams } from './redis-streams';
2
+ export { getNewRedisClient, getExistingRedisClient } from './redis-client';
3
+ export type { IRedisClient, RedisConfig } from './redis-client';
4
+ export { event, eventWithReply } from './events';
5
+ export type { IEvent, IWaitEvent, DeadLetterEvent, RedisStreamsConfig, RedisStreamsInputConfig, StreamConfigs, StreamGroupConsumer, ConsumerGroup, ConsumeFunctions, HandleFunction, ProduceFunc, NamedEventHandler, } from './types';