@mark1russell7/service 0.5.3 → 0.5.5

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.
@@ -1,4 +1,18 @@
1
1
  import { convertible, valueIn } from "@mark1russell7/quantity";
2
+ //#region src/drains.ts
3
+ const holds = /* @__PURE__ */ new WeakMap();
4
+ /** This function adds the hold of a drain under its lifecycle. It gives the function that removes the hold. */
5
+ const holdLate = (life, hold) => {
6
+ const set = holds.get(life) ?? /* @__PURE__ */ new Set();
7
+ holds.set(life, set);
8
+ set.add(hold);
9
+ return () => {
10
+ set.delete(hold);
11
+ };
12
+ };
13
+ /** This function gives the hold of each drain under a lifecycle, as it is at this time. */
14
+ const lateHolds = (life) => [...holds.get(life) ?? []].map((hold) => hold());
15
+ //#endregion
2
16
  //#region src/knobs.ts
3
17
  /**
4
18
  * This function gives a time knob in milliseconds, or infinity for a time without a bound. The
@@ -34,6 +48,6 @@ const countOf = (knobs, name, owner) => {
34
48
  return value;
35
49
  };
36
50
  //#endregion
37
- export { countOf as n, millisecondsOf as r, boundedMillisecondsOf as t };
51
+ export { lateHolds as a, holdLate as i, countOf as n, millisecondsOf as r, boundedMillisecondsOf as t };
38
52
 
39
- //# sourceMappingURL=knobs-BxG-c7Zb.js.map
53
+ //# sourceMappingURL=knobs-BZAsgb5H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knobs-BZAsgb5H.js","names":[],"sources":["../src/drains.ts","../src/knobs.ts"],"sourcesContent":["// The holds of the drains after their deadline (SYS-29). A transport that waits after the deadline of\n// its drain gives its hold under its lifecycle. At the cap of the drain, the lifecycle reads each hold.\n// A drain that waits only for the bytes of its answers did its work, thus the phase does not fail.\n\n/** A late hold tells what holds the drain of a transport after its deadline. */\nexport interface LateHold {\n /** This count is the number of calls whose end did not come: their handler still runs. */\n readonly running: number;\n /** This count is the number of answers whose bytes did not go to the client, for example to a client that does not read. */\n readonly unflushed: number;\n}\n\nconst holds = new WeakMap<object, Set<() => LateHold>>();\n\n/** This function adds the hold of a drain under its lifecycle. It gives the function that removes the hold. */\nexport const holdLate = (life: object, hold: () => LateHold): (() => void) => {\n const set = holds.get(life) ?? new Set<() => LateHold>();\n holds.set(life, set);\n set.add(hold);\n return () => {\n set.delete(hold);\n };\n};\n\n/** This function gives the hold of each drain under a lifecycle, as it is at this time. */\nexport const lateHolds = (life: object): LateHold[] => [...(holds.get(life) ?? [])].map((hold) => hold());\n","import type { Extent } from \"@mark1russell7/quantity\";\nimport { convertible, valueIn } from \"@mark1russell7/quantity\";\n\n/** A knob record is the knobs of a part, by name. A knob that is absent is undefined. */\nexport type KnobRecord = Readonly<Record<string, Extent | undefined>>;\n\n/**\n * This function gives a time knob in milliseconds, or infinity for a time without a bound. The\n * runtime has no defaults, thus a knob that is absent throws. A knob that is not a time, or that is\n * less than zero, also throws.\n */\nexport const millisecondsOf = (knobs: KnobRecord, name: string, owner: string): number => {\n const amount = knobs[name];\n if (amount === undefined) throw new Error(`${owner} needs the knob ${name}: it has no defaults`);\n if (!convertible(amount.unit, \"s\")) throw new Error(`the knob ${name} is not a time`);\n if (amount.value === \"infinite\") return Number.POSITIVE_INFINITY;\n const value = valueIn(amount, \"ms\");\n if (!(value >= 0)) throw new Error(`the knob ${name} is less than zero`);\n return value;\n};\n\n/** This function gives a time knob in milliseconds, and it throws for a time without a bound. */\nexport const boundedMillisecondsOf = (knobs: KnobRecord, name: string, owner: string): number => {\n const value = millisecondsOf(knobs, name, owner);\n if (!Number.isFinite(value)) throw new Error(`the knob ${name} of ${owner} has no bound: it needs a time`);\n return value;\n};\n\n/**\n * This function gives a count knob (the unit `count`) as a whole number above zero, or infinity\n * for a count without a bound. A knob that is absent, not a count, or not a whole number above zero throws.\n */\nexport const countOf = (knobs: KnobRecord, name: string, owner: string): number => {\n const amount = knobs[name];\n if (amount === undefined) throw new Error(`${owner} needs the knob ${name}: it has no defaults`);\n if (!convertible(amount.unit, \"count\")) throw new Error(`the knob ${name} is not a count`);\n if (amount.value === \"infinite\") return Number.POSITIVE_INFINITY;\n const value = valueIn(amount, \"count\");\n if (!(Number.isInteger(value) && value >= 1)) throw new Error(`the knob ${name} is ${value}, not a whole number above zero`);\n return value;\n};\n"],"mappings":";;AAYA,MAAM,wBAAQ,IAAI,QAAqC;;AAGvD,MAAa,YAAY,MAAc,SAAuC;CAC5E,MAAM,MAAM,MAAM,IAAI,IAAI,qBAAK,IAAI,IAAoB;CACvD,MAAM,IAAI,MAAM,GAAG;CACnB,IAAI,IAAI,IAAI;CACZ,aAAa;EACX,IAAI,OAAO,IAAI;CACjB;AACF;;AAGA,MAAa,aAAa,SAA6B,CAAC,GAAI,MAAM,IAAI,IAAI,KAAK,CAAC,CAAE,CAAC,CAAC,KAAK,SAAS,KAAK,CAAC;;;;;;;;ACdxG,MAAa,kBAAkB,OAAmB,MAAc,UAA0B;CACxF,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB,KAAK,qBAAqB;CAC/F,IAAI,CAAC,YAAY,OAAO,MAAM,GAAG,GAAG,MAAM,IAAI,MAAM,YAAY,KAAK,eAAe;CACpF,IAAI,OAAO,UAAU,YAAY,OAAO,OAAO;CAC/C,MAAM,QAAQ,QAAQ,QAAQ,IAAI;CAClC,IAAI,EAAE,SAAS,IAAI,MAAM,IAAI,MAAM,YAAY,KAAK,mBAAmB;CACvE,OAAO;AACT;;AAGA,MAAa,yBAAyB,OAAmB,MAAc,UAA0B;CAC/F,MAAM,QAAQ,eAAe,OAAO,MAAM,KAAK;CAC/C,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,MAAM,YAAY,KAAK,MAAM,MAAM,+BAA+B;CACzG,OAAO;AACT;;;;;AAMA,MAAa,WAAW,OAAmB,MAAc,UAA0B;CACjF,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB,KAAK,qBAAqB;CAC/F,IAAI,CAAC,YAAY,OAAO,MAAM,OAAO,GAAG,MAAM,IAAI,MAAM,YAAY,KAAK,gBAAgB;CACzF,IAAI,OAAO,UAAU,YAAY,OAAO,OAAO;CAC/C,MAAM,QAAQ,QAAQ,QAAQ,OAAO;CACrC,IAAI,EAAE,OAAO,UAAU,KAAK,KAAK,SAAS,IAAI,MAAM,IAAI,MAAM,YAAY,KAAK,MAAM,MAAM,gCAAgC;CAC3H,OAAO;AACT"}
@@ -1,3 +1,3 @@
1
1
  import { S as retryAfterSeconds, _ as freeAt, a as LimitRule, b as limiterProblems, c as LimiterSpec, d as Refused, f as SlidingWindow, g as decide, h as TokenBucket, i as LimitPolicy, l as LimiterStoreSpec, m as TakeStep, n as Decision, o as Limiter, p as StoreFailure, r as KeyState, s as LimiterEviction, t as Allowed, u as RefusalCause, v as idleSpan, x as longestRetryAfter, y as limitRule } from "./policy-G4QJ1ZZq.js";
