@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.
- package/CHANGELOG.md +381 -0
- package/dist/builders/action.d.ts +22 -0
- package/dist/builders/action.d.ts.map +1 -0
- package/dist/builders/mod.d.ts +5 -0
- package/dist/builders/mod.d.ts.map +1 -0
- package/dist/builders/namespace.d.ts +47 -0
- package/dist/builders/namespace.d.ts.map +1 -0
- package/dist/builders/provider.d.ts +37 -0
- package/dist/builders/provider.d.ts.map +1 -0
- package/dist/builders/types.d.ts +23 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/hub/helpers.d.ts +6 -0
- package/dist/hub/helpers.d.ts.map +1 -0
- package/dist/hub/hub.d.ts +44 -0
- package/dist/hub/hub.d.ts.map +1 -0
- package/dist/hub/mod.d.ts +11 -0
- package/dist/hub/mod.d.ts.map +1 -0
- package/dist/hub/namespaces/errors.d.ts +5 -0
- package/dist/hub/namespaces/errors.d.ts.map +1 -0
- package/dist/hub/namespaces/mod.d.ts +3 -0
- package/dist/hub/namespaces/mod.d.ts.map +1 -0
- package/dist/hub/namespaces/namespace.d.ts +148 -0
- package/dist/hub/namespaces/namespace.d.ts.map +1 -0
- package/dist/hub/namespaces/types.d.ts +36 -0
- package/dist/hub/namespaces/types.d.ts.map +1 -0
- package/dist/hub/provider/mod.d.ts +3 -0
- package/dist/hub/provider/mod.d.ts.map +1 -0
- package/dist/hub/provider/provider.d.ts +102 -0
- package/dist/hub/provider/provider.d.ts.map +1 -0
- package/dist/hub/provider/types.d.ts +28 -0
- package/dist/hub/provider/types.d.ts.map +1 -0
- package/dist/hub/store/events.d.ts +54 -0
- package/dist/hub/store/events.d.ts.map +1 -0
- package/dist/hub/store/extend.d.ts +14 -0
- package/dist/hub/store/extend.d.ts.map +1 -0
- package/dist/hub/store/hub.d.ts +11 -0
- package/dist/hub/store/hub.d.ts.map +1 -0
- package/dist/hub/store/mod.d.ts +8 -0
- package/dist/hub/store/mod.d.ts.map +1 -0
- package/dist/hub/store/mod.js +2 -0
- package/dist/hub/store/mod.js.map +7 -0
- package/dist/hub/store/namespaces.d.ts +38 -0
- package/dist/hub/store/namespaces.d.ts.map +1 -0
- package/dist/hub/store/providers.d.ts +84 -0
- package/dist/hub/store/providers.d.ts.map +1 -0
- package/dist/hub/store/selectors.d.ts +18 -0
- package/dist/hub/store/selectors.d.ts.map +1 -0
- package/dist/hub/store/store.d.ts +14 -0
- package/dist/hub/store/store.d.ts.map +1 -0
- package/dist/hub/types.d.ts +5 -0
- package/dist/hub/types.d.ts.map +1 -0
- package/dist/mod.d.ts +5 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +2 -0
- package/dist/mod.js.map +7 -0
- package/dist/test-utils/fixtures.d.ts +4 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/mod.d.ts +2 -0
- package/dist/test-utils/mod.d.ts.map +1 -0
- package/dist/test-utils/mod.js +2 -0
- package/dist/test-utils/mod.js.map +7 -0
- package/dist/utils/mod.d.ts +3 -0
- package/dist/utils/mod.d.ts.map +1 -0
- package/dist/utils/mod.js +2 -0
- package/dist/utils/mod.js.map +7 -0
- package/dist/utils/versions.d.ts +50 -0
- package/dist/utils/versions.d.ts.map +1 -0
- package/package.json +49 -0
- package/readme.md +4 -0
- package/src/builders/action.test.ts +225 -0
- package/src/builders/action.ts +86 -0
- package/src/builders/mod.ts +5 -0
- package/src/builders/namespace.ts +258 -0
- package/src/builders/provider.ts +101 -0
- package/src/builders/types.ts +28 -0
- package/src/hub/helpers.ts +11 -0
- package/src/hub/hub.test.ts +108 -0
- package/src/hub/hub.ts +145 -0
- package/src/hub/mod.ts +21 -0
- package/src/hub/namespaces/errors.ts +11 -0
- package/src/hub/namespaces/mod.ts +10 -0
- package/src/hub/namespaces/namespace.test.ts +443 -0
- package/src/hub/namespaces/namespace.ts +470 -0
- package/src/hub/namespaces/types.ts +58 -0
- package/src/hub/provider/mod.ts +9 -0
- package/src/hub/provider/provider.test.ts +277 -0
- package/src/hub/provider/provider.ts +286 -0
- package/src/hub/provider/types.ts +37 -0
- package/src/hub/store/events.ts +89 -0
- package/src/hub/store/extend.ts +125 -0
- package/src/hub/store/hub.ts +18 -0
- package/src/hub/store/mod.ts +26 -0
- package/src/hub/store/namespaces.ts +177 -0
- package/src/hub/store/providers.ts +183 -0
- package/src/hub/store/selectors.ts +59 -0
- package/src/hub/store/store.test.ts +50 -0
- package/src/hub/store/store.ts +30 -0
- package/src/hub/types.ts +13 -0
- package/src/mod.ts +27 -0
- package/src/test-utils/fixtures.ts +13 -0
- package/src/test-utils/mod.ts +1 -0
- package/src/utils/mod.ts +2 -0
- package/src/utils/versions.test.ts +23 -0
- package/src/utils/versions.ts +91 -0
- package/store/package.json +8 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Note: This only works native async, if we are going to support for old transpilers like Babel.
|
|
3
|
+
*/
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
5
|
+
export function isAsync(fn: Function) {
|
|
6
|
+
return fn.constructor.name === 'AsyncFunction';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function generateStoreId(providerId: string, namespace: string) {
|
|
10
|
+
return `${providerId}$$${namespace}`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { garbageWalletMetaData } from '../test-utils/fixtures.js';
|
|
8
|
+
|
|
9
|
+
import { Hub } from './hub.js';
|
|
10
|
+
import { createStore } from './store/mod.js';
|
|
11
|
+
|
|
12
|
+
interface TestEvmActions {
|
|
13
|
+
connect: (chain: string) => Promise<{ accounts: string[]; network: string }>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const buildEvmProvider = (id: string) => {
|
|
17
|
+
const evmNamespace = new NamespaceBuilder<TestEvmActions>('eip155', id)
|
|
18
|
+
.action('connect', async () => ({
|
|
19
|
+
accounts: ['eip155:0x1:0x000000000000000000000000000000000000dead'],
|
|
20
|
+
network: 'eth',
|
|
21
|
+
}))
|
|
22
|
+
.build();
|
|
23
|
+
|
|
24
|
+
return new ProviderBuilder(id)
|
|
25
|
+
.config('metadata', garbageWalletMetaData)
|
|
26
|
+
.add('evm', evmNamespace)
|
|
27
|
+
.build();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('Hub', () => {
|
|
31
|
+
let store: Store;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
store = createStore();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('add registers the provider in both the hub and the store', () => {
|
|
38
|
+
const provider = buildEvmProvider('wallet-a');
|
|
39
|
+
const hub = new Hub({ store }).add(provider.id, provider);
|
|
40
|
+
|
|
41
|
+
expect(hub.get('wallet-a')).toBe(provider);
|
|
42
|
+
expect(store.getState().providers.list['wallet-a']).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('get returns undefined for an unknown provider id', () => {
|
|
46
|
+
const hub = new Hub({ store });
|
|
47
|
+
|
|
48
|
+
expect(hub.get('does-not-exist')).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('getAll returns every registered provider', () => {
|
|
52
|
+
const a = buildEvmProvider('wallet-a');
|
|
53
|
+
const b = buildEvmProvider('wallet-b');
|
|
54
|
+
const hub = new Hub({ store }).add(a.id, a).add(b.id, b);
|
|
55
|
+
|
|
56
|
+
const all = hub.getAll();
|
|
57
|
+
expect(all.size).toBe(2);
|
|
58
|
+
expect(all.get('wallet-a')).toBe(a);
|
|
59
|
+
expect(all.get('wallet-b')).toBe(b);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('remove clears the provider from both the hub and the store', () => {
|
|
63
|
+
const provider = buildEvmProvider('wallet-a');
|
|
64
|
+
const hub = new Hub({ store }).add(provider.id, provider);
|
|
65
|
+
|
|
66
|
+
expect(store.getState().providers.list['wallet-a']).toBeDefined();
|
|
67
|
+
|
|
68
|
+
hub.remove('wallet-a');
|
|
69
|
+
|
|
70
|
+
expect(hub.get('wallet-a')).toBeUndefined();
|
|
71
|
+
expect(store.getState().providers.list['wallet-a']).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('remove throws when the provider id is unknown', () => {
|
|
75
|
+
const hub = new Hub({ store });
|
|
76
|
+
|
|
77
|
+
expect(() => hub.remove('nope')).toThrowError(/Provider not found/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('init runs each provider init exactly once', () => {
|
|
81
|
+
const initSpy = vi.fn();
|
|
82
|
+
const provider = new ProviderBuilder('wallet-a')
|
|
83
|
+
.config('metadata', garbageWalletMetaData)
|
|
84
|
+
.init(initSpy)
|
|
85
|
+
.build();
|
|
86
|
+
|
|
87
|
+
const hub = new Hub({ store }).add(provider.id, provider);
|
|
88
|
+
|
|
89
|
+
expect(initSpy).toBeCalledTimes(0);
|
|
90
|
+
|
|
91
|
+
hub.init();
|
|
92
|
+
hub.init();
|
|
93
|
+
|
|
94
|
+
expect(initSpy).toBeCalledTimes(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('state aggregates per-provider state and their namespaces', () => {
|
|
98
|
+
const provider = buildEvmProvider('wallet-a');
|
|
99
|
+
const hub = new Hub({ store }).add(provider.id, provider);
|
|
100
|
+
|
|
101
|
+
const state = hub.state();
|
|
102
|
+
expect(state['wallet-a']).toBeDefined();
|
|
103
|
+
expect(state['wallet-a'].installed).toBe(false);
|
|
104
|
+
expect(state['wallet-a'].connected).toBe(false);
|
|
105
|
+
expect(state['wallet-a'].namespaces).toHaveLength(1);
|
|
106
|
+
expect(state['wallet-a'].namespaces[0].connected).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/hub/hub.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* Hub stores providers with heterogeneous `TNS` shapes. `Provider<TNS>` is
|
|
7
|
+
* invariant in `TNS` (TNS appears in both reading and writing positions via
|
|
8
|
+
* the internal `Map`), so the only universally-assignable upper bound is
|
|
9
|
+
* `Provider<any>`. Concrete TNS information is preserved on each `Provider`
|
|
10
|
+
* instance itself — consumers recover precise types via `provider.get('evm')`.
|
|
11
|
+
*
|
|
12
|
+
* TODO: consider making `Provider<TNS>` covariant in TNS so this `any` can be
|
|
13
|
+
* replaced with `Provider<unknown>`. Requires switching the internal storage
|
|
14
|
+
* to a covariant container (e.g. `ReadonlyMap` semantics), moving TNS-typed
|
|
15
|
+
* constructor inputs out of the public class surface (factory pattern or
|
|
16
|
+
* interface + impl), and dropping TNS info from `getAll()`'s return type.
|
|
17
|
+
* Bigger surgery than this PR; deserves its own review cycle.
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
type AnyProvider = Provider<any>;
|
|
21
|
+
|
|
22
|
+
type HubState = {
|
|
23
|
+
[key in string]: ProviderState & {
|
|
24
|
+
namespaces: NamespaceState[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type RunAllResult = {
|
|
29
|
+
id: string;
|
|
30
|
+
provider: unknown;
|
|
31
|
+
namespaces: unknown[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
interface HubOptions {
|
|
35
|
+
store?: Store;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class Hub {
|
|
39
|
+
#providers = new Map<string, AnyProvider>();
|
|
40
|
+
#options: HubOptions;
|
|
41
|
+
|
|
42
|
+
constructor(options?: HubOptions) {
|
|
43
|
+
this.#options = options ?? {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
init() {
|
|
47
|
+
this.runAll('init');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* Running a specific action (e.g. init) on all namespaces and providers one by one.
|
|
52
|
+
*
|
|
53
|
+
* TODO: Some of methods may accepts args, with this implementation we only limit to those one without any argument.
|
|
54
|
+
*/
|
|
55
|
+
runAll(action: string): RunAllResult[] {
|
|
56
|
+
const output: RunAllResult[] = [];
|
|
57
|
+
|
|
58
|
+
const providers = this.#providers.values();
|
|
59
|
+
|
|
60
|
+
for (const provider of providers) {
|
|
61
|
+
const providerOutput: RunAllResult = {
|
|
62
|
+
id: provider.id,
|
|
63
|
+
provider: undefined,
|
|
64
|
+
namespaces: [],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const providerMethod = provider[action as keyof AnyProvider];
|
|
68
|
+
if (typeof providerMethod === 'function') {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
70
|
+
// @ts-ignore-next-line
|
|
71
|
+
providerOutput.provider = providerMethod.call(provider);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Namespace instances can have their own `action` as well. we will call them as well.
|
|
75
|
+
const namespaces = provider.getAll().values();
|
|
76
|
+
for (const namespace of namespaces) {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
78
|
+
// @ts-ignore-next-line
|
|
79
|
+
const namespaceMethod = namespace[action];
|
|
80
|
+
if (typeof namespaceMethod === 'function') {
|
|
81
|
+
const result = namespaceMethod();
|
|
82
|
+
providerOutput.namespaces.push(result);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
output.push(providerOutput);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
add(id: string, provider: AnyProvider): this {
|
|
93
|
+
if (this.#options.store) {
|
|
94
|
+
provider.store(this.#options.store);
|
|
95
|
+
}
|
|
96
|
+
this.#providers.set(id, provider);
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
remove(id: string): this {
|
|
101
|
+
const providerToRemove = this.#providers.get(id);
|
|
102
|
+
|
|
103
|
+
if (!providerToRemove) {
|
|
104
|
+
throw new Error(`Provider not found: No provider exists with ID "${id}"`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.#options.store?.getState().providers.removeProvider(id);
|
|
108
|
+
this.#providers.delete(id);
|
|
109
|
+
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get(providerId: string): AnyProvider | undefined {
|
|
114
|
+
return this.#providers.get(providerId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getAll(): Map<string, AnyProvider> {
|
|
118
|
+
return this.#providers;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
state(): HubState {
|
|
122
|
+
const output = this.runAll('state');
|
|
123
|
+
const res: HubState = {};
|
|
124
|
+
|
|
125
|
+
output.forEach((result) => {
|
|
126
|
+
const namespaces: NamespaceState[] = [];
|
|
127
|
+
result.namespaces.forEach((b) => {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
const [getNamespaceState] = b as ReturnType<Namespace<any>['state']>;
|
|
130
|
+
|
|
131
|
+
namespaces.push(getNamespaceState());
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const [getProviderState] = result.provider as ReturnType<
|
|
135
|
+
AnyProvider['state']
|
|
136
|
+
>;
|
|
137
|
+
|
|
138
|
+
res[result.id] = {
|
|
139
|
+
...(getProviderState() || {}),
|
|
140
|
+
namespaces: namespaces,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
return res;
|
|
144
|
+
}
|
|
145
|
+
}
|
package/src/hub/mod.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
Actions,
|
|
3
|
+
Subscriber,
|
|
4
|
+
SubscriberCleanUp,
|
|
5
|
+
Context,
|
|
6
|
+
} from './namespaces/mod.js';
|
|
7
|
+
export { Namespace } from './namespaces/mod.js';
|
|
8
|
+
|
|
9
|
+
export { Provider } from './provider/mod.js';
|
|
10
|
+
export type { RegisteredNamespaces } from './provider/mod.js';
|
|
11
|
+
|
|
12
|
+
export { Hub } from './hub.js';
|
|
13
|
+
export type { Store, State, ProviderMetadata } from './store/mod.js';
|
|
14
|
+
export {
|
|
15
|
+
createStore,
|
|
16
|
+
guessProviderStateSelector,
|
|
17
|
+
namespaceStateSelector,
|
|
18
|
+
} from './store/mod.js';
|
|
19
|
+
export { generateStoreId } from './helpers.js';
|
|
20
|
+
export type { CanEagerConnect } from './namespaces/types.js';
|
|
21
|
+
export type { FunctionWithContext, AnyFunction } from './types.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.';
|