@mtcute/dispatcher 0.17.2 → 0.18.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 (111) hide show
  1. package/callback-data-builder.cjs +126 -0
  2. package/callback-data-builder.d.cts +49 -0
  3. package/callback-data-builder.js +121 -0
  4. package/callback-data-builder.test.d.cts +1 -0
  5. package/context/base.d.cts +9 -0
  6. package/context/business-message.cjs +143 -0
  7. package/context/business-message.d.cts +60 -0
  8. package/context/business-message.js +138 -0
  9. package/context/callback-query.cjs +92 -0
  10. package/context/callback-query.d.cts +62 -0
  11. package/context/callback-query.js +87 -0
  12. package/context/chat-join-request.cjs +32 -0
  13. package/context/chat-join-request.d.cts +17 -0
  14. package/context/chat-join-request.d.ts +1 -1
  15. package/context/chat-join-request.js +27 -0
  16. package/context/chosen-inline-result.cjs +30 -0
  17. package/context/chosen-inline-result.d.cts +22 -0
  18. package/context/chosen-inline-result.d.ts +1 -1
  19. package/context/chosen-inline-result.js +25 -0
  20. package/context/index.d.cts +9 -0
  21. package/context/inline-query.cjs +20 -0
  22. package/context/inline-query.d.cts +15 -0
  23. package/context/inline-query.js +15 -0
  24. package/context/message.cjs +182 -0
  25. package/context/message.d.cts +82 -0
  26. package/context/message.d.ts +2 -2
  27. package/context/message.js +177 -0
  28. package/context/parse.cjs +45 -0
  29. package/context/parse.d.cts +13 -0
  30. package/context/parse.js +40 -0
  31. package/context/pre-checkout-query.cjs +24 -0
  32. package/context/pre-checkout-query.d.cts +17 -0
  33. package/context/pre-checkout-query.d.ts +1 -1
  34. package/context/pre-checkout-query.js +19 -0
  35. package/context/scene-transition.cjs +52 -0
  36. package/context/scene-transition.d.cts +24 -0
  37. package/context/scene-transition.d.ts +1 -1
  38. package/context/scene-transition.js +47 -0
  39. package/dispatcher.cjs +860 -0
  40. package/dispatcher.d.cts +880 -0
  41. package/dispatcher.d.ts +4 -4
  42. package/dispatcher.js +855 -0
  43. package/filters/bots.cjs +94 -0
  44. package/filters/bots.d.cts +62 -0
  45. package/filters/bots.d.ts +2 -4
  46. package/filters/bots.js +89 -0
  47. package/filters/bots.test.d.cts +1 -0
  48. package/filters/bundle.cjs +79 -0
  49. package/filters/bundle.d.cts +10 -0
  50. package/filters/bundle.js +74 -0
  51. package/filters/chat.cjs +43 -0
  52. package/filters/chat.d.cts +29 -0
  53. package/filters/chat.d.ts +8 -6
  54. package/filters/chat.js +38 -0
  55. package/filters/group.cjs +49 -0
  56. package/filters/group.d.cts +26 -0
  57. package/filters/group.js +44 -0
  58. package/filters/index.d.cts +4 -0
  59. package/filters/logic.cjs +57 -0
  60. package/filters/logic.d.cts +29 -0
  61. package/filters/logic.js +52 -0
  62. package/filters/logic.test.d.cts +1 -0
  63. package/filters/message.cjs +130 -0
  64. package/filters/message.d.cts +223 -0
  65. package/filters/message.d.ts +5 -1
  66. package/filters/message.js +125 -0
  67. package/filters/state.cjs +21 -0
  68. package/filters/state.d.cts +15 -0
  69. package/filters/state.js +16 -0
  70. package/filters/text.cjs +87 -0
  71. package/filters/text.d.cts +64 -0
  72. package/filters/text.d.ts +2 -2
  73. package/filters/text.js +82 -0
  74. package/filters/types.d.cts +91 -0
  75. package/filters/updates.cjs +27 -0
  76. package/filters/updates.d.cts +39 -0
  77. package/filters/updates.js +22 -0
  78. package/filters/user.cjs +57 -0
  79. package/filters/user.d.cts +24 -0
  80. package/filters/user.js +52 -0
  81. package/handler.d.cts +41 -0
  82. package/handler.d.ts +1 -1
  83. package/index.cjs +37 -2528
  84. package/index.js +16 -2507
  85. package/package.json +10 -9
  86. package/propagation.cjs +15 -0
  87. package/propagation.d.cts +22 -0
  88. package/propagation.js +10 -0
  89. package/state/index.d.cts +5 -0
  90. package/state/key.cjs +32 -0
  91. package/state/key.d.cts +24 -0
  92. package/state/key.js +27 -0
  93. package/state/provider.d.cts +5 -0
  94. package/state/providers/index.d.cts +2 -0
  95. package/state/providers/memory.cjs +79 -0
  96. package/state/providers/memory.d.cts +29 -0
  97. package/state/providers/memory.js +74 -0
  98. package/state/providers/sqlite.cjs +99 -0
  99. package/state/providers/sqlite.d.cts +28 -0
  100. package/state/providers/sqlite.js +94 -0
  101. package/state/repository.d.cts +62 -0
  102. package/state/service.cjs +69 -0
  103. package/state/service.d.cts +20 -0
  104. package/state/service.js +64 -0
  105. package/state/update-state.cjs +206 -0
  106. package/state/update-state.d.cts +151 -0
  107. package/state/update-state.d.ts +1 -1
  108. package/state/update-state.js +201 -0
  109. package/wizard.cjs +90 -0
  110. package/wizard.d.cts +64 -0
  111. package/wizard.js +85 -0