2
- import { a as keyedLimiter, i as TakeRequest, n as LimiterStore, o as memoryStore, r as MemoryStore, t as KeyedLimiter } from "./limiter-DzTyosou.js";
2
+ import { a as keyedLimiter, i as TakeRequest, n as LimiterStore, o as memoryStore, r as MemoryStore, t as KeyedLimiter } from "./store-DzTyosou.js";
3
3
  export { type Allowed, type Decision, type KeyState, type KeyedLimiter, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterStore, type LimiterStoreSpec, type MemoryStore, type RefusalCause, type Refused, type SlidingWindow, type StoreFailure, type TakeRequest, type TakeStep, type TokenBucket, decide, freeAt, idleSpan, keyedLimiter, limitRule, limiterProblems, longestRetryAfter, memoryStore, retryAfterSeconds };
package/build/redis.d.ts CHANGED
@@ -2,6 +2,7 @@ import { t as Backoff } from "./backoff-B-jGWVDm.js";
2
2
  import { n as Clock } from "./clock-D-38uH_z.js";
3
3
  import { B as Gate, Ct as Operation, E as CheckFn, G as Step, J as Poll, L as Dependency, M as Recorder, N as Check, W as ServiceDescription, et as heartbeatDetection, t as DependencyHandle } from "./lifecycle-Chu9Pri2.js";
