@hub3js/core 0.58.2-next.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 (162) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/dist/hub/store/mod.js +2 -0
  3. package/dist/hub/store/mod.js.map +7 -0
  4. package/dist/legacy/mod.js +2 -0
  5. package/dist/legacy/mod.js.map +7 -0
  6. package/dist/mod.js +2 -0
  7. package/dist/mod.js.map +7 -0
  8. package/dist/namespaces/common/mod.js +2 -0
  9. package/dist/namespaces/common/mod.js.map +7 -0
  10. package/dist/namespaces/cosmos/mod.js +2 -0
  11. package/dist/namespaces/cosmos/mod.js.map +7 -0
  12. package/dist/namespaces/evm/mod.js +2 -0
  13. package/dist/namespaces/evm/mod.js.map +7 -0
  14. package/dist/namespaces/solana/mod.js +2 -0
  15. package/dist/namespaces/solana/mod.js.map +7 -0
  16. package/dist/namespaces/starknet/mod.js +2 -0
  17. package/dist/namespaces/starknet/mod.js.map +7 -0
  18. package/dist/namespaces/sui/mod.js +2 -0
  19. package/dist/namespaces/sui/mod.js.map +7 -0
  20. package/dist/namespaces/tron/mod.js +2 -0
  21. package/dist/namespaces/tron/mod.js.map +7 -0
  22. package/dist/namespaces/utxo/mod.js +2 -0
  23. package/dist/namespaces/utxo/mod.js.map +7 -0
  24. package/dist/namespaces/xrpl/mod.js +2 -0
  25. package/dist/namespaces/xrpl/mod.js.map +7 -0
  26. package/dist/utils/mod.js +2 -0
  27. package/dist/utils/mod.js.map +7 -0
  28. package/legacy/package.json +8 -0
  29. package/namespaces/common/package.json +8 -0
  30. package/namespaces/cosmos/package.json +8 -0
  31. package/namespaces/evm/package.json +8 -0
  32. package/namespaces/solana/package.json +8 -0
  33. package/namespaces/starknet/package.json +8 -0
  34. package/namespaces/sui/package.json +8 -0
  35. package/namespaces/tron/package.json +8 -0
  36. package/namespaces/utxo/package.json +8 -0
  37. package/namespaces/xrpl/package.json +8 -0
  38. package/package.json +100 -0
  39. package/readme.md +4 -0
  40. package/src/builders/action.ts +86 -0
  41. package/src/builders/mod.ts +5 -0
  42. package/src/builders/namespace.ts +258 -0
  43. package/src/builders/provider.ts +61 -0
  44. package/src/builders/types.ts +26 -0
  45. package/src/hub/helpers.ts +11 -0
  46. package/src/hub/hub.ts +128 -0
  47. package/src/hub/mod.ts +18 -0
  48. package/src/hub/namespaces/errors.ts +11 -0
  49. package/src/hub/namespaces/mod.ts +10 -0
  50. package/src/hub/namespaces/namespace.test.ts +443 -0
  51. package/src/hub/namespaces/namespace.ts +470 -0
  52. package/src/hub/namespaces/types.ts +58 -0
  53. package/src/hub/provider/mod.ts +10 -0
  54. package/src/hub/provider/provider.test.ts +232 -0
  55. package/src/hub/provider/provider.ts +335 -0
  56. package/src/hub/provider/types.ts +54 -0
  57. package/src/hub/store/events.ts +89 -0
  58. package/src/hub/store/extend.ts +125 -0
  59. package/src/hub/store/hub.ts +18 -0
  60. package/src/hub/store/mod.ts +25 -0
  61. package/src/hub/store/namespaces.ts +177 -0
  62. package/src/hub/store/providers.ts +169 -0
  63. package/src/hub/store/selectors.ts +59 -0
  64. package/src/hub/store/store.test.ts +50 -0
  65. package/src/hub/store/store.ts +30 -0
  66. package/src/legacy/helpers.ts +75 -0
  67. package/src/legacy/mod.ts +44 -0
  68. package/src/legacy/persistor.ts +19 -0
  69. package/src/legacy/types.ts +303 -0
  70. package/src/legacy/utils.ts +20 -0
  71. package/src/legacy/wallet.ts +552 -0
  72. package/src/mod.ts +44 -0
  73. package/src/namespaces/common/actions.ts +12 -0
  74. package/src/namespaces/common/after.ts +8 -0
  75. package/src/namespaces/common/and.ts +42 -0
  76. package/src/namespaces/common/before.ts +9 -0
  77. package/src/namespaces/common/builders.ts +14 -0
  78. package/src/namespaces/common/helpers.ts +10 -0
  79. package/src/namespaces/common/hooks/changeAccountSubscriber.test.ts +173 -0
  80. package/src/namespaces/common/hooks/changeAccountSubscriber.ts +236 -0
  81. package/src/namespaces/common/mod.ts +23 -0
  82. package/src/namespaces/common/or.ts +17 -0
  83. package/src/namespaces/common/types.ts +27 -0
  84. package/src/namespaces/common/utils.ts +43 -0
  85. package/src/namespaces/cosmos/actions.ts +3 -0
  86. package/src/namespaces/cosmos/after.ts +3 -0
  87. package/src/namespaces/cosmos/and.ts +5 -0
  88. package/src/namespaces/cosmos/before.ts +3 -0
  89. package/src/namespaces/cosmos/builders.ts +15 -0
  90. package/src/namespaces/cosmos/constants.ts +1 -0
  91. package/src/namespaces/cosmos/mod.ts +15 -0
  92. package/src/namespaces/cosmos/types.ts +31 -0
  93. package/src/namespaces/cosmos/utils.ts +21 -0
  94. package/src/namespaces/evm/actions.ts +134 -0
  95. package/src/namespaces/evm/after.ts +3 -0
  96. package/src/namespaces/evm/and.ts +5 -0
  97. package/src/namespaces/evm/before.ts +3 -0
  98. package/src/namespaces/evm/builders.ts +52 -0
  99. package/src/namespaces/evm/constants.ts +2 -0
  100. package/src/namespaces/evm/eip1193.ts +1415 -0
  101. package/src/namespaces/evm/hooks.ts +43 -0
  102. package/src/namespaces/evm/mod.ts +10 -0
  103. package/src/namespaces/evm/types.ts +36 -0
  104. package/src/namespaces/evm/utils.ts +106 -0
  105. package/src/namespaces/solana/actions.ts +44 -0
  106. package/src/namespaces/solana/after.ts +3 -0
  107. package/src/namespaces/solana/and.ts +5 -0
  108. package/src/namespaces/solana/before.ts +3 -0
  109. package/src/namespaces/solana/builders.ts +40 -0
  110. package/src/namespaces/solana/constants.ts +2 -0
  111. package/src/namespaces/solana/hooks.ts +10 -0
  112. package/src/namespaces/solana/mod.ts +10 -0
  113. package/src/namespaces/solana/types.ts +25 -0
  114. package/src/namespaces/solana/utils.ts +36 -0
  115. package/src/namespaces/starknet/actions.ts +19 -0
  116. package/src/namespaces/starknet/after.ts +3 -0
  117. package/src/namespaces/starknet/and.ts +5 -0
  118. package/src/namespaces/starknet/before.ts +3 -0
  119. package/src/namespaces/starknet/builders.ts +18 -0
  120. package/src/namespaces/starknet/constants.ts +8 -0
  121. package/src/namespaces/starknet/mod.ts +9 -0
  122. package/src/namespaces/starknet/types.ts +18 -0
  123. package/src/namespaces/starknet/utils.ts +35 -0
  124. package/src/namespaces/sui/actions.ts +27 -0
  125. package/src/namespaces/sui/builders.ts +77 -0
  126. package/src/namespaces/sui/constants.ts +8 -0
  127. package/src/namespaces/sui/hooks.ts +10 -0
  128. package/src/namespaces/sui/mod.ts +8 -0
  129. package/src/namespaces/sui/types.ts +25 -0
  130. package/src/namespaces/sui/utils.ts +56 -0
  131. package/src/namespaces/tron/actions.ts +3 -0
  132. package/src/namespaces/tron/after.ts +3 -0
  133. package/src/namespaces/tron/and.ts +5 -0
  134. package/src/namespaces/tron/before.ts +3 -0
  135. package/src/namespaces/tron/builders.ts +15 -0
  136. package/src/namespaces/tron/constants.ts +8 -0
  137. package/src/namespaces/tron/mod.ts +8 -0
  138. package/src/namespaces/tron/types.ts +14 -0
  139. package/src/namespaces/tron/utils.ts +18 -0
  140. package/src/namespaces/utxo/actions.ts +3 -0
  141. package/src/namespaces/utxo/after.ts +3 -0
  142. package/src/namespaces/utxo/and.ts +5 -0
  143. package/src/namespaces/utxo/before.ts +3 -0
  144. package/src/namespaces/utxo/builders.ts +12 -0
  145. package/src/namespaces/utxo/constants.ts +2 -0
  146. package/src/namespaces/utxo/mod.ts +8 -0
  147. package/src/namespaces/utxo/types.ts +14 -0
  148. package/src/namespaces/utxo/utils.ts +18 -0
  149. package/src/namespaces/xrpl/builders.ts +14 -0
  150. package/src/namespaces/xrpl/constants.ts +7 -0
  151. package/src/namespaces/xrpl/mod.ts +5 -0
  152. package/src/namespaces/xrpl/types.ts +16 -0
  153. package/src/namespaces/xrpl/utils.ts +15 -0
  154. package/src/test-utils/fixtures.ts +13 -0
  155. package/src/types/accounts.ts +12 -0
  156. package/src/types/actions.ts +13 -0
  157. package/src/types/mod.ts +1 -0
  158. package/src/types/utils.ts +7 -0
  159. package/src/utils/mod.ts +8 -0
  160. package/src/utils/versions.test.ts +24 -0
  161. package/src/utils/versions.ts +64 -0
  162. package/store/package.json +8 -0
