diff --git a/dist/index.cjs b/dist/index.cjs index c23ac1d..881bc11 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -31,6 +31,7 @@ __export(src_exports, { SINGLETON_KEY: () => SINGLETON_KEY, SyncLocker: () => SyncLocker, ZError: () => ZError, + ZRedisClient: () => ZRedisClient, createAsyncQueue: () => createAsyncQueue, createAsyncQueues: () => createAsyncQueues, singleton: () => singleton @@ -159,11 +160,262 @@ var DeferredPromise = class { }); } }; + +// src/redis/ZRedisClient.ts +var import_redis = require("redis"); +var import_util = require("util"); +var ZRedisClient = class { + constructor(opts) { + this.subscriptions = {}; + this.handleSubscription = (channel, message) => { + if (this.subscriptions[channel]) { + for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) { + this.subscriptions[channel][i](JSON.parse(message)); + } + } + }; + this.sub = (0, import_redis.createClient)(opts); + this.pub = (0, import_redis.createClient)(opts); + this.sub.setMaxListeners(0); + this.subscribeAsync = (0, import_util.promisify)(this.sub.subscribe).bind(this.sub); + this.unsubscribeAsync = (0, import_util.promisify)(this.sub.unsubscribe).bind(this.sub); + this.publishAsync = (0, import_util.promisify)(this.pub.publish).bind(this.pub); + this.smembersAsync = (0, import_util.promisify)(this.pub.smembers).bind(this.pub); + this.sismemberAsync = (0, import_util.promisify)(this.pub.sismember).bind(this.pub); + this.hlenAsync = (0, import_util.promisify)(this.pub.hlen).bind(this.pub); + this.hgetAsync = (0, import_util.promisify)(this.pub.hget).bind(this.pub); + this.pubsubAsync = (0, import_util.promisify)(this.pub.pubsub).bind(this.pub); + this.decrAsync = (0, import_util.promisify)(this.pub.decr).bind(this.pub); + this.incrAsync = (0, import_util.promisify)(this.pub.incr).bind(this.pub); + } + async subscribe(topic, callback) { + if (!this.subscriptions[topic]) { + this.subscriptions[topic] = []; + } + this.subscriptions[topic].push(callback); + if (this.sub.listeners("message").length === 0) { + this.sub.addListener("message", this.handleSubscription); + } + await this.subscribeAsync(topic); + return this; + } + async unsubscribe(topic, callback) { + if (callback) { + const index = this.subscriptions[topic].indexOf(callback); + this.subscriptions[topic].splice(index, 1); + } else { + this.subscriptions[topic] = []; + } + if (this.subscriptions[topic].length === 0) { + await this.unsubscribeAsync(topic); + } + return this; + } + async publish(topic, data) { + if (data === void 0) { + data = false; + } + await this.publishAsync(topic, JSON.stringify(data)); + } + async exists(roomId) { + return (await this.pubsubAsync("channels", roomId)).length > 0; + } + async setex(key, value, seconds) { + return new Promise((resolve) => this.pub.setex(key, seconds, value, resolve)); + } + async expire(key, seconds) { + return new Promise((resolve) => this.pub.expire(key, seconds, resolve)); + } + async get(key) { + return new Promise((resolve, reject) => { + this.pub.get(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async set(key, val) { + return new Promise((resolve) => { + this.pub.set(key, val, () => { + resolve && resolve(""); + }); + }); + } + async del(roomId) { + return new Promise((resolve) => { + this.pub.del(roomId, resolve); + }); + } + async sadd(key, value) { + return new Promise((resolve) => { + this.pub.sadd(key, value, resolve); + }); + } + async smembers(key) { + return await this.smembersAsync(key); + } + async sismember(key, field) { + return await this.sismemberAsync(key, field); + } + async srem(key, value) { + return new Promise((resolve) => { + this.pub.srem(key, value, resolve); + }); + } + async scard(key) { + return new Promise((resolve, reject) => { + this.pub.scard(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async srandmember(key) { + return new Promise((resolve, reject) => { + this.pub.srandmember(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async sinter(...keys) { + return new Promise((resolve, reject) => { + this.pub.sinter(...keys, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zadd(key, value, member) { + return new Promise((resolve) => { + this.pub.zadd(key, value, member, resolve); + }); + } + async zincrby(key, value, member) { + return new Promise((resolve) => { + this.pub.zincrby(key, value, member, resolve); + }); + } + async zrangebyscore(key, min, max) { + return new Promise((resolve, reject) => { + this.pub.zrangebyscore(key, min, max, "withscores", (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zcard(key) { + return new Promise((resolve, reject) => { + this.pub.zcard(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zcount(key, min, max) { + return new Promise((resolve, reject) => { + this.pub.zcount(key, min, max, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zrevrank(key, member) { + return new Promise((resolve, reject) => { + this.pub.zrevrank(key, member, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zscore(key, member) { + return new Promise((resolve, reject) => { + this.pub.zscore(key, member, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zrevrange(key, start, end) { + return new Promise((resolve, reject) => { + this.pub.zrevrange(key, start, end, "withscores", (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async hset(key, field, value) { + return new Promise((resolve) => { + this.pub.hset(key, field, value, resolve); + }); + } + async hincrby(key, field, value) { + return new Promise((resolve) => { + this.pub.hincrby(key, field, value, resolve); + }); + } + async hget(key, field) { + return await this.hgetAsync(key, field); + } + async hgetall(key) { + return new Promise((resolve, reject) => { + this.pub.hgetall(key, (err, values) => { + if (err) { + return reject(err); + } + resolve(values); + }); + }); + } + async hdel(key, field) { + return new Promise((resolve, reject) => { + this.pub.hdel(key, field, (err, ok) => { + if (err) { + return reject(err); + } + resolve(ok); + }); + }); + } + async hlen(key) { + return await this.hlenAsync(key); + } + async incr(key) { + return await this.incrAsync(key); + } + async decr(key) { + return await this.decrAsync(key); + } +}; +ZRedisClient = __decorateClass([ + singleton +], ZRedisClient); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SINGLETON_KEY, SyncLocker, ZError, + ZRedisClient, createAsyncQueue, createAsyncQueues, singleton diff --git a/dist/index.cjs.map b/dist/index.cjs.map index 4ec5403..2a85cfa 100644 --- a/dist/index.cjs.map +++ b/dist/index.cjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/common/ZError.ts","../src/decorators/singleton.ts","../src/common/SyncLocker.ts","../src/common/AsyncQueue.ts"],"sourcesContent":["export { ZError } from './common/ZError'\nexport { SyncLocker } from './common/SyncLocker'\nexport * from './decorators/singleton'\nexport * from './common/AsyncQueue'","\nexport class ZError implements Error {\n code: string\n statusCode?: number\n message: string\n name: string\n\n constructor(statusCode: number, message: string) {\n this.statusCode = statusCode\n this.message = message\n }\n}\n","/**\n * 单例化一个class\n * 使用方法:\n * @singleton\n * class Test {}\n * new Test() === new Test() // returns `true`\n * 也可以不使用 decorator\n * const TestSingleton = singleton(Test)\n * new TestSingleton() === new TestSingleton() //returns 'true'\n */\n\nexport const SINGLETON_KEY = Symbol()\n\nexport type Singleton any> = T & {\n [SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never\n}\nexport const singleton = any>(classTarget: T) =>\n new Proxy(classTarget, {\n construct(target: Singleton, argumentsList, newTarget) {\n // Skip proxy for children\n if (target.prototype !== newTarget.prototype) {\n return Reflect.construct(target, argumentsList, newTarget)\n }\n if (!target[SINGLETON_KEY]) {\n target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)\n }\n return target[SINGLETON_KEY]\n },\n })\n","import { singleton } from 'decorators/singleton'\nimport { ZError } from './ZError'\n\ninterface IRequest {\n method: string\n url: string\n user?: {\n id: string\n }\n}\n\n@singleton\nexport class SyncLocker {\n map: Map = new Map()\n\n public lock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n return false\n }\n this.map.set(key, true)\n return true\n }\n\n public unlock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n this.map.delete(key)\n }\n\n public checkLock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n throw new ZError(100, 'request too fast')\n }\n this.lock(req)\n return true\n }\n\n public isLocked(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n return this.map.has(key)\n }\n}\n","type Callback = () => Promise\n\nexport type AsyncQueue = {\n push: (task: Callback) => Promise\n flush: () => Promise\n size: number\n}\n\n/**\n * Ensures that each callback pushed onto the queue is executed in series.\n * Such a quetie 😻\n * @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple\n * tasks are pushed onto the queue while there is an active task, only the\n * last one will be executed, once the active task has completed.\n * e.g. in the below example, only 0 and 3 will be executed.\n * ```\n * const queue = createAsyncQueue({ dedupeConcurrent: true })\n * queue.push(async () => console.log(0)) // returns 0\n * queue.push(async () => console.log(1)) // returns 3\n * queue.push(async () => console.log(2)) // returns 3\n * queue.push(async () => console.log(3)) // returns 3\n * ```\n * */\nexport function createAsyncQueue(opts = { dedupeConcurrent: false }): AsyncQueue {\n const { dedupeConcurrent } = opts\n let queue: Callback[] = []\n let running: Promise | undefined\n let nextPromise = new DeferredPromise()\n const push = (task: Callback) => {\n let taskPromise = new DeferredPromise()\n if (dedupeConcurrent) {\n queue = []\n if (nextPromise.started) nextPromise = new DeferredPromise()\n taskPromise = nextPromise\n }\n queue.push(() => {\n taskPromise.started = true\n task().then(taskPromise.resolve).catch(taskPromise.reject)\n return taskPromise.promise\n })\n if (!running) running = start()\n return taskPromise.promise\n }\n const start = async () => {\n while (queue.length) {\n const task = queue.shift()!\n await task().catch(() => {})\n }\n running = undefined\n }\n return {\n push,\n flush: () => running || Promise.resolve(),\n get size() {\n return queue.length\n },\n }\n}\n\nexport const createAsyncQueues = (opts = { dedupeConcurrent: false }) => {\n const queues: { [queueId: string]: AsyncQueue } = {}\n const push = (queueId: string, task: Callback) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].push(task)\n }\n const flush = (queueId: string) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].flush()\n }\n return { push, flush }\n}\n\nclass DeferredPromise {\n started = false\n resolve: (x: T | PromiseLike) => void = () => {}\n reject: (x: E) => void = () => {}\n promise: Promise\n\n constructor() {\n this.promise = new Promise((res, rej) => {\n this.resolve = res\n this.reject = rej\n })\n }\n}\n\n// function main() {\n// const queue = createAsyncQueue()\n// queue.push(async () => {\n// console.log(0)\n// }) // returns 0\n// queue.push(async () => {\n// console.log(1)\n\n// return new Promise((resolve, reject) => {\n// setTimeout(() => {\n// console.log('12')\n// resolve()\n// }, 1000)\n// })\n// }) // returns 3\n// queue.push(async () => console.log(2)) // returns 3\n// queue.push(async () => console.log(3)) // returns 3\n// console.log('hi')\n// }\n\n// main()\n"],"mappings":"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","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/common/ZError.ts","../src/decorators/singleton.ts","../src/common/SyncLocker.ts","../src/common/AsyncQueue.ts","../src/redis/ZRedisClient.ts"],"sourcesContent":["export { ZError } from './common/ZError'\nexport { SyncLocker } from './common/SyncLocker'\nexport * from './decorators/singleton'\nexport * from './common/AsyncQueue'\nexport { ZRedisClient } from './redis/ZRedisClient'","\nexport class ZError implements Error {\n code: string\n statusCode?: number\n message: string\n name: string\n\n constructor(statusCode: number, message: string) {\n this.statusCode = statusCode\n this.message = message\n }\n}\n","/**\n * 单例化一个class\n * 使用方法:\n * @singleton\n * class Test {}\n * new Test() === new Test() // returns `true`\n * 也可以不使用 decorator\n * const TestSingleton = singleton(Test)\n * new TestSingleton() === new TestSingleton() //returns 'true'\n */\n\nexport const SINGLETON_KEY = Symbol()\n\nexport type Singleton any> = T & {\n [SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never\n}\nexport const singleton = any>(classTarget: T) =>\n new Proxy(classTarget, {\n construct(target: Singleton, argumentsList, newTarget) {\n // Skip proxy for children\n if (target.prototype !== newTarget.prototype) {\n return Reflect.construct(target, argumentsList, newTarget)\n }\n if (!target[SINGLETON_KEY]) {\n target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)\n }\n return target[SINGLETON_KEY]\n },\n })\n","import { singleton } from 'decorators/singleton'\nimport { ZError } from './ZError'\n\ninterface IRequest {\n method: string\n url: string\n user?: {\n id: string\n }\n}\n\n@singleton\nexport class SyncLocker {\n map: Map = new Map()\n\n public lock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n return false\n }\n this.map.set(key, true)\n return true\n }\n\n public unlock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n this.map.delete(key)\n }\n\n public checkLock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n throw new ZError(100, 'request too fast')\n }\n this.lock(req)\n return true\n }\n\n public isLocked(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n return this.map.has(key)\n }\n}\n","type Callback = () => Promise\n\nexport type AsyncQueue = {\n push: (task: Callback) => Promise\n flush: () => Promise\n size: number\n}\n\n/**\n * Ensures that each callback pushed onto the queue is executed in series.\n * Such a quetie 😻\n * @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple\n * tasks are pushed onto the queue while there is an active task, only the\n * last one will be executed, once the active task has completed.\n * e.g. in the below example, only 0 and 3 will be executed.\n * ```\n * const queue = createAsyncQueue({ dedupeConcurrent: true })\n * queue.push(async () => console.log(0)) // returns 0\n * queue.push(async () => console.log(1)) // returns 3\n * queue.push(async () => console.log(2)) // returns 3\n * queue.push(async () => console.log(3)) // returns 3\n * ```\n * */\nexport function createAsyncQueue(opts = { dedupeConcurrent: false }): AsyncQueue {\n const { dedupeConcurrent } = opts\n let queue: Callback[] = []\n let running: Promise | undefined\n let nextPromise = new DeferredPromise()\n const push = (task: Callback) => {\n let taskPromise = new DeferredPromise()\n if (dedupeConcurrent) {\n queue = []\n if (nextPromise.started) nextPromise = new DeferredPromise()\n taskPromise = nextPromise\n }\n queue.push(() => {\n taskPromise.started = true\n task().then(taskPromise.resolve).catch(taskPromise.reject)\n return taskPromise.promise\n })\n if (!running) running = start()\n return taskPromise.promise\n }\n const start = async () => {\n while (queue.length) {\n const task = queue.shift()!\n await task().catch(() => {})\n }\n running = undefined\n }\n return {\n push,\n flush: () => running || Promise.resolve(),\n get size() {\n return queue.length\n },\n }\n}\n\nexport const createAsyncQueues = (opts = { dedupeConcurrent: false }) => {\n const queues: { [queueId: string]: AsyncQueue } = {}\n const push = (queueId: string, task: Callback) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].push(task)\n }\n const flush = (queueId: string) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].flush()\n }\n return { push, flush }\n}\n\nclass DeferredPromise {\n started = false\n resolve: (x: T | PromiseLike) => void = () => {}\n reject: (x: E) => void = () => {}\n promise: Promise\n\n constructor() {\n this.promise = new Promise((res, rej) => {\n this.resolve = res\n this.reject = rej\n })\n }\n}\n\n// function main() {\n// const queue = createAsyncQueue()\n// queue.push(async () => {\n// console.log(0)\n// }) // returns 0\n// queue.push(async () => {\n// console.log(1)\n\n// return new Promise((resolve, reject) => {\n// setTimeout(() => {\n// console.log('12')\n// resolve()\n// }, 1000)\n// })\n// }) // returns 3\n// queue.push(async () => console.log(2)) // returns 3\n// queue.push(async () => console.log(3)) // returns 3\n// console.log('hi')\n// }\n\n// main()\n","import { singleton } from 'decorators/singleton'\nimport { ClientOpts, RedisClient, createClient } from 'redis'\nimport { promisify } from 'util'\n\n\ntype Callback = (...args: any[]) => void\n\n@singleton\nexport class ZRedisClient {\n public pub: RedisClient\n public sub: RedisClient\n\n protected subscribeAsync: any\n protected unsubscribeAsync: any\n protected publishAsync: any\n\n protected subscriptions: { [channel: string]: Callback[] } = {}\n\n protected smembersAsync: any\n protected sismemberAsync: any\n protected hgetAsync: any\n protected hlenAsync: any\n protected pubsubAsync: any\n protected incrAsync: any\n protected decrAsync: any\n\n constructor(opts?: ClientOpts) {\n this.sub = createClient(opts)\n this.pub = createClient(opts)\n\n // no listener limit\n this.sub.setMaxListeners(0)\n\n // create promisified pub/sub methods.\n this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub)\n this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub)\n\n this.publishAsync = promisify(this.pub.publish).bind(this.pub)\n\n // create promisified redis methods.\n this.smembersAsync = promisify(this.pub.smembers).bind(this.pub)\n this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub)\n this.hlenAsync = promisify(this.pub.hlen).bind(this.pub)\n this.hgetAsync = promisify(this.pub.hget).bind(this.pub)\n this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub)\n this.decrAsync = promisify(this.pub.decr).bind(this.pub)\n this.incrAsync = promisify(this.pub.incr).bind(this.pub)\n }\n\n public async subscribe(topic: string, callback: Callback) {\n if (!this.subscriptions[topic]) {\n this.subscriptions[topic] = []\n }\n\n this.subscriptions[topic].push(callback)\n\n if (this.sub.listeners('message').length === 0) {\n this.sub.addListener('message', this.handleSubscription)\n }\n\n await this.subscribeAsync(topic)\n\n return this\n }\n\n public async unsubscribe(topic: string, callback?: Callback) {\n if (callback) {\n const index = this.subscriptions[topic].indexOf(callback)\n this.subscriptions[topic].splice(index, 1)\n } else {\n this.subscriptions[topic] = []\n }\n\n if (this.subscriptions[topic].length === 0) {\n await this.unsubscribeAsync(topic)\n }\n\n return this\n }\n\n public async publish(topic: string, data: any) {\n if (data === undefined) {\n data = false\n }\n\n await this.publishAsync(topic, JSON.stringify(data))\n }\n\n public async exists(roomId: string): Promise {\n return (await this.pubsubAsync('channels', roomId)).length > 0\n }\n\n public async setex(key: string, value: string, seconds: number) {\n return new Promise(resolve => this.pub.setex(key, seconds, value, resolve))\n }\n\n public async expire(key: string, seconds: number) {\n return new Promise(resolve => this.pub.expire(key, seconds, resolve))\n }\n\n public async get(key: string): Promise {\n return new Promise((resolve, reject) => {\n this.pub.get(key, (err, data: string | null) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async set(key: string, val: string) {\n return new Promise(resolve => {\n this.pub.set(key, val, () => {\n resolve && resolve('')\n })\n })\n }\n\n public async del(roomId: string) {\n return new Promise(resolve => {\n this.pub.del(roomId, resolve)\n })\n }\n\n public async sadd(key: string, value: any) {\n return new Promise(resolve => {\n this.pub.sadd(key, value, resolve)\n })\n }\n\n public async smembers(key: string): Promise {\n return await this.smembersAsync(key)\n }\n\n public async sismember(key: string, field: string): Promise {\n return await this.sismemberAsync(key, field)\n }\n\n public async srem(key: string, value: any) {\n return new Promise(resolve => {\n this.pub.srem(key, value, resolve)\n })\n }\n\n public async scard(key: string) {\n return new Promise((resolve, reject) => {\n this.pub.scard(key, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n public async srandmember(key: string) {\n return new Promise((resolve, reject) => {\n this.pub.srandmember(key, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async sinter(...keys: string[]) {\n return new Promise((resolve, reject) => {\n this.pub.sinter(...keys, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zadd(key: string, value: any, member: string) {\n return new Promise(resolve => {\n this.pub.zadd(key, value, member, resolve)\n })\n }\n\n public async zincrby(key: string, value: any, member: string) {\n return new Promise(resolve => {\n this.pub.zincrby(key, value, member, resolve)\n })\n }\n\n public async zrangebyscore(key: string, min: number, max: number) {\n return new Promise((resolve, reject) => {\n this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zcard(key: string) {\n return new Promise((resolve, reject) => {\n this.pub.zcard(key, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zcount(key: string, min: number, max: number) {\n return new Promise((resolve, reject) => {\n this.pub.zcount(key, min, max, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zrevrank(key: string, member: string) {\n return new Promise((resolve, reject) => {\n this.pub.zrevrank(key, member, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zscore(key: string, member: string) {\n return new Promise((resolve, reject) => {\n this.pub.zscore(key, member, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zrevrange(key: string, start: number, end: number): Promise {\n return new Promise((resolve, reject) => {\n this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async hset(key: string, field: string, value: string) {\n return new Promise(resolve => {\n this.pub.hset(key, field, value, resolve)\n })\n }\n\n public async hincrby(key: string, field: string, value: number) {\n return new Promise(resolve => {\n this.pub.hincrby(key, field, value, resolve)\n })\n }\n\n public async hget(key: string, field: string) {\n return await this.hgetAsync(key, field)\n }\n\n public async hgetall(key: string) {\n return new Promise<{ [key: string]: string }>((resolve, reject) => {\n this.pub.hgetall(key, (err, values) => {\n if (err) {\n return reject(err)\n }\n resolve(values)\n })\n })\n }\n\n public async hdel(key: string, field: string) {\n return new Promise((resolve, reject) => {\n this.pub.hdel(key, field, (err, ok) => {\n if (err) {\n return reject(err)\n }\n resolve(ok)\n })\n })\n }\n\n public async hlen(key: string): Promise {\n return await this.hlenAsync(key)\n }\n\n public async incr(key: string): Promise {\n return await this.incrAsync(key)\n }\n\n public async decr(key: string): Promise {\n return await this.decrAsync(key)\n }\n\n protected handleSubscription = (channel: string, message: string) => {\n if (this.subscriptions[channel]) {\n for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) {\n this.subscriptions[channel][i](JSON.parse(message))\n }\n }\n }\n}\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/dist/index.d.cts b/dist/index.d.cts index 70443fe..1d0a55c 100644 --- a/dist/index.d.cts +++ b/dist/index.d.cts @@ -1,6 +1,7 @@ export { ZError } from './common/ZError.cjs'; export { SyncLocker } from './common/SyncLocker.cjs'; export { AsyncQueue, createAsyncQueue, createAsyncQueues } from './common/AsyncQueue.cjs'; +import { RedisClient, ClientOpts } from 'redis'; /** * 单例化一个class @@ -18,4 +19,59 @@ type Singleton any> = T & { }; declare const singleton: any>(classTarget: T) => T; -export { SINGLETON_KEY, type Singleton, singleton }; +type Callback = (...args: any[]) => void; +declare class ZRedisClient { + pub: RedisClient; + sub: RedisClient; + protected subscribeAsync: any; + protected unsubscribeAsync: any; + protected publishAsync: any; + protected subscriptions: { + [channel: string]: Callback[]; + }; + protected smembersAsync: any; + protected sismemberAsync: any; + protected hgetAsync: any; + protected hlenAsync: any; + protected pubsubAsync: any; + protected incrAsync: any; + protected decrAsync: any; + constructor(opts?: ClientOpts); + subscribe(topic: string, callback: Callback): Promise; + unsubscribe(topic: string, callback?: Callback): Promise; + publish(topic: string, data: any): Promise; + exists(roomId: string): Promise; + setex(key: string, value: string, seconds: number): Promise; + expire(key: string, seconds: number): Promise; + get(key: string): Promise; + set(key: string, val: string): Promise; + del(roomId: string): Promise; + sadd(key: string, value: any): Promise; + smembers(key: string): Promise; + sismember(key: string, field: string): Promise; + srem(key: string, value: any): Promise; + scard(key: string): Promise; + srandmember(key: string): Promise; + sinter(...keys: string[]): Promise; + zadd(key: string, value: any, member: string): Promise; + zincrby(key: string, value: any, member: string): Promise; + zrangebyscore(key: string, min: number, max: number): Promise; + zcard(key: string): Promise; + zcount(key: string, min: number, max: number): Promise; + zrevrank(key: string, member: string): Promise; + zscore(key: string, member: string): Promise; + zrevrange(key: string, start: number, end: number): Promise; + hset(key: string, field: string, value: string): Promise; + hincrby(key: string, field: string, value: number): Promise; + hget(key: string, field: string): Promise; + hgetall(key: string): Promise<{ + [key: string]: string; + }>; + hdel(key: string, field: string): Promise; + hlen(key: string): Promise; + incr(key: string): Promise; + decr(key: string): Promise; + protected handleSubscription: (channel: string, message: string) => void; +} + +export { SINGLETON_KEY, type Singleton, ZRedisClient, singleton }; diff --git a/dist/index.d.ts b/dist/index.d.ts index 06431ee..ee5e8e8 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,6 +1,7 @@ export { ZError } from './common/ZError.js'; export { SyncLocker } from './common/SyncLocker.js'; export { AsyncQueue, createAsyncQueue, createAsyncQueues } from './common/AsyncQueue.js'; +import { RedisClient, ClientOpts } from 'redis'; /** * 单例化一个class @@ -18,4 +19,59 @@ type Singleton any> = T & { }; declare const singleton: any>(classTarget: T) => T; -export { SINGLETON_KEY, type Singleton, singleton }; +type Callback = (...args: any[]) => void; +declare class ZRedisClient { + pub: RedisClient; + sub: RedisClient; + protected subscribeAsync: any; + protected unsubscribeAsync: any; + protected publishAsync: any; + protected subscriptions: { + [channel: string]: Callback[]; + }; + protected smembersAsync: any; + protected sismemberAsync: any; + protected hgetAsync: any; + protected hlenAsync: any; + protected pubsubAsync: any; + protected incrAsync: any; + protected decrAsync: any; + constructor(opts?: ClientOpts); + subscribe(topic: string, callback: Callback): Promise; + unsubscribe(topic: string, callback?: Callback): Promise; + publish(topic: string, data: any): Promise; + exists(roomId: string): Promise; + setex(key: string, value: string, seconds: number): Promise; + expire(key: string, seconds: number): Promise; + get(key: string): Promise; + set(key: string, val: string): Promise; + del(roomId: string): Promise; + sadd(key: string, value: any): Promise; + smembers(key: string): Promise; + sismember(key: string, field: string): Promise; + srem(key: string, value: any): Promise; + scard(key: string): Promise; + srandmember(key: string): Promise; + sinter(...keys: string[]): Promise; + zadd(key: string, value: any, member: string): Promise; + zincrby(key: string, value: any, member: string): Promise; + zrangebyscore(key: string, min: number, max: number): Promise; + zcard(key: string): Promise; + zcount(key: string, min: number, max: number): Promise; + zrevrank(key: string, member: string): Promise; + zscore(key: string, member: string): Promise; + zrevrange(key: string, start: number, end: number): Promise; + hset(key: string, field: string, value: string): Promise; + hincrby(key: string, field: string, value: number): Promise; + hget(key: string, field: string): Promise; + hgetall(key: string): Promise<{ + [key: string]: string; + }>; + hdel(key: string, field: string): Promise; + hlen(key: string): Promise; + incr(key: string): Promise; + decr(key: string): Promise; + protected handleSubscription: (channel: string, message: string) => void; +} + +export { SINGLETON_KEY, type Singleton, ZRedisClient, singleton }; diff --git a/dist/index.js b/dist/index.js index c94713e..ddee5e8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -132,10 +132,261 @@ var DeferredPromise = class { }); } }; + +// src/redis/ZRedisClient.ts +import { createClient } from "redis"; +import { promisify } from "util"; +var ZRedisClient = class { + constructor(opts) { + this.subscriptions = {}; + this.handleSubscription = (channel, message) => { + if (this.subscriptions[channel]) { + for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) { + this.subscriptions[channel][i](JSON.parse(message)); + } + } + }; + this.sub = createClient(opts); + this.pub = createClient(opts); + this.sub.setMaxListeners(0); + this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub); + this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub); + this.publishAsync = promisify(this.pub.publish).bind(this.pub); + this.smembersAsync = promisify(this.pub.smembers).bind(this.pub); + this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub); + this.hlenAsync = promisify(this.pub.hlen).bind(this.pub); + this.hgetAsync = promisify(this.pub.hget).bind(this.pub); + this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub); + this.decrAsync = promisify(this.pub.decr).bind(this.pub); + this.incrAsync = promisify(this.pub.incr).bind(this.pub); + } + async subscribe(topic, callback) { + if (!this.subscriptions[topic]) { + this.subscriptions[topic] = []; + } + this.subscriptions[topic].push(callback); + if (this.sub.listeners("message").length === 0) { + this.sub.addListener("message", this.handleSubscription); + } + await this.subscribeAsync(topic); + return this; + } + async unsubscribe(topic, callback) { + if (callback) { + const index = this.subscriptions[topic].indexOf(callback); + this.subscriptions[topic].splice(index, 1); + } else { + this.subscriptions[topic] = []; + } + if (this.subscriptions[topic].length === 0) { + await this.unsubscribeAsync(topic); + } + return this; + } + async publish(topic, data) { + if (data === void 0) { + data = false; + } + await this.publishAsync(topic, JSON.stringify(data)); + } + async exists(roomId) { + return (await this.pubsubAsync("channels", roomId)).length > 0; + } + async setex(key, value, seconds) { + return new Promise((resolve) => this.pub.setex(key, seconds, value, resolve)); + } + async expire(key, seconds) { + return new Promise((resolve) => this.pub.expire(key, seconds, resolve)); + } + async get(key) { + return new Promise((resolve, reject) => { + this.pub.get(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async set(key, val) { + return new Promise((resolve) => { + this.pub.set(key, val, () => { + resolve && resolve(""); + }); + }); + } + async del(roomId) { + return new Promise((resolve) => { + this.pub.del(roomId, resolve); + }); + } + async sadd(key, value) { + return new Promise((resolve) => { + this.pub.sadd(key, value, resolve); + }); + } + async smembers(key) { + return await this.smembersAsync(key); + } + async sismember(key, field) { + return await this.sismemberAsync(key, field); + } + async srem(key, value) { + return new Promise((resolve) => { + this.pub.srem(key, value, resolve); + }); + } + async scard(key) { + return new Promise((resolve, reject) => { + this.pub.scard(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async srandmember(key) { + return new Promise((resolve, reject) => { + this.pub.srandmember(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async sinter(...keys) { + return new Promise((resolve, reject) => { + this.pub.sinter(...keys, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zadd(key, value, member) { + return new Promise((resolve) => { + this.pub.zadd(key, value, member, resolve); + }); + } + async zincrby(key, value, member) { + return new Promise((resolve) => { + this.pub.zincrby(key, value, member, resolve); + }); + } + async zrangebyscore(key, min, max) { + return new Promise((resolve, reject) => { + this.pub.zrangebyscore(key, min, max, "withscores", (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zcard(key) { + return new Promise((resolve, reject) => { + this.pub.zcard(key, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zcount(key, min, max) { + return new Promise((resolve, reject) => { + this.pub.zcount(key, min, max, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zrevrank(key, member) { + return new Promise((resolve, reject) => { + this.pub.zrevrank(key, member, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zscore(key, member) { + return new Promise((resolve, reject) => { + this.pub.zscore(key, member, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async zrevrange(key, start, end) { + return new Promise((resolve, reject) => { + this.pub.zrevrange(key, start, end, "withscores", (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } + async hset(key, field, value) { + return new Promise((resolve) => { + this.pub.hset(key, field, value, resolve); + }); + } + async hincrby(key, field, value) { + return new Promise((resolve) => { + this.pub.hincrby(key, field, value, resolve); + }); + } + async hget(key, field) { + return await this.hgetAsync(key, field); + } + async hgetall(key) { + return new Promise((resolve, reject) => { + this.pub.hgetall(key, (err, values) => { + if (err) { + return reject(err); + } + resolve(values); + }); + }); + } + async hdel(key, field) { + return new Promise((resolve, reject) => { + this.pub.hdel(key, field, (err, ok) => { + if (err) { + return reject(err); + } + resolve(ok); + }); + }); + } + async hlen(key) { + return await this.hlenAsync(key); + } + async incr(key) { + return await this.incrAsync(key); + } + async decr(key) { + return await this.decrAsync(key); + } +}; +ZRedisClient = __decorateClass([ + singleton +], ZRedisClient); export { SINGLETON_KEY, SyncLocker, ZError, + ZRedisClient, createAsyncQueue, createAsyncQueues, singleton diff --git a/dist/index.js.map b/dist/index.js.map index 336e2e5..a61dcbd 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/common/ZError.ts","../src/decorators/singleton.ts","../src/common/SyncLocker.ts","../src/common/AsyncQueue.ts"],"sourcesContent":["\nexport class ZError implements Error {\n code: string\n statusCode?: number\n message: string\n name: string\n\n constructor(statusCode: number, message: string) {\n this.statusCode = statusCode\n this.message = message\n }\n}\n","/**\n * 单例化一个class\n * 使用方法:\n * @singleton\n * class Test {}\n * new Test() === new Test() // returns `true`\n * 也可以不使用 decorator\n * const TestSingleton = singleton(Test)\n * new TestSingleton() === new TestSingleton() //returns 'true'\n */\n\nexport const SINGLETON_KEY = Symbol()\n\nexport type Singleton any> = T & {\n [SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never\n}\nexport const singleton = any>(classTarget: T) =>\n new Proxy(classTarget, {\n construct(target: Singleton, argumentsList, newTarget) {\n // Skip proxy for children\n if (target.prototype !== newTarget.prototype) {\n return Reflect.construct(target, argumentsList, newTarget)\n }\n if (!target[SINGLETON_KEY]) {\n target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)\n }\n return target[SINGLETON_KEY]\n },\n })\n","import { singleton } from 'decorators/singleton'\nimport { ZError } from './ZError'\n\ninterface IRequest {\n method: string\n url: string\n user?: {\n id: string\n }\n}\n\n@singleton\nexport class SyncLocker {\n map: Map = new Map()\n\n public lock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n return false\n }\n this.map.set(key, true)\n return true\n }\n\n public unlock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n this.map.delete(key)\n }\n\n public checkLock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n throw new ZError(100, 'request too fast')\n }\n this.lock(req)\n return true\n }\n\n public isLocked(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n return this.map.has(key)\n }\n}\n","type Callback = () => Promise\n\nexport type AsyncQueue = {\n push: (task: Callback) => Promise\n flush: () => Promise\n size: number\n}\n\n/**\n * Ensures that each callback pushed onto the queue is executed in series.\n * Such a quetie 😻\n * @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple\n * tasks are pushed onto the queue while there is an active task, only the\n * last one will be executed, once the active task has completed.\n * e.g. in the below example, only 0 and 3 will be executed.\n * ```\n * const queue = createAsyncQueue({ dedupeConcurrent: true })\n * queue.push(async () => console.log(0)) // returns 0\n * queue.push(async () => console.log(1)) // returns 3\n * queue.push(async () => console.log(2)) // returns 3\n * queue.push(async () => console.log(3)) // returns 3\n * ```\n * */\nexport function createAsyncQueue(opts = { dedupeConcurrent: false }): AsyncQueue {\n const { dedupeConcurrent } = opts\n let queue: Callback[] = []\n let running: Promise | undefined\n let nextPromise = new DeferredPromise()\n const push = (task: Callback) => {\n let taskPromise = new DeferredPromise()\n if (dedupeConcurrent) {\n queue = []\n if (nextPromise.started) nextPromise = new DeferredPromise()\n taskPromise = nextPromise\n }\n queue.push(() => {\n taskPromise.started = true\n task().then(taskPromise.resolve).catch(taskPromise.reject)\n return taskPromise.promise\n })\n if (!running) running = start()\n return taskPromise.promise\n }\n const start = async () => {\n while (queue.length) {\n const task = queue.shift()!\n await task().catch(() => {})\n }\n running = undefined\n }\n return {\n push,\n flush: () => running || Promise.resolve(),\n get size() {\n return queue.length\n },\n }\n}\n\nexport const createAsyncQueues = (opts = { dedupeConcurrent: false }) => {\n const queues: { [queueId: string]: AsyncQueue } = {}\n const push = (queueId: string, task: Callback) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].push(task)\n }\n const flush = (queueId: string) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].flush()\n }\n return { push, flush }\n}\n\nclass DeferredPromise {\n started = false\n resolve: (x: T | PromiseLike) => void = () => {}\n reject: (x: E) => void = () => {}\n promise: Promise\n\n constructor() {\n this.promise = new Promise((res, rej) => {\n this.resolve = res\n this.reject = rej\n })\n }\n}\n\n// function main() {\n// const queue = createAsyncQueue()\n// queue.push(async () => {\n// console.log(0)\n// }) // returns 0\n// queue.push(async () => {\n// console.log(1)\n\n// return new Promise((resolve, reject) => {\n// setTimeout(() => {\n// console.log('12')\n// resolve()\n// }, 1000)\n// })\n// }) // returns 3\n// queue.push(async () => console.log(2)) // returns 3\n// queue.push(async () => console.log(3)) // returns 3\n// console.log('hi')\n// }\n\n// main()\n"],"mappings":"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","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/common/ZError.ts","../src/decorators/singleton.ts","../src/common/SyncLocker.ts","../src/common/AsyncQueue.ts","../src/redis/ZRedisClient.ts"],"sourcesContent":["\nexport class ZError implements Error {\n code: string\n statusCode?: number\n message: string\n name: string\n\n constructor(statusCode: number, message: string) {\n this.statusCode = statusCode\n this.message = message\n }\n}\n","/**\n * 单例化一个class\n * 使用方法:\n * @singleton\n * class Test {}\n * new Test() === new Test() // returns `true`\n * 也可以不使用 decorator\n * const TestSingleton = singleton(Test)\n * new TestSingleton() === new TestSingleton() //returns 'true'\n */\n\nexport const SINGLETON_KEY = Symbol()\n\nexport type Singleton any> = T & {\n [SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never\n}\nexport const singleton = any>(classTarget: T) =>\n new Proxy(classTarget, {\n construct(target: Singleton, argumentsList, newTarget) {\n // Skip proxy for children\n if (target.prototype !== newTarget.prototype) {\n return Reflect.construct(target, argumentsList, newTarget)\n }\n if (!target[SINGLETON_KEY]) {\n target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)\n }\n return target[SINGLETON_KEY]\n },\n })\n","import { singleton } from 'decorators/singleton'\nimport { ZError } from './ZError'\n\ninterface IRequest {\n method: string\n url: string\n user?: {\n id: string\n }\n}\n\n@singleton\nexport class SyncLocker {\n map: Map = new Map()\n\n public lock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n return false\n }\n this.map.set(key, true)\n return true\n }\n\n public unlock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n this.map.delete(key)\n }\n\n public checkLock(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n if (this.map.has(key)) {\n throw new ZError(100, 'request too fast')\n }\n this.lock(req)\n return true\n }\n\n public isLocked(req: IRequest) {\n const key = `${req.method}:${req.url}:${req.user?.id || ''}`\n return this.map.has(key)\n }\n}\n","type Callback = () => Promise\n\nexport type AsyncQueue = {\n push: (task: Callback) => Promise\n flush: () => Promise\n size: number\n}\n\n/**\n * Ensures that each callback pushed onto the queue is executed in series.\n * Such a quetie 😻\n * @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple\n * tasks are pushed onto the queue while there is an active task, only the\n * last one will be executed, once the active task has completed.\n * e.g. in the below example, only 0 and 3 will be executed.\n * ```\n * const queue = createAsyncQueue({ dedupeConcurrent: true })\n * queue.push(async () => console.log(0)) // returns 0\n * queue.push(async () => console.log(1)) // returns 3\n * queue.push(async () => console.log(2)) // returns 3\n * queue.push(async () => console.log(3)) // returns 3\n * ```\n * */\nexport function createAsyncQueue(opts = { dedupeConcurrent: false }): AsyncQueue {\n const { dedupeConcurrent } = opts\n let queue: Callback[] = []\n let running: Promise | undefined\n let nextPromise = new DeferredPromise()\n const push = (task: Callback) => {\n let taskPromise = new DeferredPromise()\n if (dedupeConcurrent) {\n queue = []\n if (nextPromise.started) nextPromise = new DeferredPromise()\n taskPromise = nextPromise\n }\n queue.push(() => {\n taskPromise.started = true\n task().then(taskPromise.resolve).catch(taskPromise.reject)\n return taskPromise.promise\n })\n if (!running) running = start()\n return taskPromise.promise\n }\n const start = async () => {\n while (queue.length) {\n const task = queue.shift()!\n await task().catch(() => {})\n }\n running = undefined\n }\n return {\n push,\n flush: () => running || Promise.resolve(),\n get size() {\n return queue.length\n },\n }\n}\n\nexport const createAsyncQueues = (opts = { dedupeConcurrent: false }) => {\n const queues: { [queueId: string]: AsyncQueue } = {}\n const push = (queueId: string, task: Callback) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].push(task)\n }\n const flush = (queueId: string) => {\n if (!queues[queueId]) queues[queueId] = createAsyncQueue(opts)\n return queues[queueId].flush()\n }\n return { push, flush }\n}\n\nclass DeferredPromise {\n started = false\n resolve: (x: T | PromiseLike) => void = () => {}\n reject: (x: E) => void = () => {}\n promise: Promise\n\n constructor() {\n this.promise = new Promise((res, rej) => {\n this.resolve = res\n this.reject = rej\n })\n }\n}\n\n// function main() {\n// const queue = createAsyncQueue()\n// queue.push(async () => {\n// console.log(0)\n// }) // returns 0\n// queue.push(async () => {\n// console.log(1)\n\n// return new Promise((resolve, reject) => {\n// setTimeout(() => {\n// console.log('12')\n// resolve()\n// }, 1000)\n// })\n// }) // returns 3\n// queue.push(async () => console.log(2)) // returns 3\n// queue.push(async () => console.log(3)) // returns 3\n// console.log('hi')\n// }\n\n// main()\n","import { singleton } from 'decorators/singleton'\nimport { ClientOpts, RedisClient, createClient } from 'redis'\nimport { promisify } from 'util'\n\n\ntype Callback = (...args: any[]) => void\n\n@singleton\nexport class ZRedisClient {\n public pub: RedisClient\n public sub: RedisClient\n\n protected subscribeAsync: any\n protected unsubscribeAsync: any\n protected publishAsync: any\n\n protected subscriptions: { [channel: string]: Callback[] } = {}\n\n protected smembersAsync: any\n protected sismemberAsync: any\n protected hgetAsync: any\n protected hlenAsync: any\n protected pubsubAsync: any\n protected incrAsync: any\n protected decrAsync: any\n\n constructor(opts?: ClientOpts) {\n this.sub = createClient(opts)\n this.pub = createClient(opts)\n\n // no listener limit\n this.sub.setMaxListeners(0)\n\n // create promisified pub/sub methods.\n this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub)\n this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub)\n\n this.publishAsync = promisify(this.pub.publish).bind(this.pub)\n\n // create promisified redis methods.\n this.smembersAsync = promisify(this.pub.smembers).bind(this.pub)\n this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub)\n this.hlenAsync = promisify(this.pub.hlen).bind(this.pub)\n this.hgetAsync = promisify(this.pub.hget).bind(this.pub)\n this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub)\n this.decrAsync = promisify(this.pub.decr).bind(this.pub)\n this.incrAsync = promisify(this.pub.incr).bind(this.pub)\n }\n\n public async subscribe(topic: string, callback: Callback) {\n if (!this.subscriptions[topic]) {\n this.subscriptions[topic] = []\n }\n\n this.subscriptions[topic].push(callback)\n\n if (this.sub.listeners('message').length === 0) {\n this.sub.addListener('message', this.handleSubscription)\n }\n\n await this.subscribeAsync(topic)\n\n return this\n }\n\n public async unsubscribe(topic: string, callback?: Callback) {\n if (callback) {\n const index = this.subscriptions[topic].indexOf(callback)\n this.subscriptions[topic].splice(index, 1)\n } else {\n this.subscriptions[topic] = []\n }\n\n if (this.subscriptions[topic].length === 0) {\n await this.unsubscribeAsync(topic)\n }\n\n return this\n }\n\n public async publish(topic: string, data: any) {\n if (data === undefined) {\n data = false\n }\n\n await this.publishAsync(topic, JSON.stringify(data))\n }\n\n public async exists(roomId: string): Promise {\n return (await this.pubsubAsync('channels', roomId)).length > 0\n }\n\n public async setex(key: string, value: string, seconds: number) {\n return new Promise(resolve => this.pub.setex(key, seconds, value, resolve))\n }\n\n public async expire(key: string, seconds: number) {\n return new Promise(resolve => this.pub.expire(key, seconds, resolve))\n }\n\n public async get(key: string): Promise {\n return new Promise((resolve, reject) => {\n this.pub.get(key, (err, data: string | null) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async set(key: string, val: string) {\n return new Promise(resolve => {\n this.pub.set(key, val, () => {\n resolve && resolve('')\n })\n })\n }\n\n public async del(roomId: string) {\n return new Promise(resolve => {\n this.pub.del(roomId, resolve)\n })\n }\n\n public async sadd(key: string, value: any) {\n return new Promise(resolve => {\n this.pub.sadd(key, value, resolve)\n })\n }\n\n public async smembers(key: string): Promise {\n return await this.smembersAsync(key)\n }\n\n public async sismember(key: string, field: string): Promise {\n return await this.sismemberAsync(key, field)\n }\n\n public async srem(key: string, value: any) {\n return new Promise(resolve => {\n this.pub.srem(key, value, resolve)\n })\n }\n\n public async scard(key: string) {\n return new Promise((resolve, reject) => {\n this.pub.scard(key, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n public async srandmember(key: string) {\n return new Promise((resolve, reject) => {\n this.pub.srandmember(key, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async sinter(...keys: string[]) {\n return new Promise((resolve, reject) => {\n this.pub.sinter(...keys, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zadd(key: string, value: any, member: string) {\n return new Promise(resolve => {\n this.pub.zadd(key, value, member, resolve)\n })\n }\n\n public async zincrby(key: string, value: any, member: string) {\n return new Promise(resolve => {\n this.pub.zincrby(key, value, member, resolve)\n })\n }\n\n public async zrangebyscore(key: string, min: number, max: number) {\n return new Promise((resolve, reject) => {\n this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zcard(key: string) {\n return new Promise((resolve, reject) => {\n this.pub.zcard(key, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zcount(key: string, min: number, max: number) {\n return new Promise((resolve, reject) => {\n this.pub.zcount(key, min, max, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zrevrank(key: string, member: string) {\n return new Promise((resolve, reject) => {\n this.pub.zrevrank(key, member, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zscore(key: string, member: string) {\n return new Promise((resolve, reject) => {\n this.pub.zscore(key, member, (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async zrevrange(key: string, start: number, end: number): Promise {\n return new Promise((resolve, reject) => {\n this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {\n if (err) {\n return reject(err)\n }\n resolve(data)\n })\n })\n }\n\n public async hset(key: string, field: string, value: string) {\n return new Promise(resolve => {\n this.pub.hset(key, field, value, resolve)\n })\n }\n\n public async hincrby(key: string, field: string, value: number) {\n return new Promise(resolve => {\n this.pub.hincrby(key, field, value, resolve)\n })\n }\n\n public async hget(key: string, field: string) {\n return await this.hgetAsync(key, field)\n }\n\n public async hgetall(key: string) {\n return new Promise<{ [key: string]: string }>((resolve, reject) => {\n this.pub.hgetall(key, (err, values) => {\n if (err) {\n return reject(err)\n }\n resolve(values)\n })\n })\n }\n\n public async hdel(key: string, field: string) {\n return new Promise((resolve, reject) => {\n this.pub.hdel(key, field, (err, ok) => {\n if (err) {\n return reject(err)\n }\n resolve(ok)\n })\n })\n }\n\n public async hlen(key: string): Promise {\n return await this.hlenAsync(key)\n }\n\n public async incr(key: string): Promise {\n return await this.incrAsync(key)\n }\n\n public async decr(key: string): Promise {\n return await this.decrAsync(key)\n }\n\n protected handleSubscription = (channel: string, message: string) => {\n if (this.subscriptions[channel]) {\n for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) {\n this.subscriptions[channel][i](JSON.parse(message))\n }\n }\n }\n}\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/package.json b/package.json index f73f426..a5e756d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ }, "typesVersions": { "*": { - "utils/*": ["dist/utils/*.d.ts"] + "utils/*": [ + "dist/utils/*.d.ts" + ] } }, "type": "module", @@ -31,9 +33,11 @@ "@metamask/eth-sig-util": "^4.0.1", "crypto-js": "^4.2.0", "ethereumjs-util": "^7.1.5", + "redis": "^3.1.2", "web3": "^1.7.4" }, "devDependencies": { + "@types/redis": "^2.8.28", "ts-node": "^10.9.2", "tsup": "^8.0.1", "typescript": "^5.3.3" diff --git a/src/index.ts b/src/index.ts index 888d996..d4fd04a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export { ZError } from './common/ZError' export { SyncLocker } from './common/SyncLocker' export * from './decorators/singleton' -export * from './common/AsyncQueue' \ No newline at end of file +export * from './common/AsyncQueue' +export { ZRedisClient } from './redis/ZRedisClient' \ No newline at end of file diff --git a/src/redis/ZRedisClient.ts b/src/redis/ZRedisClient.ts new file mode 100644 index 0000000..3e938b7 --- /dev/null +++ b/src/redis/ZRedisClient.ts @@ -0,0 +1,313 @@ +import { singleton } from 'decorators/singleton' +import { ClientOpts, RedisClient, createClient } from 'redis' +import { promisify } from 'util' + + +type Callback = (...args: any[]) => void + +@singleton +export class ZRedisClient { + public pub: RedisClient + public sub: RedisClient + + protected subscribeAsync: any + protected unsubscribeAsync: any + protected publishAsync: any + + protected subscriptions: { [channel: string]: Callback[] } = {} + + protected smembersAsync: any + protected sismemberAsync: any + protected hgetAsync: any + protected hlenAsync: any + protected pubsubAsync: any + protected incrAsync: any + protected decrAsync: any + + constructor(opts?: ClientOpts) { + this.sub = createClient(opts) + this.pub = createClient(opts) + + // no listener limit + this.sub.setMaxListeners(0) + + // create promisified pub/sub methods. + this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub) + this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub) + + this.publishAsync = promisify(this.pub.publish).bind(this.pub) + + // create promisified redis methods. + this.smembersAsync = promisify(this.pub.smembers).bind(this.pub) + this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub) + this.hlenAsync = promisify(this.pub.hlen).bind(this.pub) + this.hgetAsync = promisify(this.pub.hget).bind(this.pub) + this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub) + this.decrAsync = promisify(this.pub.decr).bind(this.pub) + this.incrAsync = promisify(this.pub.incr).bind(this.pub) + } + + public async subscribe(topic: string, callback: Callback) { + if (!this.subscriptions[topic]) { + this.subscriptions[topic] = [] + } + + this.subscriptions[topic].push(callback) + + if (this.sub.listeners('message').length === 0) { + this.sub.addListener('message', this.handleSubscription) + } + + await this.subscribeAsync(topic) + + return this + } + + public async unsubscribe(topic: string, callback?: Callback) { + if (callback) { + const index = this.subscriptions[topic].indexOf(callback) + this.subscriptions[topic].splice(index, 1) + } else { + this.subscriptions[topic] = [] + } + + if (this.subscriptions[topic].length === 0) { + await this.unsubscribeAsync(topic) + } + + return this + } + + public async publish(topic: string, data: any) { + if (data === undefined) { + data = false + } + + await this.publishAsync(topic, JSON.stringify(data)) + } + + public async exists(roomId: string): Promise { + return (await this.pubsubAsync('channels', roomId)).length > 0 + } + + public async setex(key: string, value: string, seconds: number) { + return new Promise(resolve => this.pub.setex(key, seconds, value, resolve)) + } + + public async expire(key: string, seconds: number) { + return new Promise(resolve => this.pub.expire(key, seconds, resolve)) + } + + public async get(key: string): Promise { + return new Promise((resolve, reject) => { + this.pub.get(key, (err, data: string | null) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async set(key: string, val: string) { + return new Promise(resolve => { + this.pub.set(key, val, () => { + resolve && resolve('') + }) + }) + } + + public async del(roomId: string) { + return new Promise(resolve => { + this.pub.del(roomId, resolve) + }) + } + + public async sadd(key: string, value: any) { + return new Promise(resolve => { + this.pub.sadd(key, value, resolve) + }) + } + + public async smembers(key: string): Promise { + return await this.smembersAsync(key) + } + + public async sismember(key: string, field: string): Promise { + return await this.sismemberAsync(key, field) + } + + public async srem(key: string, value: any) { + return new Promise(resolve => { + this.pub.srem(key, value, resolve) + }) + } + + public async scard(key: string) { + return new Promise((resolve, reject) => { + this.pub.scard(key, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + public async srandmember(key: string) { + return new Promise((resolve, reject) => { + this.pub.srandmember(key, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async sinter(...keys: string[]) { + return new Promise((resolve, reject) => { + this.pub.sinter(...keys, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async zadd(key: string, value: any, member: string) { + return new Promise(resolve => { + this.pub.zadd(key, value, member, resolve) + }) + } + + public async zincrby(key: string, value: any, member: string) { + return new Promise(resolve => { + this.pub.zincrby(key, value, member, resolve) + }) + } + + public async zrangebyscore(key: string, min: number, max: number) { + return new Promise((resolve, reject) => { + this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async zcard(key: string) { + return new Promise((resolve, reject) => { + this.pub.zcard(key, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async zcount(key: string, min: number, max: number) { + return new Promise((resolve, reject) => { + this.pub.zcount(key, min, max, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async zrevrank(key: string, member: string) { + return new Promise((resolve, reject) => { + this.pub.zrevrank(key, member, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async zscore(key: string, member: string) { + return new Promise((resolve, reject) => { + this.pub.zscore(key, member, (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async zrevrange(key: string, start: number, end: number): Promise { + return new Promise((resolve, reject) => { + this.pub.zrevrange(key, start, end, 'withscores', (err, data) => { + if (err) { + return reject(err) + } + resolve(data) + }) + }) + } + + public async hset(key: string, field: string, value: string) { + return new Promise(resolve => { + this.pub.hset(key, field, value, resolve) + }) + } + + public async hincrby(key: string, field: string, value: number) { + return new Promise(resolve => { + this.pub.hincrby(key, field, value, resolve) + }) + } + + public async hget(key: string, field: string) { + return await this.hgetAsync(key, field) + } + + public async hgetall(key: string) { + return new Promise<{ [key: string]: string }>((resolve, reject) => { + this.pub.hgetall(key, (err, values) => { + if (err) { + return reject(err) + } + resolve(values) + }) + }) + } + + public async hdel(key: string, field: string) { + return new Promise((resolve, reject) => { + this.pub.hdel(key, field, (err, ok) => { + if (err) { + return reject(err) + } + resolve(ok) + }) + }) + } + + public async hlen(key: string): Promise { + return await this.hlenAsync(key) + } + + public async incr(key: string): Promise { + return await this.incrAsync(key) + } + + public async decr(key: string): Promise { + return await this.decrAsync(key) + } + + protected handleSubscription = (channel: string, message: string) => { + if (this.subscriptions[channel]) { + for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) { + this.subscriptions[channel][i](JSON.parse(message)) + } + } + } +} diff --git a/yarn.lock b/yarn.lock index 78f407c..f9e2587 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,6 +623,13 @@ dependencies: "@types/node" "*" +"@types/redis@^2.8.28": + version "2.8.32" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11" + integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w== + dependencies: + "@types/node" "*" + "@types/responselike@^1.0.0": version "1.0.3" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" @@ -1218,6 +1225,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +denque@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" + integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -2674,6 +2686,33 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +redis-commands@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + +redis@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c" + integrity sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw== + dependencies: + denque "^1.5.0" + redis-commands "^1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + request@^2.79.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"