4
4
  import { n as ServiceBuilder } from "./builder-DyDB7zM9.js";
5
+ import { n as LimiterStore } from "./store-DzTyosou.js";
5
6
  import { Extent, Quantity } from "@mark1russell7/quantity";
6
7
  import { Machine } from "@mark1russell7/algebra";
7
8
  //#region src/redis/connection.d.ts
@@ -273,6 +274,45 @@ export declare const tcpDial: DialFn;
273
274
  */
274
275
  export declare const redisClient: (name: string, spec: RedisClientSpec, options?: RedisClientOptions) => RedisClient;
275
276
  //#endregion
277
+ //#region src/redis/limiter.d.ts
278
+ /**
279
+ * This script decides one take in one atomic step of Redis. It does the arithmetic of `decide`, and
280
+ * it reads `TIME` of the server, the one clock of all replicas. A test can give the time in its place.
281
+ * - KEYS[1] is the state of the key. KEYS[2] is the index of the limiter, a sorted set of its keys
282
+ * by the time at which each key is free.
283
+ * - ARGV is the type of the rule, its two numbers, the cap of keys, the key, and the time or "".
284
+ * - The state of a bucket is its full time. The state of a window is the time of each take in it.
285
+ * - The state expires at its free time (PX), and the index lets the cap count only busy keys.
286
+ * - The reply is `{1, remaining}`, or `{0, retryAfter, cause}` with the time as text. Redis gives
287
+ * each Lua number as a whole number, thus the fraction of a millisecond goes as text.
288
+ */
289
+ export declare const LIMITER_SCRIPT = "\nlocal kind = ARGV[1]\nlocal a = tonumber(ARGV[2])\nlocal b = tonumber(ARGV[3])\nlocal cap = tonumber(ARGV[4])\nlocal member = ARGV[5]\nlocal now\nif ARGV[6] == '' then\n local t = redis.call('TIME')\n now = tonumber(t[1]) * 1000 + tonumber(t[2]) / 1000\nelse\n now = tonumber(ARGV[6])\nend\nlocal TOL = 1e-6\nlocal function num(x) return string.format('%.6f', x) end\nlocal raw = redis.call('GET', KEYS[1])\nlocal full = nil\nlocal stamps = nil\nif raw then\n if kind == 'token-bucket' then\n full = tonumber(raw)\n else\n stamps = {}\n for x in string.gmatch(raw, '[^,]+') do stamps[#stamps + 1] = tonumber(x) end\n end\nend\nlocal function freeAt()\n if kind == 'token-bucket' then return full end\n if #stamps == 0 then return -math.huge end\n return stamps[#stamps] + b\nend\nif raw and freeAt() <= now then\n redis.call('DEL', KEYS[1])\n redis.call('ZREM', KEYS[2], member)\n raw = nil\n full = nil\n stamps = nil\nend\nif not raw then\n if redis.call('ZCARD', KEYS[2]) >= cap then\n redis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', num(now))\n if redis.call('ZCARD', KEYS[2]) >= cap then\n local first = redis.call('ZRANGE', KEYS[2], 0, 0, 'WITHSCORES')\n return {0, num(tonumber(first[2]) - now), 'cap'}\n end\n end\nend\nlocal remaining\nlocal state\nif kind == 'token-bucket' then\n local span = a * b\n local base = now\n if full and full > now then base = full end\n local nxt = base + a\n if nxt - now > span + TOL then\n return {0, num(nxt - span - now), 'limit'}\n end\n remaining = math.floor((span - (nxt - now)) / a + TOL)\n if remaining < 0 then remaining = 0 end\n full = nxt\n state = num(full)\nelse\n local live = {}\n for _, stamp in ipairs(stamps or {}) do\n if stamp + b > now + TOL then live[#live + 1] = stamp end\n end\n if #live >= a then\n return {0, num(live[1] + b - now), 'limit'}\n end\n live[#live + 1] = now\n remaining = a - #live\n stamps = live\n local parts = {}\n for i, stamp in ipairs(live) do parts[i] = num(stamp) end\n state = table.concat(parts, ',')\nend\nlocal free = freeAt()\nlocal ttl = math.ceil(free - now)\nif ttl < 1 then ttl = 1 end\nredis.call('SET', KEYS[1], state, 'PX', ttl)\nredis.call('ZADD', KEYS[2], num(free), member)\nif redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end\nreturn {1, remaining}\n";
290
+ /** These options make a store of a limiter in Redis. */
291
+ interface RedisLimiterOptions {
292
+ /** This text starts the name of each key of the store in Redis, for example `limit:`. It has no braces. */
293
+ readonly prefix: string;
294
+ /**
295
+ * This clock replaces `TIME` of the server, for a test that compares the store with `decide`. Without
296
+ * it, the script reads `TIME`, thus each replica gets the one clock of the store.
297
+ */
298
+ readonly clock?: Clock | undefined;
299
+ }
300
+ /** A Redis client part is the part of a Redis client that the store uses: `send`. */
301
+ type RedisSender = Pick<RedisClient, "send">;
302
+ /**
303
+ * This function makes a store that the replicas share, in Redis 7. Each take is one `EVAL` of
304
+ * `LIMITER_SCRIPT`. The client does not send it again, because a second take counts two times.
305
+ * - The names of the keys have the limiter in braces, a hash tag. Thus the state and the index of a
306
+ * limiter are in one slot of a Redis Cluster.
307
+ * - Each state expires at the time that the key is free, thus the eviction is exact, as in the memory
308
+ * store. The index of the limiter bounds the count of busy keys to `maxKeys`.
309
+ * - A reply of an error, an outcome that is not certain, or a reply of another shape rejects the
310
+ * take of the store. `keyedLimiter` then applies the `onStoreFailure` of the spec.
311
+ * - `TIME` is the wall clock of the Redis host, not a monotonic clock. Thus a step of that clock
312
+ * moves the counts of each key. The replicas still share one clock.
313
+ */
314
+ export declare const redisLimiterStore: (client: RedisSender, options: RedisLimiterOptions) => LimiterStore;
315
+ //#endregion
276
316
  //#region src/redis/machine.d.ts