@@ -0,0 +1,201 @@
1
+ import { sleep } from "@fuman/utils";
2
+ import { MtArgumentError, MtcuteError } from "@mtcute/core";
3
+ class RateLimitError extends MtcuteError {
4
+ constructor(reset) {
5
+ super("You are being rate limited.");
6
+ this.reset = reset;
7
+ }
8
+ }
9
+ class UpdateState {
10
+ _key;
11
+ _localKey;
12
+ _storage;
13
+ _scene;
14
+ _scoped;
15
+ _cached;
16
+ _localStorage;
17
+ _localKeyBase;
18
+ constructor(storage, key, scene, scoped, customStorage, customKey) {
19
+ this._storage = storage;
20
+ this._key = key;
21
+ this._scene = scene;
22
+ this._scoped = scoped;
23
+ this._localStorage = customStorage ?? storage;
24
+ this._localKeyBase = customKey ?? key;
25
+ this._updateLocalKey();
26
+ }
27
+ /** Name of the current scene */
28
+ get scene() {
29
+ return this._scene;
30
+ }
31
+ _updateLocalKey() {
32
+ if (!this._scoped) {
33
+ this._localKey = this._localKeyBase;
34
+ } else {
35
+ this._localKey = this._scene ? `${this._scene}_${this._localKeyBase}` : this._localKeyBase;
36
+ }
37
+ }
38
+ async get(fallback, force) {
39
+ if (typeof fallback === "boolean") {
40
+ force = fallback;
41
+ fallback = void 0;
42
+ }
43
+ if (!force && this._cached !== void 0) {
44
+ if (!this._cached && fallback) {
45
+ return typeof fallback === "function" ? fallback() : fallback;
46
+ }
47
+ return this._cached;
48
+ }
49
+ let res = await this._localStorage.getState(this._localKey);
50
+ if (!res && fallback) {
51
+ res = typeof fallback === "function" ? fallback() : fallback;
52
+ }
53
+ this._cached = res;
54
+ return res;
55
+ }
56
+ /**
57
+ * Set new state to the storage
58
+ *
59
+ * @param state New state
60
+ * @param ttl TTL for the new state (in seconds)
61
+ */
62
+ async set(state, ttl) {
63
+ this._cached = state;
64
+ await this._localStorage.setState(this._localKey, state, ttl);
65
+ }
66
+ /**
67
+ * Merge the given object to the current state.
68
+ *
69
+ * > **Note**: If the storage currently has no state,
70
+ * > then `fallback` must be provided.
71
+ *
72
+ * Basically a shorthand to calling `.get()`,
73
+ * modifying and then calling `.set()`
74
+ *
75
+ * @param state State to be merged
76
+ * @param fallback Default state
77
+ * @param ttl TTL for the new state (in seconds)
78
+ * @param forceLoad Whether to force load the old state from storage
79
+ */
80
+ async merge(state, params = {}) {
81
+ const { fallback, ttl, forceLoad } = params;
82
+ const old = await this.get(forceLoad);
83
+ if (!old) {
84
+ if (!fallback) {
85
+ throw new MtArgumentError("Cannot use merge on empty state without fallback.");
86
+ }
87
+ const fallback_ = typeof fallback === "function" ? fallback() : fallback;
88
+ await this.set({ ...fallback_, ...state }, ttl);
89
+ } else {
90
+ await this.set({ ...old, ...state }, ttl);
91
+ }
92
+ return this._cached;
93
+ }
94
+ /**
95
+ * Delete the state from the storage
96
+ */
97
+ async delete() {
98
+ this._cached = null;
99
+ await this._localStorage.deleteState(this._localKey);
100
+ }
101
+ /**
102
+ * Enter some scene
103
+ */
104
+ async enter(scene, params) {
105
+ const { with: with_, ttl, reset = true } = params ?? {};
106
+ if (reset && this._scoped) await this.delete();
107
+ if (!scene["_scene"]) {
108
+ throw new MtArgumentError("Cannot enter a non-scene Dispatcher");
109
+ }
110
+ if (!scene["_parent"]) {
111
+ throw new MtArgumentError("This scene has not been registered");
112
+ }
113
+ this._scene = scene["_scene"];
114
+ this._scoped = scene["_sceneScoped"];
115
+ this._updateLocalKey();
116
+ await this._storage.setCurrentScene(this._key, this._scene, ttl);
117
+ if (with_) {
118
+ if (scene["_customStateKeyDelegate"]) {
119
+ throw new MtArgumentError("Cannot use `with` parameter when the scene uses a custom state key delegate");
120
+ }
121
+ await scene.getState(this._key).set(with_, ttl);
122
+ }
123
+ }
124
+ /**
125
+ * Exit from current scene to the root
126
+ *
127
+ * @param reset
128
+ * Whether to reset scene state (only applicable in case this is a scoped scene)
129
+ */
130
+ async exit(reset = true) {
131
+ if (reset && this._scoped) await this.delete();
132
+ this._scene = null;
133
+ this._updateLocalKey();
134
+ await this._storage.deleteCurrentScene(this._key);
135
+ }
136
+ /**
137
+ * Rate limit some handler.
138
+ *
139
+ * When the rate limit exceeds, {@link RateLimitError} is thrown.
140
+ *
141
+ * This is a simple rate-limiting solution that uses
142
+ * the same key as the state. If you need something more
143
+ * sophisticated and/or customizable, you'll have to implement
144
+ * your own rate-limiter.
145
+ *
146
+ * > **Note**: `key` is used to prefix the local key
147
+ * > derived using the given key delegate.
148
+ *
149
+ * @param key Key of the rate limit
150
+ * @param limit Maximum number of requests in `window`
151
+ * @param window Window size in seconds
152
+ * @returns Tuple containing the number of remaining and
153
+ * unix time in ms when the user can try again
154
+ */
155
+ async rateLimit(key, limit, window) {
156
+ const [remaining, reset] = await this._localStorage.getRateLimit(`${key}:${this._localKey}`, limit, window);
157
+ if (!remaining) {
158
+ throw new RateLimitError(reset);
159
+ }
160
+ return [remaining - 1, reset];
161
+ }
162
+ /**
163
+ * Throttle some handler.
164
+ *
165
+ * When the rate limit exceeds, this function waits for it to reset.
166
+ *
167
+ * This is a simple wrapper over {@link rateLimit}, and follows the same logic.
168
+ *
169
+ * > **Note**: `key` is used to prefix the local key
170
+ * > derived using the given key delegate.
171
+ *
172
+ * @param key Key of the rate limit
173
+ * @param limit Maximum number of requests in `window`
174
+ * @param window Window size in seconds
175
+ * @returns Tuple containing the number of remaining and
176
+ * unix time in ms when the user can try again
177
+ */
178
+ async throttle(key, limit, window) {
179
+ try {
180
+ return await this.rateLimit(key, limit, window);
181
+ } catch (e) {
182
+ if (e instanceof RateLimitError) {
183
+ await sleep(e.reset - Date.now());
184
+ return this.throttle(key, limit, window);
185
+ }
186
+ throw e;
187
+ }
188
+ }
189
+ /**
190
+ * Reset the rate limit
191
+ *
192
+ * @param key Key of the rate limit
193
+ */
194
+ async resetRateLimit(key) {
195
+ await this._localStorage.resetRateLimit(`${key}:${this._localKey}`);
196
+ }
197
+ }
198
+ export {
199
+ RateLimitError,
200
+ UpdateState
201
+ };
package/wizard.cjs ADDED
@@ -0,0 +1,90 @@
1
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
2
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
3
+ console.warn("[mtcute-workspace] CommonJS support is deprecated and will be removed in 0.20.0. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
4
+ console.warn("[mtcute-workspace] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
5
+ }
6
+ "use strict";
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ const dispatcher = require("./dispatcher.cjs");
9
+ const logic = require("./filters/logic.cjs");
10
+ const state = require("./filters/state.cjs");
11
+ var WizardSceneAction = /* @__PURE__ */ ((WizardSceneAction2) => {
12
+ WizardSceneAction2["Next"] = "next";
13
+ WizardSceneAction2["Stay"] = "stay";
14
+ WizardSceneAction2["Exit"] = "exit";
15
+ return WizardSceneAction2;
16
+ })(WizardSceneAction || {});
17
+ class WizardScene extends dispatcher.Dispatcher {
18
+ _steps = 0;
19
+ _defaultState = {};
20
+ constructor(name, params) {
21
+ super(void 0, { sceneName: name, ...params });
22
+ }
23
+ setDefaultState(defaultState) {
24
+ this._defaultState = defaultState;
25
+ }
26
+ /**
27
+ * Get the total number of registered steps
28
+ */
29
+ get totalSteps() {
30
+ return this._steps;
31
+ }
32
+ /**
33
+ * Go to the Nth step
34
+ */
35
+ async goToStep(state2, step) {
36
+ if (step >= this._steps) {
37
+ await state2.exit();
38
+ } else {
39
+ await state2.merge({ $step: step }, { fallback: this._defaultState });
40
+ }
41
+ }
42
+ /**
43
+ * Skip N steps
44
+ */
45
+ async skip(state2, count = 1) {
46
+ const { $step } = await state2.get() || {};
47
+ if ($step === void 0) throw new Error("Wizard state is not initialized");
48
+ return this.goToStep(state2, $step + count);
49
+ }
50
+ /**
51
+ * Filter that will only pass if the current step is `step`
52
+ */
53
+ // eslint-disable-next-line ts/no-empty-object-type
54
+ static onNthStep(step) {
55
+ const filter = state.state((it) => it.$step === step);
56
+ if (step === 0) return logic.or(state.stateEmpty, filter);
57
+ return filter;
58
+ }
59
+ /**
60
+ * Filter that will only pass if the current step is the one after last one added
61
+ */
62
+ // eslint-disable-next-line ts/no-empty-object-type
63
+ onCurrentStep() {
64
+ return WizardScene.onNthStep(this._steps);
65
+ }
66
+ /**
67
+ * Add a step to the wizard
68
+ */
69
+ addStep(handler) {
70
+ const step = this._steps++;
71
+ this.onNewMessage(WizardScene.onNthStep(step), async (msg, state2) => {
72
+ const result = await handler(msg, state2);
73
+ if (typeof result === "number") {
74
+ await this.goToStep(state2, result);
75
+ return;
76
+ }
77
+ switch (result) {
78
+ case "next": {
79
+ await this.goToStep(state2, step + 1);
80
+ break;
81
+ }
82
+ case "exit":
83
+ await state2.exit();
84
+ break;
85
+ }
86
+ });
87
+ }
88
+ }
89
+ exports.WizardScene = WizardScene;
90
+ exports.WizardSceneAction = WizardSceneAction;
package/wizard.d.cts ADDED
@@ -0,0 +1,64 @@
1
+ import { MaybePromise } from '@mtcute/core';
2
+ import { MessageContext } from './context/message.js';
3
+ import { DispatcherParams, Dispatcher } from './dispatcher.js';
4
+ import { UpdateFilter } from './filters/index.js';
5
+ import { UpdateState } from './state/update-state.js';
6
+ /**
7
+ * Action for the wizard scene.
8
+ *
9
+ * `Next`: Continue to the next registered step
10
+ * (or exit, if this is the last step)
11
+ *
12
+ * `Stay`: Stay on the same step
13
+ *
14
+ * `Exit`: Exit from the wizard scene
15
+ *
16
+ * You can also return a `number` to jump to that step
17
+ */
18
+ export declare enum WizardSceneAction {
19
+ Next = "next",
20
+ Stay = "stay",
21
+ Exit = "exit"
22
+ }
23
+ interface WizardInternalState {
24
+ $step?: number;
25
+ }
26
+ /**
27
+ * Wizard is a special type of Dispatcher
28
+ * that can be used to simplify implementing
29
+ * step-by-step scenes.
30
+ */
31
+ export declare class WizardScene<State extends object> extends Dispatcher<State & WizardInternalState> {
32
+ private _steps;
33
+ private _defaultState;
34
+ constructor(name: string, params?: Omit<DispatcherParams, 'sceneName'>);
35
+ static for: never;
36
+ static child: never;
37
+ static scene: never;
38
+ setDefaultState(defaultState: State): void;
39
+ /**
40
+ * Get the total number of registered steps
41
+ */
42
+ get totalSteps(): number;
43
+ /**
44
+ * Go to the Nth step
45
+ */
46
+ goToStep(state: UpdateState<WizardInternalState>, step: number): Promise<void>;
47
+ /**
48
+ * Skip N steps
49
+ */
50
+ skip(state: UpdateState<WizardInternalState>, count?: number): Promise<void>;
51
+ /**
52
+ * Filter that will only pass if the current step is `step`
53
+ */
54
+ static onNthStep(step: number): UpdateFilter<any, {}, WizardInternalState>;
55
+ /**
56
+ * Filter that will only pass if the current step is the one after last one added
57
+ */
58
+ onCurrentStep(): UpdateFilter<any, {}, WizardInternalState>;
59
+ /**
60
+ * Add a step to the wizard
61
+ */
62
+ addStep(handler: (msg: MessageContext, state: UpdateState<State & WizardInternalState>) => MaybePromise<WizardSceneAction | number>): void;
63
+ }
64
+ export {};
package/wizard.js ADDED
@@ -0,0 +1,85 @@
1
+ import { Dispatcher } from "./dispatcher.js";
2
+ import { or } from "./filters/logic.js";
3
+ import { state, stateEmpty } from "./filters/state.js";
4
+ var WizardSceneAction = /* @__PURE__ */ ((WizardSceneAction2) => {
5
+ WizardSceneAction2["Next"] = "next";
6
+ WizardSceneAction2["Stay"] = "stay";
7
+ WizardSceneAction2["Exit"] = "exit";
8
+ return WizardSceneAction2;
9
+ })(WizardSceneAction || {});
10
+ class WizardScene extends Dispatcher {
11
+ _steps = 0;
12
+ _defaultState = {};
13
+ constructor(name, params) {
14
+ super(void 0, { sceneName: name, ...params });
15
+ }
16
+ setDefaultState(defaultState) {
17
+ this._defaultState = defaultState;
18
+ }
19
+ /**
20
+ * Get the total number of registered steps
21
+ */
22
+ get totalSteps() {
23
+ return this._steps;
24
+ }
25
+ /**
26
+ * Go to the Nth step
27
+ */
28
+ async goToStep(state2, step) {
29
+ if (step >= this._steps) {
30
+ await state2.exit();
31
+ } else {
32
+ await state2.merge({ $step: step }, { fallback: this._defaultState });
33
+ }
34
+ }
35
+ /**
36
+ * Skip N steps
37
+ */
38
+ async skip(state2, count = 1) {
39
+ const { $step } = await state2.get() || {};
40
+ if ($step === void 0) throw new Error("Wizard state is not initialized");
41
+ return this.goToStep(state2, $step + count);
42
+ }
43
+ /**
44
+ * Filter that will only pass if the current step is `step`
45
+ */
46
+ // eslint-disable-next-line ts/no-empty-object-type
47
+ static onNthStep(step) {
48
+ const filter = state((it) => it.$step === step);
49
+ if (step === 0) return or(stateEmpty, filter);
50
+ return filter;
51
+ }
52
+ /**
53
+ * Filter that will only pass if the current step is the one after last one added
54
+ */
55
+ // eslint-disable-next-line ts/no-empty-object-type
56
+ onCurrentStep() {
57
+ return WizardScene.onNthStep(this._steps);
58
+ }
59
+ /**
60
+ * Add a step to the wizard
61
+ */
62
+ addStep(handler) {
63
+ const step = this._steps++;
64
+ this.onNewMessage(WizardScene.onNthStep(step), async (msg, state2) => {
65
+ const result = await handler(msg, state2);
66
+ if (typeof result === "number") {
67
+ await this.goToStep(state2, result);
68
+ return;
69
+ }
70
+ switch (result) {
71
+ case "next": {
72
+ await this.goToStep(state2, step + 1);
73
+ break;
74
+ }
75
+ case "exit":
76
+ await state2.exit();
77
+ break;
78
+ }
79
+ });
80
+ }
81
+ }
82
+ export {
83
+ WizardScene,
84
+ WizardSceneAction
85
+ };