package/src/hub/hub.ts ADDED
@@ -0,0 +1,128 @@
1
+ import type { Namespace, State as NamespaceState } from './namespaces/mod.js';
2
+ import type { Provider, State as ProviderState } from './provider/mod.js';
3
+ import type { Store } from './store/mod.js';
4
+
5
+ type HubState = {
6
+ [key in string]: ProviderState & {
7
+ namespaces: NamespaceState[];
8
+ };
9
+ };
10
+
11
+ type RunAllResult = {
12
+ id: string;
13
+ provider: unknown;
14
+ namespaces: unknown[];
15
+ };
16
+
17
+ interface HubOptions {
18
+ store?: Store;
19
+ }
20
+ export class Hub {
21
+ #providers = new Map<string, Provider>();
22
+ #options: HubOptions;
23
+
24
+ constructor(options?: HubOptions) {
25
+ this.#options = options ?? {};
26
+ }
27
+
28
+ init() {
29
+ this.runAll('init');
30
+ }
31
+
32
+ /*
33
+ * Running a specific action (e.g. init) on all namespaces and providers one by one.
34
+ *
35
+ * TODO: Some of methods may accepts args, with this implementation we only limit to those one without any argument.
36
+ */
37
+ runAll(action: string): RunAllResult[] {
38
+ const output: RunAllResult[] = [];
39
+
40
+ // run action on all providers eagerConnect, disconnect
41
+ const providers = this.#providers.values();
42
+
43
+ for (const provider of providers) {
44
+ // Calling `action` on `Provider` if exists.
45
+ const providerOutput: RunAllResult = {
46
+ id: provider.id,
47
+ provider: undefined,
48
+ namespaces: [],
49
+ };
50
+
51
+ const providerMethod = provider[action as keyof Provider];
52
+ if (typeof providerMethod === 'function') {
53
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
54
+ // @ts-ignore-next-line
55
+ providerOutput.provider = providerMethod.call(provider);
56
+ }
57
+
58
+ // Namespace instances can have their own `action` as well. we will call them as well.
59
+ const namespaces = provider.getAll().values();
60
+ for (const namespace of namespaces) {
61
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
62
+ // @ts-ignore-next-line
63
+ const namespaceMethod = namespace[action];
64
+ if (typeof namespaceMethod === 'function') {
65
+ const result = namespaceMethod();
66
+ providerOutput.namespaces.push(result);
67
+ }
68
+ }
69
+
70
+ output.push(providerOutput);
71
+ }
72
+
73
+ return output;
74
+ }
75
+
76
+ add(id: string, provider: Provider) {
77
+ if (this.#options.store) {
78
+ provider.store(this.#options.store);
79
+ }
80
+ this.#providers.set(id, provider);
81
+ return this;
82
+ }
83
+ remove(id: string) {
84
+ const providerToRemove = this.#providers.get(id);
85
+
86
+ if (!providerToRemove) {
87
+ throw new Error(`Provider not found: No provider exists with ID "${id}"`);
88
+ }
89
+
90
+ this.#options.store?.getState().providers.removeProvider(id);
91
+ this.#providers.delete(id);
92
+
93
+ return this;
94
+ }
95
+
96
+ get(providerId: string): Provider | undefined {
97
+ return this.#providers.get(providerId);
98
+ }
99
+
100
+ getAll() {
101
+ return this.#providers;
102
+ }
103
+
104
+ state(): HubState {
105
+ const output = this.runAll('state');
106
+ const res: HubState = {};
107
+
108
+ output.forEach((result) => {
109
+ const namespaces: NamespaceState[] = [];
110
+ result.namespaces.forEach((b) => {
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ const [getNamespaceState] = b as ReturnType<Namespace<any>['state']>;
113
+
114
+ namespaces.push(getNamespaceState());
115
+ });
116
+
117
+ const [getProviderState] = result.provider as ReturnType<
118
+ Provider['state']
119
+ >;
120
+
121
+ res[result.id] = {
122
+ ...(getProviderState() || {}),
123
+ namespaces: namespaces,
124
+ };
125
+ });
126
+ return res;
127
+ }
128
+ }
package/src/hub/mod.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type {
2
+ Subscriber,
3
+ SubscriberCleanUp,
4
+ Context,
5
+ } from './namespaces/mod.js';
6
+ export { Namespace } from './namespaces/mod.js';
7
+
8
+ export { Provider } from './provider/mod.js';
9
+ export type { CommonNamespaces, CommonNamespaceKeys } from './provider/mod.js';
10
+
11
+ export { Hub } from './hub.js';
12
+ export type { Store, State, ProviderMetadata } from './store/mod.js';
13
+ export {
14
+ createStore,
15
+ guessProviderStateSelector,
16
+ namespaceStateSelector,
17
+ } from './store/mod.js';
18
+ export { generateStoreId } from './helpers.js';
@@ -0,0 +1,11 @@
1
+ export const ACTION_NOT_FOUND_ERROR = (name: string) =>
2
+ `Couldn't find "${name}" action. Are you sure you've added the action?`;
3
+
4
+ export const OR_ELSE_ACTION_FAILED_ERROR = (name: string) =>
5
+ `An error occurred during running ${name}`;
6
+
7
+ export const BEFORE_ACTION_FAILED_ERROR = (name: string) =>
8
+ `An error occurred during running before for "${name}" action`;
9
+
10
+ export const NO_STORE_FOUND_ERROR =
11
+ 'For setup store, you should set `store` first.';
@@ -0,0 +1,10 @@
1
+ export type {
2
+ Subscriber,
3
+ SubscriberCleanUp,
4
+ State,
5
+ RegisteredActions as ActionsMap,
6
+ Context,
7
+ Actions,
8
+ } from './types.js';
9
+
10
+ export { Namespace } from './namespace.js';
@@ -0,0 +1,443 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+
3
+ import { createStore } from '../mod.js';
4
+
5
+ import { OR_ELSE_ACTION_FAILED_ERROR } from './errors.js';
6
+ import { Namespace } from './namespace.js';
7
+
8
+ interface CustomMatchers<R = unknown> {
9
+ toThrowErrorWithCause: (expectedError: unknown, expectedCause: unknown) => R;
10
+ }
11
+
12
+ declare module 'vitest' {
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
14
+ interface Assertion<T = any> extends CustomMatchers<T> {}
15
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
16
+ interface AsymmetricMatchersContaining extends CustomMatchers {}
17
+ }
18
+
19
+ interface TestNamespaceActions {
20
+ connect: () => void;
21
+ disconnect: () => void;
22
+ }
23
+
24
+ expect.extend({
25
+ toThrowErrorWithCause(actualFunction, expectedError, expectedCause) {
26
+ try {
27
+ actualFunction();
28
+ } catch (error) {
29
+ const isExactError = error === expectedError;
30
+ const hasExpectedCause =
31
+ error instanceof Error && error.cause === expectedCause;
32
+
33
+ if (isExactError) {
34
+ return {
35
+ pass: true,
36
+ message: () => `Expected function to throw the exact error.`,
37
+ };
38
+ }
39
+
40
+ if (hasExpectedCause) {
41
+ return {
42
+ pass: true,
43
+ message: () =>
44
+ `Expected function to throw an error with the exact cause.`,
45
+ };
46
+ }
47
+
48
+ return {
49
+ pass: false,
50
+ message: () =>
51
+ `Expected function to throw the exact error with the expected cause, but got a different error.`,
52
+ };
53
+ }
54
+
55
+ return {
56
+ pass: false,
57
+ message: () =>
58
+ `Expected function to throw an Error, but it did not throw.`,
59
+ };
60
+ },
61
+ });
62
+
63
+ describe('check initializing Namespace', () => {
64
+ const NAMESPACE = 'evm';
65
+ const PROVIDER_ID = 'garbage provider';
66
+
67
+ test('initialize a namespace and run an action', () => {
68
+ const connect = vi.fn();
69
+ const disconnect = vi.fn();
70
+ const actions = new Map();
71
+ actions.set('connect', connect);
72
+ actions.set('disconnect', disconnect);
73
+
74
+ const ns = new Namespace<TestNamespaceActions>(NAMESPACE, PROVIDER_ID, {
75
+ actions: actions,
76
+ });
77
+
78
+ ns.run('connect');
79
+
80
+ expect(disconnect).toBeCalledTimes(0);
81
+ expect(connect).toBeCalledTimes(1);
82
+ });
83
+
84
+ test('init action should be called once', () => {
85
+ const connect = vi.fn();
86
+ const disconnect = vi.fn();
87
+ const init = vi.fn();
88
+ const actions = new Map();
89
+ actions.set('connect', connect);
90
+ actions.set('disconnect', disconnect);
91
+ actions.set('init', init);
92
+
93
+ const ns = new Namespace<TestNamespaceActions>(NAMESPACE, PROVIDER_ID, {
94
+ actions: actions,
95
+ });
96
+
97
+ expect(init).toBeCalledTimes(0);
98
+
99
+ ns.run('connect');
100
+ ns.init();
101
+ ns.init();
102
+
103
+ expect(disconnect).toBeCalledTimes(0);
104
+ expect(connect).toBeCalledTimes(1);
105
+ expect(init).toBeCalledTimes(1);
106
+ });
107
+
108
+ test('state should be updated and actions have access to them', () => {
109
+ const connect = vi.fn((context) => {
110
+ const [, setState] = context.state();
111
+ setState('connected', true);
112
+ });
113
+ const init = vi.fn((context) => {
114
+ const [, setState] = context.state();
115
+ setState('connecting', true);
116
+ });
117
+ const actions = new Map();
118
+ actions.set('connect', connect);
119
+ actions.set('init', init);
120
+
121
+ const store = createStore();
122
+ const ns = new Namespace<TestNamespaceActions>(NAMESPACE, PROVIDER_ID, {
123
+ actions: actions,
124
+ store,
125
+ });
126
+
127
+ ns.run('connect');
128
+ ns.init();
129
+ ns.init();
130
+
131
+ const [currentState] = ns.state();
132
+ expect(currentState().connected).toBe(true);
133
+ expect(currentState('connecting')).toBe(true);
134
+ });
135
+
136
+ test('has method returns true when namespace contains the specified action', () => {
137
+ const connectAction = vi.fn((context) => {
138
+ const [, setState] = context.state();
139
+ setState('connected', true);
140
+ });
141
+
142
+ const actionsMap = new Map([['connect', connectAction]]);
143
+
144
+ const store = createStore();
145
+ const namespace = new Namespace<TestNamespaceActions>(
146
+ NAMESPACE,
147
+ PROVIDER_ID,
148
+ {
149
+ actions: actionsMap,
150
+ store,
151
+ },
152
+ );
153
+
154
+ expect(namespace.has('connect')).toBe(true);
155
+ expect(namespace.has('disconnect')).toBe(false);
156
+ });
157
+ test("throw an error if store doesn't set", () => {
158
+ const ns = new Namespace<TestNamespaceActions>(NAMESPACE, PROVIDER_ID, {
159
+ actions: new Map(),
160
+ });
161
+
162
+ expect(() => ns.state()).toThrowError();
163
+ });
164
+ });
165
+
166
+ describe('check actions with hooks and operators', () => {
167
+ const NAMESPACE = 'bip122';
168
+ const PROVIDER_ID = 'garbage provider';
169
+
170
+ test('add actions and run them.', () => {
171
+ const actions = new Map();
172
+ actions.set('hello', () => 'hello world');
173
+ actions.set('bye', () => 'bye bye');
174
+ actions.set('chainable', () => "it's also chainable");
175
+ actions.set('chain2', () => "it's also chainable");
176
+
177
+ const ns = new Namespace<{
178
+ hello: () => string;
179
+ bye: () => string;
180
+ chainable: () => void;
181
+ chain2: () => void;
182
+ }>(NAMESPACE, PROVIDER_ID, { actions });
183
+
184
+ expect(ns.run('hello')).toBe('hello world');
185
+ expect(ns.run('bye')).toBe('bye bye');
186
+
187
+ expect(() => {
188
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
189
+ // @ts-ignore-next-line
190
+ return ns.run('some_action_name_that_has_not_added');
191
+ }).toThrowError();
192
+ });
193
+
194
+ test('should be called before/after target action correctly even with multiple hook assigned to an action name', () => {
195
+ const beforeAction = vi.fn();
196
+ const anotherBeforeAction = vi.fn();
197
+ const afterAction = vi.fn();
198
+ const anotherAfterAction = vi.fn();
199
+ const connectAction = vi.fn();
200
+ const disconnectAction = vi.fn();
201
+
202
+ const actions = new Map();
203
+ actions.set('connect', connectAction);
204
+ actions.set('disconnect', disconnectAction);
205
+
206
+ const ns = new Namespace<TestNamespaceActions>(NAMESPACE, PROVIDER_ID, {
207
+ actions,
208
+ });
209
+
210
+ ns.run('connect');
211
+ expect(connectAction).toBeCalledTimes(1);
212
+ expect(beforeAction).toBeCalledTimes(0);
213
+ expect(anotherBeforeAction).toBeCalledTimes(0);
214
+ expect(afterAction).toBeCalledTimes(0);
215
+ expect(anotherAfterAction).toBeCalledTimes(0);
216
+
217
+ ns.before('connect', beforeAction);
218
+ ns.before('connect', anotherBeforeAction);
219
+ ns.run('connect');
220
+ expect(connectAction).toBeCalledTimes(2);
221
+ expect(beforeAction).toBeCalledTimes(1);
222
+ expect(anotherBeforeAction).toBeCalledTimes(1);
223
+ expect(afterAction).toBeCalledTimes(0);
224
+ expect(anotherAfterAction).toBeCalledTimes(0);
225
+
226
+ ns.after('connect', afterAction);
227
+ ns.after('connect', anotherAfterAction);
228
+ ns.run('connect');
229
+ expect(beforeAction).toBeCalledTimes(2);
230
+ expect(anotherBeforeAction).toBeCalledTimes(2);
231
+ expect(afterAction).toBeCalledTimes(1);
232
+ expect(anotherAfterAction).toBeCalledTimes(1);
233
+ });
234
+
235
+ test('should call `and_then` sequentially.', () => {
236
+ const andActionFirst = vi.fn((_ctx, result) => result + 1);
237
+ const andActionSecond = vi.fn((_ctx, result) => result + 1);
238
+
239
+ const connectAction = vi.fn(() => 0);
240
+ const disconnectAction = vi.fn();
241
+
242
+ const actions = new Map();
243
+ actions.set('connect', connectAction);
244
+ actions.set('disconnect', disconnectAction);
245
+
246
+ const ns = new Namespace<TestNamespaceActions>(NAMESPACE, PROVIDER_ID, {
247
+ actions: actions,
248
+ });
249
+ ns.and_then('connect', andActionFirst);
250
+ ns.and_then('connect', andActionSecond);
251
+
252
+ const result = ns.run('connect');
253
+
254
+ expect(connectAction).toBeCalledTimes(1);
255
+ expect(andActionFirst).toBeCalledTimes(1);
256
+ expect(andActionSecond).toBeCalledTimes(1);
257
+
258
+ expect(result).toBe(2);
259
+
260
+ ns.run('connect');
261
+ expect(connectAction).toBeCalledTimes(2);
262
+ expect(andActionFirst).toBeCalledTimes(2);
263
+ expect(andActionSecond).toBeCalledTimes(2);
264
+ });
265
+
266
+ test("shouldn't run other `and_then` hooks if one of them fails then fallback to `or_else`.", () => {
267
+ const andActionFirst = vi.fn((_ctx, _result) => {
268
+ throw new Error('Oops!');
269
+ });
270
+ const andActionSecond = vi.fn((_ctx, result) => result + 1);
271
+
272
+ const orAction = vi.fn((_ctx, e) => e instanceof Error);
273
+
274
+ const connectAction = vi.fn(() => 0);
275
+ const actions = new Map();
276
+ actions.set('connect', connectAction);
277
+
278
+ const ns = new Namespace<{
279
+ connect: () => void;
280
+ }>(NAMESPACE, PROVIDER_ID, {
281
+ actions: actions,
282
+ configs: {},
283
+ });
284
+ ns.and_then('connect', andActionFirst);
285
+ ns.and_then('connect', andActionSecond);
286
+
287
+ ns.or_else('connect', orAction);
288
+
289
+ const result = ns.run('connect');
290
+
291
+ expect(connectAction).toBeCalledTimes(1);
292
+ expect(andActionFirst).toBeCalledTimes(1);
293
+ expect(andActionSecond).toBeCalledTimes(0);
294
+ expect(orAction).toBeCalledTimes(1);
295
+ expect(result).toBe(true);
296
+ });
297
+
298
+ test('should throw error if there are no `or` to handle error', () => {
299
+ const andActionFirst = vi.fn((_ctx, _result) => {
300
+ throw new Error('Oops!');
301
+ });
302
+ const andActionSecond = vi.fn((_ctx, result) => result + 1);
303
+
304
+ const connectAction = vi.fn(() => 0);
305
+ const actions = new Map();
306
+ actions.set('connect', connectAction);
307
+
308
+ const ns = new Namespace<{
309
+ connect: () => void;
310
+ }>(NAMESPACE, PROVIDER_ID, {
311
+ actions: actions,
312
+ configs: {},
313
+ });
314
+
315
+ ns.and_then('connect', andActionFirst);
316
+ ns.and_then('connect', andActionSecond);
317
+
318
+ expect(() => ns.run('connect')).toThrowError();
319
+ });
320
+
321
+ test('ensure `or_else` has access to error', () => {
322
+ const actions = new Map();
323
+ actions.set('connect', () => {
324
+ throw new Error('Oops!');
325
+ });
326
+
327
+ const ns = new Namespace<{
328
+ connect: () => void;
329
+ }>(NAMESPACE, PROVIDER_ID, {
330
+ actions: actions,
331
+ configs: {},
332
+ });
333
+
334
+ ns.or_else('connect', (_ctx: unknown, err: unknown) => {
335
+ return err instanceof Error;
336
+ });
337
+
338
+ const result = ns.run('connect');
339
+ expect(result).toBe(true);
340
+ });
341
+
342
+ test('should call `or_else` sequentially.', () => {
343
+ const orActionFirst = vi.fn((_ctx, _err) => 1);
344
+ const orActionSecond = vi.fn((_ctx, _err) => _err + 1);
345
+
346
+ const connectAction = vi.fn(() => {
347
+ throw new Error('Oops!');
348
+ });
349
+
350
+ const actions = new Map();
351
+ actions.set('connect', connectAction);
352
+
353
+ const ns = new Namespace<{
354
+ connect: () => void;
355
+ }>(NAMESPACE, PROVIDER_ID, {
356
+ actions: actions,
357
+ });
358
+
359
+ ns.or_else('connect', orActionFirst);
360
+ ns.or_else('connect', orActionSecond);
361
+
362
+ const result = ns.run('connect');
363
+
364
+ expect(connectAction).toBeCalledTimes(1);
365
+ expect(orActionFirst).toBeCalledTimes(1);
366
+ expect(orActionSecond).toBeCalledTimes(1);
367
+
368
+ expect(result).toBe(2);
369
+
370
+ ns.run('connect');
371
+ expect(connectAction).toBeCalledTimes(2);
372
+ expect(orActionFirst).toBeCalledTimes(2);
373
+ expect(orActionSecond).toBeCalledTimes(2);
374
+ });
375
+ test('throw a wrapped error if `or_else` handler fails to run.', () => {
376
+ const orActionFirst = vi.fn((_ctx, _err) => 1);
377
+ const orActionSecondError = 'This is actually a bad situation';
378
+ const orActionSecond = vi.fn((_ctx, _err) => {
379
+ // eslint-disable-next-line @typescript-eslint/only-throw-error
380
+ throw orActionSecondError;
381
+ });
382
+ const connectActionError = new Error('Oops!');
383
+
384
+ const connectAction = vi.fn(() => {
385
+ throw connectActionError;
386
+ });
387
+
388
+ const actions = new Map();
389
+ actions.set('connect', connectAction);
390
+
391
+ const ns = new Namespace<{
392
+ connect: () => void;
393
+ }>(NAMESPACE, PROVIDER_ID, {
394
+ actions: actions,
395
+ });
396
+
397
+ ns.or_else('connect', orActionFirst);
398
+ ns.or_else('connect', orActionSecond);
399
+
400
+ expect(() => ns.run('connect')).toThrowError(
401
+ expect.objectContaining({
402
+ message: OR_ELSE_ACTION_FAILED_ERROR(
403
+ `connect for ${ns.namespaceId} namespace.`,
404
+ ),
405
+ cause: expect.objectContaining({
406
+ message: orActionSecondError,
407
+ cause: connectActionError,
408
+ }),
409
+ }),
410
+ );
411
+
412
+ expect(orActionFirst).toBeCalledTimes(1);
413
+ expect(connectAction).toBeCalledTimes(1);
414
+ expect(orActionSecond).toBeCalledTimes(1);
415
+ });
416
+
417
+ test('throw the exact error if `or_else` handler fails and the error is an instance of Error.', () => {
418
+ const orActionError = new Error('Or action error');
419
+ const orAction = vi.fn((_ctx, _err) => {
420
+ throw orActionError;
421
+ });
422
+ const connectActionError = new Error('Oops!');
423
+ const connectAction = vi.fn(() => {
424
+ throw connectActionError;
425
+ });
426
+ const actions = new Map();
427
+ actions.set('connect', connectAction);
428
+
429
+ const ns = new Namespace<{
430
+ connect: () => void;
431
+ }>(NAMESPACE, PROVIDER_ID, {
432
+ actions: actions,
433
+ });
434
+ ns.or_else('connect', orAction);
435
+
436
+ expect(() => {
437
+ ns.run('connect');
438
+ }).toThrowErrorWithCause(orActionError, connectActionError);
439
+
440
+ expect(orAction).toBeCalledTimes(1);
441
+ expect(connectAction).toBeCalledTimes(1);
442
+ });
443
+ });