277
317
  /**
278
318
  * The Redis client as a `mark1russell7.machine/1` document. Its states are the states of ioredis.
@@ -292,5 +332,5 @@ export declare const REDIS_OPERATIONS: Readonly<Record<string, Operation>>;
292
332
  /** This function gives the name of a command as the table writes it: upper case, and CLIENT ID with its subcommand. */
293
333
  export declare const commandName: (command: string, args: readonly unknown[]) => string;
294
334
  //#endregion
295
- export { type CheckOptions, type ClientStep, type ConnectionFactory, type Detector, type DialFn, type Join, type Meeting, type RedisClient, type RedisClientOptions, type RedisClientSpec, type RedisConnection, type RedisConnectionOptions, type RedisKnobs, type RedisSocket, type Sent, type Situation, heartbeatDetection };
335
+ export { type CheckOptions, type ClientStep, type ConnectionFactory, type Detector, type DialFn, type Join, type Meeting, type RedisClient, type RedisClientOptions, type RedisClientSpec, type RedisConnection, type RedisConnectionOptions, type RedisKnobs, type RedisLimiterOptions, type RedisSender, type RedisSocket, type Sent, type Situation, heartbeatDetection };
296
336
  //# sourceMappingURL=redis.d.ts.map
package/build/redis.js CHANGED
@@ -688,6 +688,159 @@ const redisClient = (name, spec, options = {}) => {
688
688
  };
