@hub3js/core 0.0.0-experimental-03bf1f89-20260602

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 (105) hide show
  1. package/CHANGELOG.md +381 -0
  2. package/dist/builders/action.d.ts +22 -0
  3. package/dist/builders/action.d.ts.map +1 -0
  4. package/dist/builders/mod.d.ts +5 -0
  5. package/dist/builders/mod.d.ts.map +1 -0
  6. package/dist/builders/namespace.d.ts +47 -0
  7. package/dist/builders/namespace.d.ts.map +1 -0
  8. package/dist/builders/provider.d.ts +37 -0
  9. package/dist/builders/provider.d.ts.map +1 -0
  10. package/dist/builders/types.d.ts +23 -0
  11. package/dist/builders/types.d.ts.map +1 -0
  12. package/dist/hub/helpers.d.ts +6 -0
  13. package/dist/hub/helpers.d.ts.map +1 -0
  14. package/dist/hub/hub.d.ts +44 -0
  15. package/dist/hub/hub.d.ts.map +1 -0
  16. package/dist/hub/mod.d.ts +11 -0
  17. package/dist/hub/mod.d.ts.map +1 -0
  18. package/dist/hub/namespaces/errors.d.ts +5 -0
  19. package/dist/hub/namespaces/errors.d.ts.map +1 -0
  20. package/dist/hub/namespaces/mod.d.ts +3 -0
  21. package/dist/hub/namespaces/mod.d.ts.map +1 -0
  22. package/dist/hub/namespaces/namespace.d.ts +148 -0
  23. package/dist/hub/namespaces/namespace.d.ts.map +1 -0
  24. package/dist/hub/namespaces/types.d.ts +36 -0
  25. package/dist/hub/namespaces/types.d.ts.map +1 -0
  26. package/dist/hub/provider/mod.d.ts +3 -0
  27. package/dist/hub/provider/mod.d.ts.map +1 -0
  28. package/dist/hub/provider/provider.d.ts +102 -0
  29. package/dist/hub/provider/provider.d.ts.map +1 -0
  30. package/dist/hub/provider/types.d.ts +28 -0
  31. package/dist/hub/provider/types.d.ts.map +1 -0
  32. package/dist/hub/store/events.d.ts +54 -0
  33. package/dist/hub/store/events.d.ts.map +1 -0
  34. package/dist/hub/store/extend.d.ts +14 -0
  35. package/dist/hub/store/extend.d.ts.map +1 -0
  36. package/dist/hub/store/hub.d.ts +11 -0
  37. package/dist/hub/store/hub.d.ts.map +1 -0
  38. package/dist/hub/store/mod.d.ts +8 -0
  39. package/dist/hub/store/mod.d.ts.map +1 -0
  40. package/dist/hub/store/mod.js +2 -0
  41. package/dist/hub/store/mod.js.map +7 -0
  42. package/dist/hub/store/namespaces.d.ts +38 -0
  43. package/dist/hub/store/namespaces.d.ts.map +1 -0
  44. package/dist/hub/store/providers.d.ts +84 -0
  45. package/dist/hub/store/providers.d.ts.map +1 -0
  46. package/dist/hub/store/selectors.d.ts +18 -0
  47. package/dist/hub/store/selectors.d.ts.map +1 -0
  48. package/dist/hub/store/store.d.ts +14 -0
  49. package/dist/hub/store/store.d.ts.map +1 -0
  50. package/dist/hub/types.d.ts +5 -0
  51. package/dist/hub/types.d.ts.map +1 -0
  52. package/dist/mod.d.ts +5 -0
  53. package/dist/mod.d.ts.map +1 -0
  54. package/dist/mod.js +2 -0
  55. package/dist/mod.js.map +7 -0
  56. package/dist/test-utils/fixtures.d.ts +4 -0
  57. package/dist/test-utils/fixtures.d.ts.map +1 -0
  58. package/dist/test-utils/mod.d.ts +2 -0
  59. package/dist/test-utils/mod.d.ts.map +1 -0
  60. package/dist/test-utils/mod.js +2 -0
  61. package/dist/test-utils/mod.js.map +7 -0
  62. package/dist/utils/mod.d.ts +3 -0
  63. package/dist/utils/mod.d.ts.map +1 -0
  64. package/dist/utils/mod.js +2 -0
  65. package/dist/utils/mod.js.map +7 -0
  66. package/dist/utils/versions.d.ts +50 -0
  67. package/dist/utils/versions.d.ts.map +1 -0
  68. package/package.json +49 -0
  69. package/readme.md +4 -0
  70. package/src/builders/action.test.ts +225 -0
  71. package/src/builders/action.ts +86 -0
  72. package/src/builders/mod.ts +5 -0
  73. package/src/builders/namespace.ts +258 -0
  74. package/src/builders/provider.ts +101 -0
  75. package/src/builders/types.ts +28 -0
  76. package/src/hub/helpers.ts +11 -0
  77. package/src/hub/hub.test.ts +108 -0
  78. package/src/hub/hub.ts +145 -0
  79. package/src/hub/mod.ts +21 -0
  80. package/src/hub/namespaces/errors.ts +11 -0
  81. package/src/hub/namespaces/mod.ts +10 -0
  82. package/src/hub/namespaces/namespace.test.ts +443 -0
  83. package/src/hub/namespaces/namespace.ts +470 -0
  84. package/src/hub/namespaces/types.ts +58 -0
  85. package/src/hub/provider/mod.ts +9 -0
  86. package/src/hub/provider/provider.test.ts +277 -0
  87. package/src/hub/provider/provider.ts +286 -0
  88. package/src/hub/provider/types.ts +37 -0
  89. package/src/hub/store/events.ts +89 -0
  90. package/src/hub/store/extend.ts +125 -0
  91. package/src/hub/store/hub.ts +18 -0
  92. package/src/hub/store/mod.ts +26 -0
  93. package/src/hub/store/namespaces.ts +177 -0
  94. package/src/hub/store/providers.ts +183 -0
  95. package/src/hub/store/selectors.ts +59 -0
  96. package/src/hub/store/store.test.ts +50 -0
  97. package/src/hub/store/store.ts +30 -0
  98. package/src/hub/types.ts +13 -0
  99. package/src/mod.ts +27 -0
  100. package/src/test-utils/fixtures.ts +13 -0
  101. package/src/test-utils/mod.ts +1 -0
  102. package/src/utils/mod.ts +2 -0
  103. package/src/utils/versions.test.ts +23 -0
  104. package/src/utils/versions.ts +91 -0
  105. package/store/package.json +8 -0