689
689
  };
690
690
  //#endregion
691
+ //#region src/redis/limiter.ts
692
+ /**
693
+ * This script decides one take in one atomic step of Redis. It does the arithmetic of `decide`, and
694
+ * it reads `TIME` of the server, the one clock of all replicas. A test can give the time in its place.
695
+ * - KEYS[1] is the state of the key. KEYS[2] is the index of the limiter, a sorted set of its keys
696
+ * by the time at which each key is free.
697
+ * - ARGV is the type of the rule, its two numbers, the cap of keys, the key, and the time or "".
698
+ * - The state of a bucket is its full time. The state of a window is the time of each take in it.
699
+ * - The state expires at its free time (PX), and the index lets the cap count only busy keys.
700
+ * - The reply is `{1, remaining}`, or `{0, retryAfter, cause}` with the time as text. Redis gives
701
+ * each Lua number as a whole number, thus the fraction of a millisecond goes as text.
702
+ */
703
+ const LIMITER_SCRIPT = `
704
+ local kind = ARGV[1]
705
+ local a = tonumber(ARGV[2])
706
+ local b = tonumber(ARGV[3])
707
+ local cap = tonumber(ARGV[4])
708
+ local member = ARGV[5]
709
+ local now
710
+ if ARGV[6] == '' then
711
+ local t = redis.call('TIME')
712
+ now = tonumber(t[1]) * 1000 + tonumber(t[2]) / 1000
713
+ else
714
+ now = tonumber(ARGV[6])
715
+ end
716
+ local TOL = 1e-6
717
+ local function num(x) return string.format('%.6f', x) end
718
+ local raw = redis.call('GET', KEYS[1])
719
+ local full = nil
720
+ local stamps = nil
721
+ if raw then
722
+ if kind == 'token-bucket' then
723
+ full = tonumber(raw)
724
+ else
725
+ stamps = {}
726
+ for x in string.gmatch(raw, '[^,]+') do stamps[#stamps + 1] = tonumber(x) end
727
+ end
728
+ end
729
+ local function freeAt()
730
+ if kind == 'token-bucket' then return full end
731
+ if #stamps == 0 then return -math.huge end
732
+ return stamps[#stamps] + b
733
+ end
734
+ if raw and freeAt() <= now then
735
+ redis.call('DEL', KEYS[1])
736
+ redis.call('ZREM', KEYS[2], member)
737
+ raw = nil
738
+ full = nil
739
+ stamps = nil
740
+ end
741
+ if not raw then
742
+ if redis.call('ZCARD', KEYS[2]) >= cap then
743
+ redis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', num(now))
744
+ if redis.call('ZCARD', KEYS[2]) >= cap then
745
+ local first = redis.call('ZRANGE', KEYS[2], 0, 0, 'WITHSCORES')
746
+ return {0, num(tonumber(first[2]) - now), 'cap'}
747
+ end
748
+ end
749
+ end
750
+ local remaining
751
+ local state
752
+ if kind == 'token-bucket' then
753
+ local span = a * b
754
+ local base = now
755
+ if full and full > now then base = full end
756
+ local nxt = base + a
757
+ if nxt - now > span + TOL then
758
+ return {0, num(nxt - span - now), 'limit'}
759
+ end
760
+ remaining = math.floor((span - (nxt - now)) / a + TOL)
761
+ if remaining < 0 then remaining = 0 end
762
+ full = nxt
763
+ state = num(full)
764
+ else
765
+ local live = {}
766
+ for _, stamp in ipairs(stamps or {}) do
767
+ if stamp + b > now + TOL then live[#live + 1] = stamp end
768
+ end
769
+ if #live >= a then
770
+ return {0, num(live[1] + b - now), 'limit'}
771
+ end
772
+ live[#live + 1] = now
773
+ remaining = a - #live
774
+ stamps = live
775
+ local parts = {}
776
+ for i, stamp in ipairs(live) do parts[i] = num(stamp) end
777
+ state = table.concat(parts, ',')
778
+ end
779
+ local free = freeAt()
780
+ local ttl = math.ceil(free - now)
781
+ if ttl < 1 then ttl = 1 end
782
+ redis.call('SET', KEYS[1], state, 'PX', ttl)
783
+ redis.call('ZADD', KEYS[2], num(free), member)
784
+ if redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end
785
+ return {1, remaining}
786
+ `;
787
+ /**
788
+ * This function makes a store that the replicas share, in Redis 7. Each take is one `EVAL` of
789
+ * `LIMITER_SCRIPT`. The client does not send it again, because a second take counts two times.
790
+ * - The names of the keys have the limiter in braces, a hash tag. Thus the state and the index of a
791
+ * limiter are in one slot of a Redis Cluster.
792
+ * - Each state expires at the time that the key is free, thus the eviction is exact, as in the memory
793
+ * store. The index of the limiter bounds the count of busy keys to `maxKeys`.
794
+ * - A reply of an error, an outcome that is not certain, or a reply of another shape rejects the
795
+ * take of the store. `keyedLimiter` then applies the `onStoreFailure` of the spec.
796
+ * - `TIME` is the wall clock of the Redis host, not a monotonic clock. Thus a step of that clock
797
+ * moves the counts of each key. The replicas still share one clock.
798
+ */
799
+ const redisLimiterStore = (client, options) => {
800
+ if (/[{}]/.test(options.prefix)) throw new Error(`the prefix ${options.prefix} of a limiter store has a brace: the store puts the hash tag in it`);
801
+ return {
802
+ kind: "shared",
803
+ async take({ limiter, key, rule, maxKeys }) {
804
+ const base = `${options.prefix}{${limiter}}`;
805
+ const numbers = rule.type === "token-bucket" ? [rule.interval, rule.burst] : [rule.limit, rule.window];
806
+ const now = options.clock === void 0 ? "" : String(options.clock.now());
807
+ const sent = await client.send("EVAL", [
808
+ LIMITER_SCRIPT,
809
+ 2,
810
+ `${base}:key:${key}`,
811
+ `${base}:keys`,
812
+ rule.type,
813
+ String(numbers[0]),
814
+ String(numbers[1]),
815
+ String(maxKeys),
816
+ key,
817
+ now
818
+ ]);
819
+ if (sent.outcome !== "ok") throw new Error(`the limiter store ${limiter} failed (${sent.outcome}): ${sent.error}`);
820
+ return replyOf(limiter, sent.value);
821
+ }
822
+ };
823
+ };
824
+ /** This function gives the decision of a reply of `LIMITER_SCRIPT`. A reply of another shape throws. */
825
+ const replyOf = (limiter, reply) => {
826
+ if (Array.isArray(reply) && Number(reply[0]) === 1 && reply.length === 2) return {
827
+ allowed: true,
828
+ remaining: Number(reply[1])
829
+ };
830
+ if (Array.isArray(reply) && Number(reply[0]) === 0 && reply.length === 3 && (reply[2] === "limit" || reply[2] === "cap")) {
831
+ const retryAfter = Number(reply[1]);
832
+ if (Number.isFinite(retryAfter) && retryAfter > 0) return {
833
+ allowed: false,
834
+ retryAfter: {
835
+ value: retryAfter,
836
+ unit: "ms"
837
+ },
838
+ cause: reply[2]
839
+ };
840
+ }
841
+ throw new Error(`the limiter store ${limiter} gave a reply that is not a decision: ${JSON.stringify(reply)}`);
842
+ };
843
+ //#endregion
691
844
  //#region src/redis/machine.ts
692
845
  const t = (id, from, on, to, extra = {}) => ({
693
846
  id,
@@ -850,6 +1003,6 @@ const REDIS_CLIENT_MACHINE = {
850
1003
  ]
851
1004
  };
852
1005
  //#endregion
853
- export { REDIS_CLIENT_MACHINE, REDIS_OPERATIONS, commandName, firstDetector, heartbeatDetection, ioredis, meetings, redisClient, retryBackoff, specProblems, tcpDial, unwatched };
1006
+ export { LIMITER_SCRIPT, REDIS_CLIENT_MACHINE, REDIS_OPERATIONS, commandName, firstDetector, heartbeatDetection, ioredis, meetings, redisClient, redisLimiterStore, retryBackoff, specProblems, tcpDial, unwatched };
854
1007
 
855
1008
  //# sourceMappingURL=redis.js.map