@@ -0,0 +1,277 @@
1
+ import type { Store } from '../store/mod.js';
2
+
3
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
4
+
5
+ import { NamespaceBuilder } from '../../builders/namespace.js';
6
+ import { ProviderBuilder } from '../../builders/provider.js';
7
+ import {
8
+ garbageWalletInfo,
9
+ garbageWalletMetaData,
10
+ } from '../../test-utils/fixtures.js';
11
+ import { createStore } from '../store/mod.js';
12
+
13
+ import { Provider } from './provider.js';
14
+
15
+ interface TestEvmActions {
16
+ connect: (chain: string) => Promise<{ accounts: string[]; network: string }>;
17
+ }
18
+
19
+ interface TestSolanaActions {
20
+ connect: () => Promise<string[]>;
21
+ }
22
+
23
+ type TestNamespaces = {
24
+ evm: TestEvmActions;
25
+ solana: TestSolanaActions;
26
+ };
27
+
28
+ describe('check providers', () => {
29
+ let namespaces: {
30
+ evm: NamespaceBuilder<TestEvmActions>;
31
+ solana: NamespaceBuilder<TestSolanaActions>;
32
+ };
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ let namespacesMap: Map<any, any>;
35
+ let store: Store;
36
+
37
+ beforeEach(() => {
38
+ store = createStore();
39
+ const evmNamespace = new NamespaceBuilder<TestEvmActions>(
40
+ 'eip155',
41
+ 'garbage',
42
+ );
43
+ const solanaNamespace = new NamespaceBuilder<TestSolanaActions>(
44
+ 'solana',
45
+ 'garbage',
46
+ );
47
+
48
+ namespaces = {
49
+ evm: evmNamespace,
50
+ solana: solanaNamespace,
51
+ };
52
+
53
+ namespacesMap = new Map();
54
+ namespacesMap.set('evm', evmNamespace.build());
55
+ namespacesMap.set('solana', solanaNamespace.build());
56
+
57
+ return () => {
58
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
59
+ // @ts-ignore-next-line
60
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
61
+ ((store = undefined), (namespaces = undefined));
62
+ };
63
+ });
64
+
65
+ test('Initialize providers correctly', () => {
66
+ const provider = new Provider<TestNamespaces>(
67
+ 'garbage',
68
+ namespacesMap,
69
+ garbageWalletInfo,
70
+ );
71
+
72
+ const allNamespaces = provider.getAll();
73
+
74
+ expect(allNamespaces.size).toBe(2);
75
+ });
76
+
77
+ test('throw error if state() is called before store initialization', () => {
78
+ const provider = new Provider<TestNamespaces>(
79
+ 'garbage',
80
+ namespacesMap,
81
+ garbageWalletInfo,
82
+ );
83
+
84
+ expect(() => provider.state()).toThrowError();
85
+ });
86
+
87
+ test('should return wallet info via info() without store initialization', () => {
88
+ const provider = new Provider<TestNamespaces>(
89
+ 'garbage',
90
+ namespacesMap,
91
+ garbageWalletInfo,
92
+ );
93
+
94
+ expect(provider.info()).toBe(garbageWalletInfo);
95
+ });
96
+
97
+ test('should return wallet info via info() after store initialization', () => {
98
+ const store = createStore();
99
+ const provider = new Provider<TestNamespaces>(
100
+ 'garbage',
101
+ namespacesMap,
102
+ garbageWalletInfo,
103
+ {
104
+ store,
105
+ },
106
+ );
107
+
108
+ expect(provider.info()).toMatchObject(
109
+ store.getState().providers.list['garbage'].config,
110
+ );
111
+ });
112
+
113
+ test('access state correctly', () => {
114
+ const provider = new Provider<TestNamespaces>(
115
+ 'garbage',
116
+ namespacesMap,
117
+ garbageWalletInfo,
118
+ {
119
+ store: createStore(),
120
+ },
121
+ );
122
+
123
+ const [getState, setState] = provider.state();
124
+
125
+ expect(getState().connected).toBe(false);
126
+ expect(getState('connected')).toBe(false);
127
+ expect(() => {
128
+ // @ts-expect-error intentionally using an state that doesn't exist.
129
+ getState('not_exist_state');
130
+ }).toThrowError();
131
+ expect(() => {
132
+ // @ts-expect-error intentionally using an state that doesn't exist and try to update.
133
+ setState('another_not_exist_state');
134
+ }).toThrowError();
135
+ });
136
+ test('update state properly', () => {
137
+ const store = createStore();
138
+ const provider = new Provider<TestNamespaces>(
139
+ 'garbage',
140
+ namespacesMap,
141
+ garbageWalletInfo,
142
+ {
143
+ store,
144
+ },
145
+ );
146
+
147
+ const [getState, setState] = provider.state();
148
+
149
+ provider.store(store);
150
+ setState('installed', true);
151
+ const isInstalled = getState('installed');
152
+ expect(isInstalled).toBe(true);
153
+ });
154
+
155
+ test('run namespace actions from provider', async () => {
156
+ const { evm, solana } = namespaces;
157
+ solana.action('connect', async () => [
158
+ 'solana:mainnet:0x000000000000000000000000000000000000dead',
159
+ ]);
160
+ const testNamespaces = new Map();
161
+ testNamespaces.set('evm', evm.build());
162
+ testNamespaces.set('solana', solana.build());
163
+
164
+ const provider = new Provider<TestNamespaces>(
165
+ 'garbage',
166
+ testNamespaces,
167
+ garbageWalletInfo,
168
+ );
169
+
170
+ const result = await provider.get('solana')?.connect();
171
+
172
+ expect(result).toStrictEqual([
173
+ 'solana:mainnet:0x000000000000000000000000000000000000dead',
174
+ ]);
175
+ // Since we didn't add any action regarding connect for `evm`
176
+ await expect(async () =>
177
+ provider.get('evm')?.connect('0x1'),
178
+ ).rejects.toThrowError();
179
+ });
180
+
181
+ test('sets config properly', () => {
182
+ const builder = new ProviderBuilder('garbage');
183
+ builder.config('metadata', garbageWalletMetaData);
184
+ const provider = builder.build().store(store);
185
+
186
+ expect(provider.info()?.metadata).toStrictEqual(garbageWalletMetaData);
187
+ });
188
+
189
+ test('.init should works on Provider', () => {
190
+ const builder = new ProviderBuilder('garbage').config(
191
+ 'metadata',
192
+ garbageWalletMetaData,
193
+ );
194
+ let count = 0;
195
+ builder.init(() => {
196
+ count++;
197
+ });
198
+ const provider = builder.build().store(store);
199
+ expect(count).toBe(0);
200
+ provider.init();
201
+ provider.init();
202
+ provider.init();
203
+ expect(count).toBe(1);
204
+ });
205
+
206
+ test(".init shouldn't do anything when use hasn't set anything", () => {
207
+ const builder = new ProviderBuilder('garbage').config(
208
+ 'metadata',
209
+ garbageWalletMetaData,
210
+ );
211
+ const provider = builder.build().store(store);
212
+ expect(() => {
213
+ provider.init();
214
+ provider.init();
215
+ provider.init();
216
+ }).not.toThrow();
217
+ });
218
+
219
+ test('A provider can be found using its namespace', () => {
220
+ const builder = new ProviderBuilder('garbage', { store }).config(
221
+ 'metadata',
222
+ garbageWalletMetaData,
223
+ );
224
+
225
+ const { evm, solana } = namespaces;
226
+ builder.add('evm', evm.build()).add('solana', solana.build());
227
+ const provider = builder.build();
228
+
229
+ const result = provider.findByNamespace('solana');
230
+ expect(result).toBeDefined();
231
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
232
+ // @ts-ignore-next-line
233
+ expect(result?.namespaceId).toBe('solana');
234
+
235
+ const result2 = provider.findByNamespace('evm');
236
+ expect(result2).toBeUndefined();
237
+ });
238
+
239
+ test('`before/after` is calling with correct context ', () => {
240
+ const connect = vi.fn();
241
+ const before = vi.fn(function (context) {
242
+ const [, setState] = context.state();
243
+ setState('installed', true);
244
+ });
245
+ const after = vi.fn(function (context) {
246
+ const [, setState] = context.state();
247
+ setState('installed', false);
248
+ });
249
+
250
+ const { evm } = namespaces;
251
+ const evmNamespace = evm.action('connect', connect).build();
252
+
253
+ const builder = new ProviderBuilder('garbage', { store })
254
+ .add('evm', evmNamespace)
255
+ .config('metadata', garbageWalletMetaData);
256
+ const provider = builder.build();
257
+
258
+ const [getState] = provider.state();
259
+ const result = provider.get('evm');
260
+
261
+ // Adding `after` then make it will run
262
+ provider.before('connect', before);
263
+ void result?.connect('whatever');
264
+
265
+ expect(connect).toBeCalledTimes(1);
266
+ expect(before).toBeCalledTimes(1);
267
+ expect(getState('installed')).toBe(true);
268
+
269
+ // Adding `after` then make it will run
270
+ provider.after('connect', after);
271
+ void result?.connect('whatever');
272
+ expect(connect).toBeCalledTimes(2);
273
+ expect(after).toBeCalledTimes(1);
274
+
275
+ expect(getState('installed')).toBe(false);
276
+ });
277
+ });
@@ -0,0 +1,286 @@
1
+ import type {
2
+ Context,
3
+ ExtendableInternalActions,
4
+ GetState,
5
+ RegisteredNamespaces,
6
+ SetState,
7
+ State,
8
+ } from './types.js';
9
+ import type { FindProxiedNamespace } from '../../builders/mod.js';
10
+ import type { ProviderConfig, ProviderInfo, Store } from '../store/mod.js';
11
+ import type { AnyFunction, FunctionWithContext } from '../types.js';
12
+
13
+ const VERSION = '1.0';
14
+
15
+ /**
16
+ * A `Provider` represents one wallet/integration and owns the set of namespaces
17
+ * that wallet exposes (e.g. `evm`, `solana`).
18
+ *
19
+ * The `TNS` generic captures exactly which namespaces this provider supports.
20
+ * It's normally inferred by `ProviderBuilder` from chained `.add()` calls, so
21
+ * `phantom.get('cosmos')` is a compile-time error if Phantom never registered
22
+ * `cosmos`. Construct directly only when you have a precise namespace shape
23
+ * to pass via `new Provider<MyNamespaces>(...)`.
24
+ */
25
+ export class Provider<TNS = unknown> {
26
+ public readonly id: string;
27
+ public readonly version = VERSION;
28
+
29
+ #namespaces: RegisteredNamespaces<TNS>;
30
+ #initiated = false;
31
+ #extendInternalActions: ExtendableInternalActions = {};
32
+ #store: Store | undefined;
33
+ #configs: ProviderConfig<TNS>;
34
+
35
+ constructor(
36
+ id: string,
37
+ namespaces: RegisteredNamespaces<TNS>,
38
+ configs: ProviderConfig<TNS>,
39
+ options?: {
40
+ /**
41
+ * There are some cases we need to have a behavior like initializing a provider which will be run when we are creating an instance.
42
+ * These internal steps and behaviors will be useful for library user to extend the behavior by running a specific code.
43
+ */
44
+ extendInternalActions?: ExtendableInternalActions;
45
+ store?: Store;
46
+ },
47
+ ) {
48
+ this.id = id;
49
+ this.#configs = configs;
50
+ // it should be only created here, to make sure `after/before` will work properly.
51
+ this.#extendInternalActions = options?.extendInternalActions || {};
52
+ this.#namespaces = namespaces;
53
+
54
+ /**
55
+ * Our assumption is that the store will be optional for the provider.
56
+ * If a store is provided in the options, it will be initialized and set up.
57
+ */
58
+ if (options?.store) {
59
+ this.#store = options.store;
60
+ this.#setupStore();
61
+ }
62
+ }
63
+
64
+ /**
65
+ * This is an special callback that will be called **only once**.
66
+ * We don't call this in `constructor` and developer should call this manually. we only ensure it will be called once.
67
+ *
68
+ * ```ts
69
+ * const myInit = () => { whatever; } *
70
+ * const provider = new Provider(..., {extendInternalActions: {init: myInit} });
71
+ *
72
+ * // Will run `myInit`
73
+ * provider.init()
74
+ *
75
+ * // Will not run `myInit` anymore.
76
+ * provider.init()
77
+ * provider.init()
78
+ * ```
79
+ */
80
+ public init(): void {
81
+ if (this.#initiated) {
82
+ return;
83
+ }
84
+
85
+ const definedInitByUser = this.#extendInternalActions.init;
86
+ if (definedInitByUser) {
87
+ definedInitByUser(this.#context());
88
+ }
89
+
90
+ this.#initiated = true;
91
+ }
92
+
93
+ /**
94
+ * Getting state of a provider.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const provider = new Provider(...);
99
+ * const [getState, setState] = provider.state();
100
+ *
101
+ * getState('installed');
102
+ * // or
103
+ * getState().installed;
104
+ * ```
105
+ */
106
+ public state(): [GetState, SetState] {
107
+ const store = this.#store;
108
+ if (!store) {
109
+ throw new Error(
110
+ `Any store detected for ${this.id}. You need to set your store using '.store' method first.`,
111
+ );
112
+ }
113
+
114
+ const setState: SetState = (name, value) => {
115
+ switch (name) {
116
+ case 'installed':
117
+ return store.getState().providers.updateStatus(this.id, name, value);
118
+ default:
119
+ throw new Error(
120
+ `Unhandled state update for provider. (provider id: ${this.id}, state name: ${name})`,
121
+ );
122
+ }
123
+ };
124
+
125
+ const getState: GetState = <K extends keyof State>(name?: K) => {
126
+ const state: State = store
127
+ .getState()
128
+ .providers.guessNamespacesState(this.id);
129
+
130
+ if (!name) {
131
+ return state;
132
+ }
133
+
134
+ switch (name) {
135
+ case 'installed':
136
+ case 'connected':
137
+ case 'connecting':
138
+ return state[name];
139
+ default:
140
+ throw new Error('Unhandled state for provider');
141
+ }
142
+ };
143
+
144
+ return [getState, setState];
145
+ }
146
+
147
+ /**
148
+ * For keeping state, we need a store. all the states will be write to/read from store.
149
+ *
150
+ * **Note: When you are setting an store for provider, it will be set for its namespaces automatically as well**
151
+ */
152
+ public store(store: Store): this {
153
+ if (this.#store) {
154
+ console.warn(
155
+ "You've already set an store for your Provider. Old store will be replaced by the new one.",
156
+ );
157
+ }
158
+ this.#store = store;
159
+ this.#setupStore();
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Getting information about a provider which has been set on constructing Provider.
165
+ */
166
+ public info(): ProviderInfo<TNS> | undefined {
167
+ const store = this.#store;
168
+ if (!store) {
169
+ return this.#configs;
170
+ }
171
+ const config = store.getState().providers.list[this.id].config;
172
+
173
+ return { metadata: config.metadata };
174
+ }
175
+
176
+ /**
177
+ * A list of registered _proxied_ namespaces.
178
+ */
179
+ public getAll(): RegisteredNamespaces<TNS> {
180
+ return this.#namespaces;
181
+ }
182
+
183
+ /**
184
+ * Get a registered namespace in provider by its **namespace key**.
185
+ *
186
+ * Note: difference between namespace key and namespace id is the first one
187
+ * is the key under which the namespace is registered (the `K` in `.add(K, ns)`),
188
+ * and the second one is the runtime `namespaceId` (e.g. `'eip155'`) chosen
189
+ * when constructing the namespace itself.
190
+ */
191
+ public get<K extends keyof TNS>(
192
+ id: K,
193
+ ): FindProxiedNamespace<K, TNS> | undefined {
194
+ return this.#namespaces.get(id) as FindProxiedNamespace<K, TNS> | undefined;
195
+ }
196
+
197
+ /**
198
+ * Get a registered namespace by its **namespaceId** (the runtime id chosen
199
+ * when constructing the namespace). Returns the union of all namespaces this
200
+ * provider supports — caller is expected to discriminate via `namespaceId`.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const provider = new Provider(...);
205
+ * provider.findByNamespace("whatever-id-i-set-for-namespace")
206
+ * ```
207
+ */
208
+ public findByNamespace(
209
+ namespaceLookingFor: string,
210
+ ): FindProxiedNamespace<keyof TNS, TNS> | undefined {
211
+ let result: FindProxiedNamespace<keyof TNS, TNS> | undefined = undefined;
212
+
213
+ this.#namespaces.forEach((namespace) => {
214
+ if (namespace.namespaceId === namespaceLookingFor) {
215
+ result = namespace;
216
+ }
217
+ });
218
+
219
+ return result;
220
+ }
221
+
222
+ /**
223
+ * Running a hook function _before_ a specific action for **all registered namespaces**.
224
+ */
225
+ public before(
226
+ actionName: string,
227
+ hookFn: FunctionWithContext<AnyFunction, Context>,
228
+ ): this {
229
+ this.#addHook('before', actionName, hookFn);
230
+ return this;
231
+ }
232
+
233
+ /**
234
+ * Running a hook function _after_ a specific action for **all registered namespaces**.
235
+ */
236
+ public after(
237
+ actionName: string,
238
+ hookFn: FunctionWithContext<AnyFunction, Context>,
239
+ ): this {
240
+ this.#addHook('after', actionName, hookFn);
241
+ return this;
242
+ }
243
+
244
+ #addHook(
245
+ hookName: 'after' | 'before',
246
+ actionName: string,
247
+ cb: FunctionWithContext<AnyFunction, Context>,
248
+ ): this {
249
+ const context = {
250
+ state: this.state.bind(this),
251
+ };
252
+
253
+ this.#namespaces.forEach((namespace) => {
254
+ if (hookName === 'after') {
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
+ namespace.after(actionName as any, cb, { context });
257
+ } else if (hookName === 'before') {
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ namespace.before(actionName as any, cb, { context });
260
+ } else {
261
+ throw new Error(`Your hook name is invalid: ${hookName}`);
262
+ }
263
+ });
264
+
265
+ return this;
266
+ }
267
+
268
+ #setupStore(): void {
269
+ const store = this.#store;
270
+ if (!store) {
271
+ throw new Error('For setup store, you should set `store` first.');
272
+ }
273
+ store
274
+ .getState()
275
+ .providers.addProvider(this.id, this.#configs as ProviderConfig);
276
+ this.#namespaces.forEach((namespace) => {
277
+ namespace.store(store);
278
+ });
279
+ }
280
+
281
+ #context(): Context {
282
+ return {
283
+ state: this.state.bind(this),
284
+ };
285
+ }
286
+ }
@@ -0,0 +1,37 @@
1
+ import type { FindProxiedNamespace } from '../../builders/mod.js';
2
+ import type { Store } from '../mod.js';
3
+ import type { AnyFunction, FunctionWithContext } from '../types.js';
4
+
5
+ export type Context = {
6
+ state: () => [GetState, SetState];
7
+ };
8
+
9
+ export interface State {
10
+ installed: boolean;
11
+ connected: boolean;
12
+ connecting: boolean;
13
+ }
14
+
15
+ export type SetState = <K extends keyof Pick<State, 'installed'>>(
16
+ name: K,
17
+ value: State[K],
18
+ ) => void;
19
+ export type GetState = {
20
+ (): State;
21
+ <K extends keyof State>(name: K): State[K];
22
+ };
23
+
24
+ export interface ExtendableInternalActions {
25
+ init?: FunctionWithContext<AnyFunction, Context>;
26
+ }
27
+
28
+ /**
29
+ * The internal `Provider.#namespaces` map type — keys are the registered
30
+ * namespace ids, values are their corresponding proxied namespaces.
31
+ */
32
+ export type RegisteredNamespaces<T> = Map<
33
+ keyof T,
34
+ FindProxiedNamespace<keyof T, T>
35
+ >;
36
+
37
+ export type ProviderBuilderOptions = { store?: Store };
@@ -0,0 +1,89 @@
1
+ export type NamespaceDisconnectedEvent = {
2
+ type: 'namespace_disconnected';
3
+ provider: string;
4
+ namespace: string;
5
+ };
6
+
7
+ export type NamespaceConnectedEvent = {
8
+ type: 'namespace_connected';
9
+ provider: string;
10
+ namespace: string;
11
+ accounts: string[];
12
+ };
13
+
14
+ export type NamespaceSwitchedAccountEvent = {
15
+ type: 'namespace_account_switched';
16
+ provider: string;
17
+ namespace: string;
18
+ accounts: string[];
19
+ previousAccounts: string[];
20
+ };
21
+
22
+ export type NamespaceSwitchedNetworkEvent = {
23
+ type: 'namespace_network_switched';
24
+ provider: string;
25
+ namespace: string;
26
+ network: string;
27
+ previousNetwork: string | null;
28
+ };
29
+
30
+ export type ProviderDetectedEvent = {
31
+ type: 'provider_detected';
32
+ provider: string;
33
+ };
34
+
35
+ export type ProviderConnectingEvent = {
36
+ type: 'provider_connecting';
37
+ provider: string;
38
+ value: boolean;
39
+ };
40
+
41
+ export type ProviderConnectedEvent = {
42
+ type: 'provider_connected';
43
+ provider: string;
44
+ };
45
+
46
+ export type ProviderDisconnectedEvent = {
47
+ type: 'provider_disconnected';
48
+ provider: string;
49
+ };
50
+
51
+ export type Event =
52
+ | NamespaceDisconnectedEvent
53
+ | NamespaceConnectedEvent
54
+ | NamespaceSwitchedAccountEvent
55
+ | NamespaceSwitchedNetworkEvent
56
+ | ProviderDetectedEvent
57
+ | ProviderConnectingEvent
58
+ | ProviderConnectedEvent
59
+ | ProviderDisconnectedEvent;
60
+
61
+ /**
62
+ *
63
+ * Keeping an array of Event and when iterates over its values, it will be removed (consume) from the array.
64
+ *
65
+ */
66
+ export class ConsumableEvents {
67
+ #data: Event[] = [];
68
+
69
+ push(val: Event) {
70
+ this.#data.push(val);
71
+ }
72
+
73
+ [Symbol.iterator](): Iterator<Event> {
74
+ return {
75
+ next: (): IteratorResult<Event> => {
76
+ if (this.#data.length == 0) {
77
+ return { done: true, value: undefined };
78
+ }
79
+
80
+ // Typescript can not narrow the type, but we have a runtime check to ensure it will never be an empty list
81
+ const value = this.#data.shift()!;
82
+ return {
83
+ done: false,
84
+ value,
85
+ };
86
+ },
87
+ };
88
+ }
89
+ }