@monygroupcorp/micro-web3 0.1.1 → 1.2.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.
- package/CLAUDE.md +6 -0
- package/dist/{micro-web3.cjs.js → micro-web3.cjs} +3 -3
- package/dist/micro-web3.cjs.map +1 -0
- package/dist/micro-web3.esm.js +2 -2
- package/dist/micro-web3.esm.js.map +1 -1
- package/dist/micro-web3.umd.js +2 -2
- package/dist/micro-web3.umd.js.map +1 -1
- package/docs/plans/2026-01-22-event-indexer.md +3642 -0
- package/monygroupcorp-micro-web3-0.1.3.tgz +0 -0
- package/package.json +2 -2
- package/rollup.config.cjs +1 -1
- package/src/components/FloatingWalletButton/FloatingWalletButton.js +26 -5
- package/src/components/SettingsModal/SettingsModal.js +371 -0
- package/src/components/SyncProgressBar/SyncProgressBar.js +238 -0
- package/src/components/WalletButton/WalletButton.js +213 -0
- package/src/index.js +18 -2
- package/src/indexer/EntityResolver.js +218 -0
- package/src/indexer/Patterns.js +277 -0
- package/src/indexer/QueryEngine.js +149 -0
- package/src/indexer/SyncEngine.js +494 -0
- package/src/indexer/index.js +13 -0
- package/src/services/BlockchainService.js +30 -0
- package/src/services/ContractCache.js +20 -2
- package/src/services/EventIndexer.js +399 -0
- package/src/storage/IndexedDBAdapter.js +423 -0
- package/src/storage/IndexerSettings.js +88 -0
- package/src/storage/MemoryAdapter.js +194 -0
- package/src/storage/StorageAdapter.js +129 -0
- package/src/storage/index.js +41 -0
- package/dist/micro-web3.cjs.js.map +0 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { FloatingWalletButton } from '../FloatingWalletButton/FloatingWalletButton.js';
|
|
2
|
+
import WalletModal from '../Wallet/WalletModal.js';
|
|
3
|
+
|
|
4
|
+
class WalletButtonModal extends WalletModal {
|
|
5
|
+
events() {
|
|
6
|
+
const baseEvents = typeof super.events === 'function' ? super.events() : {};
|
|
7
|
+
return {
|
|
8
|
+
...baseEvents,
|
|
9
|
+
'click .wallet-option': async (e) => {
|
|
10
|
+
const button = e?.target?.closest?.('.wallet-option');
|
|
11
|
+
const walletType = button?.dataset?.wallet;
|
|
12
|
+
if (!walletType) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (this.onWalletSelected) {
|
|
16
|
+
await this.onWalletSelected(walletType);
|
|
17
|
+
}
|
|
18
|
+
this.hide();
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class WalletButton extends FloatingWalletButton {
|
|
25
|
+
constructor(props) {
|
|
26
|
+
super(props);
|
|
27
|
+
this.modalRoot = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async showWalletModal() {
|
|
31
|
+
const providerMap =
|
|
32
|
+
this.walletService?.providerMap ||
|
|
33
|
+
{
|
|
34
|
+
rabby: () => (window.ethereum?.isRabby ? window.ethereum : null),
|
|
35
|
+
rainbow: () => (window.ethereum?.isRainbow ? window.ethereum : null),
|
|
36
|
+
phantom: () => window.phantom?.ethereum || null,
|
|
37
|
+
metamask: () => window.ethereum || null,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const walletIcons =
|
|
41
|
+
this.walletService?.walletIcons ||
|
|
42
|
+
{
|
|
43
|
+
rabby: '/public/wallets/rabby.webp',
|
|
44
|
+
rainbow: '/public/wallets/rainbow.webp',
|
|
45
|
+
phantom: '/public/wallets/phantom.webp',
|
|
46
|
+
metamask: '/public/wallets/MetaMask.webp',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (!this.walletModal) {
|
|
50
|
+
this.walletModal = new WalletButtonModal({
|
|
51
|
+
providerMap,
|
|
52
|
+
walletIcons,
|
|
53
|
+
onWalletSelected: async (walletType) => {
|
|
54
|
+
await this.handleWalletSelection(walletType);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!this.modalRoot) {
|
|
59
|
+
this.modalRoot = document.createElement('div');
|
|
60
|
+
document.body.appendChild(this.modalRoot);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.walletModal.mount(this.modalRoot);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.walletModal.show();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onUnmount() {
|
|
70
|
+
if (this.modalRoot?.parentNode) {
|
|
71
|
+
this.modalRoot.parentNode.removeChild(this.modalRoot);
|
|
72
|
+
this.modalRoot = null;
|
|
73
|
+
}
|
|
74
|
+
super.onUnmount();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
if (this.state.loading) {
|
|
79
|
+
return `
|
|
80
|
+
<div class="floating-wallet-button loading">
|
|
81
|
+
<div class="wallet-spinner"></div>
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { walletConnected, address, balance, menuOpen } = this.state;
|
|
87
|
+
|
|
88
|
+
if (!walletConnected) {
|
|
89
|
+
return `
|
|
90
|
+
<div class="floating-wallet-button disconnected" data-ref="wallet-button">
|
|
91
|
+
<button class="wallet-btn" data-ref="connect-btn">
|
|
92
|
+
<span class="wallet-mark"></span>
|
|
93
|
+
<span class="wallet-text">Connect Wallet</span>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const truncatedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
100
|
+
const classNames = ['floating-wallet-button', 'connected'];
|
|
101
|
+
if (menuOpen) {
|
|
102
|
+
classNames.push('menu-open');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const markup = `
|
|
106
|
+
<div class="${classNames.join(' ')}" data-ref="wallet-button">
|
|
107
|
+
<button class="wallet-btn" data-ref="wallet-btn" title="${this.escapeHtml(address)}\nBalance: ${balance} ETH">
|
|
108
|
+
<span class="wallet-mark wallet-mark--connected"></span>
|
|
109
|
+
<span class="wallet-address">${this.escapeHtml(truncatedAddress)}</span>
|
|
110
|
+
<span class="wallet-disconnect" data-ref="inline-disconnect" title="Disconnect">⏏︎</span>
|
|
111
|
+
</button>
|
|
112
|
+
${this.renderWalletPanel(address, balance, menuOpen)}
|
|
113
|
+
</div>
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
return this.minifyHtml(markup);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
events() {
|
|
120
|
+
const baseEvents = super.events?.() || {};
|
|
121
|
+
return {
|
|
122
|
+
...baseEvents,
|
|
123
|
+
'click [data-ref="inline-disconnect"]': (e) => this.handleInlineDisconnect(e),
|
|
124
|
+
'click [data-ref="wallet-btn"]': (e) => this.handleWalletToggle(e),
|
|
125
|
+
'click [data-ref="connect-btn"]': (e) => this.handleWalletToggle(e),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
renderWalletPanel(address, balance, menuOpen) {
|
|
130
|
+
const truncatedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
131
|
+
|
|
132
|
+
const markup = `
|
|
133
|
+
<div class="wallet-info-panel" data-ref="wallet-panel" aria-hidden="${menuOpen ? 'false' : 'true'}">
|
|
134
|
+
<div class="wallet-info-row">
|
|
135
|
+
<span class="wallet-info-label">Address</span>
|
|
136
|
+
<span class="wallet-info-value">${this.escapeHtml(truncatedAddress)}</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="wallet-info-row">
|
|
139
|
+
<span class="wallet-info-label">ETH Balance</span>
|
|
140
|
+
<span class="wallet-info-value">${balance} ETH</span>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
return this.minifyHtml(markup);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onStateUpdate(oldState, newState) {
|
|
149
|
+
if (typeof super.onStateUpdate === 'function') {
|
|
150
|
+
super.onStateUpdate(oldState, newState);
|
|
151
|
+
}
|
|
152
|
+
if (!oldState || oldState.menuOpen !== newState.menuOpen) {
|
|
153
|
+
if (newState.menuOpen) {
|
|
154
|
+
this.attachOutsideClickListener();
|
|
155
|
+
} else {
|
|
156
|
+
this.detachOutsideClickListener();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
attachOutsideClickListener() {
|
|
162
|
+
if (this.outsideClickHandler || this.outsideClickTimeout) return;
|
|
163
|
+
this.outsideClickHandler = (event) => {
|
|
164
|
+
if (!this.element) return;
|
|
165
|
+
const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
|
|
166
|
+
const clickedInside =
|
|
167
|
+
(path.length && path.includes(this.element)) ||
|
|
168
|
+
(event.target && this.element.contains(event.target));
|
|
169
|
+
if (!clickedInside) {
|
|
170
|
+
this.setState({ menuOpen: false });
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
this.outsideClickTimeout = window.setTimeout(() => {
|
|
174
|
+
document.addEventListener('click', this.outsideClickHandler);
|
|
175
|
+
this.outsideClickTimeout = null;
|
|
176
|
+
}, 0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
detachOutsideClickListener() {
|
|
180
|
+
if (this.outsideClickTimeout) {
|
|
181
|
+
window.clearTimeout(this.outsideClickTimeout);
|
|
182
|
+
this.outsideClickTimeout = null;
|
|
183
|
+
}
|
|
184
|
+
if (this.outsideClickHandler) {
|
|
185
|
+
document.removeEventListener('click', this.outsideClickHandler);
|
|
186
|
+
this.outsideClickHandler = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setupDOMEventListeners() {
|
|
191
|
+
// Delegated events handle wiring in this component.
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
handleWalletToggle(e) {
|
|
195
|
+
this.handleButtonClick(e);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
handleInlineDisconnect(e) {
|
|
199
|
+
if (e) {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
e.stopPropagation();
|
|
202
|
+
}
|
|
203
|
+
super.handleDisconnect(e);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
minifyHtml(markup) {
|
|
207
|
+
return typeof markup === 'string'
|
|
208
|
+
? markup.replace(/>\s+</g, '><').trim()
|
|
209
|
+
: markup;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default WalletButton;
|
package/src/index.js
CHANGED
|
@@ -4,9 +4,14 @@ import WalletService from './services/WalletService.js';
|
|
|
4
4
|
import ContractCache from './services/ContractCache.js';
|
|
5
5
|
import PriceService from './services/PriceService.js';
|
|
6
6
|
import * as IpfsService from './services/IpfsService.js';
|
|
7
|
+
import EventIndexer from './services/EventIndexer.js';
|
|
8
|
+
|
|
9
|
+
// Storage adapters and settings
|
|
10
|
+
import { IndexedDBAdapter, MemoryAdapter, IndexerSettings } from './storage/index.js';
|
|
7
11
|
|
|
8
12
|
// UI Components
|
|
9
13
|
import { FloatingWalletButton } from './components/FloatingWalletButton/FloatingWalletButton.js';
|
|
14
|
+
import { WalletButton } from './components/WalletButton/WalletButton.js';
|
|
10
15
|
import { WalletModal } from './components/Wallet/WalletModal.js';
|
|
11
16
|
import { IpfsImage } from './components/Ipfs/IpfsImage.js';
|
|
12
17
|
import { SwapInterface } from './components/Swap/SwapInterface.js';
|
|
@@ -18,6 +23,8 @@ import { BondingCurve } from './components/BondingCurve/BondingCurve.js';
|
|
|
18
23
|
import { PriceDisplay } from './components/Display/PriceDisplay.js';
|
|
19
24
|
import { BalanceDisplay } from './components/Display/BalanceDisplay.js';
|
|
20
25
|
import { MessagePopup } from './components/Util/MessagePopup.js';
|
|
26
|
+
import { SyncProgressBar } from './components/SyncProgressBar/SyncProgressBar.js';
|
|
27
|
+
import { SettingsModal } from './components/SettingsModal/SettingsModal.js';
|
|
21
28
|
|
|
22
29
|
export {
|
|
23
30
|
// Services
|
|
@@ -26,9 +33,16 @@ export {
|
|
|
26
33
|
ContractCache,
|
|
27
34
|
PriceService,
|
|
28
35
|
IpfsService,
|
|
36
|
+
EventIndexer,
|
|
37
|
+
|
|
38
|
+
// Storage adapters and settings
|
|
39
|
+
IndexedDBAdapter,
|
|
40
|
+
MemoryAdapter,
|
|
41
|
+
IndexerSettings,
|
|
29
42
|
|
|
30
43
|
// UI Components
|
|
31
44
|
FloatingWalletButton,
|
|
45
|
+
WalletButton,
|
|
32
46
|
WalletModal,
|
|
33
47
|
IpfsImage,
|
|
34
48
|
SwapInterface,
|
|
@@ -39,5 +53,7 @@ export {
|
|
|
39
53
|
BondingCurve,
|
|
40
54
|
PriceDisplay,
|
|
41
55
|
BalanceDisplay,
|
|
42
|
-
MessagePopup
|
|
43
|
-
|
|
56
|
+
MessagePopup,
|
|
57
|
+
SyncProgressBar,
|
|
58
|
+
SettingsModal
|
|
59
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/indexer/EntityResolver.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Entity resolver for EventIndexer.
|
|
5
|
+
* Provides the Entities API for domain-level queries.
|
|
6
|
+
*/
|
|
7
|
+
class EntityResolver {
|
|
8
|
+
constructor(queryEngine, entityDefinitions = {}) {
|
|
9
|
+
this.queryEngine = queryEngine;
|
|
10
|
+
this.definitions = entityDefinitions;
|
|
11
|
+
this.entityAPIs = {};
|
|
12
|
+
|
|
13
|
+
// Create API for each entity
|
|
14
|
+
for (const [name, definition] of Object.entries(entityDefinitions)) {
|
|
15
|
+
this.entityAPIs[name] = this._createEntityAPI(name, definition);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get entity API by name.
|
|
21
|
+
* @param {string} name - Entity name
|
|
22
|
+
* @returns {EntityQueryable}
|
|
23
|
+
*/
|
|
24
|
+
getEntity(name) {
|
|
25
|
+
return this.entityAPIs[name];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get all entity APIs (for proxy access).
|
|
30
|
+
* @returns {Object}
|
|
31
|
+
*/
|
|
32
|
+
getAllEntities() {
|
|
33
|
+
return this.entityAPIs;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_createEntityAPI(name, definition) {
|
|
37
|
+
const self = this;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
/**
|
|
41
|
+
* Query entities.
|
|
42
|
+
* @param {EntityWhereClause} where - Filter conditions
|
|
43
|
+
* @returns {Promise<Entity[]>}
|
|
44
|
+
*/
|
|
45
|
+
async query(where = {}) {
|
|
46
|
+
return self._queryEntities(name, definition, where);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get single entity by key.
|
|
51
|
+
* @param {string|number} key - Entity key value
|
|
52
|
+
* @returns {Promise<Entity|null>}
|
|
53
|
+
*/
|
|
54
|
+
async get(key) {
|
|
55
|
+
const entities = await self._queryEntities(name, definition, {
|
|
56
|
+
[definition.key]: key
|
|
57
|
+
});
|
|
58
|
+
return entities[0] || null;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to entity changes.
|
|
63
|
+
* @param {EntityWhereClause} where - Filter conditions
|
|
64
|
+
* @param {Function} callback - Called when entities change
|
|
65
|
+
* @returns {Function} Unsubscribe function
|
|
66
|
+
*/
|
|
67
|
+
subscribe(where, callback) {
|
|
68
|
+
// Subscribe to source event and related events
|
|
69
|
+
const eventTypes = self._getRelevantEventTypes(definition);
|
|
70
|
+
|
|
71
|
+
return self.queryEngine.subscribe(eventTypes, async () => {
|
|
72
|
+
const entities = await self._queryEntities(name, definition, where);
|
|
73
|
+
callback(entities);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Count entities.
|
|
79
|
+
* @param {EntityWhereClause} where - Filter conditions
|
|
80
|
+
* @returns {Promise<number>}
|
|
81
|
+
*/
|
|
82
|
+
async count(where = {}) {
|
|
83
|
+
const entities = await self._queryEntities(name, definition, where);
|
|
84
|
+
return entities.length;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async _queryEntities(name, definition, where = {}) {
|
|
90
|
+
// Separate status filter from direct filters
|
|
91
|
+
const statusFilter = where.status;
|
|
92
|
+
const directFilters = { ...where };
|
|
93
|
+
delete directFilters.status;
|
|
94
|
+
|
|
95
|
+
// Query source events
|
|
96
|
+
const sourceResult = await this.queryEngine.query(definition.source, {
|
|
97
|
+
where: directFilters,
|
|
98
|
+
limit: 10000 // Get all matching source events
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Get event checker for status/computed evaluation
|
|
102
|
+
const eventChecker = await this.queryEngine.getEventChecker();
|
|
103
|
+
|
|
104
|
+
// Prefetch events needed for status evaluation
|
|
105
|
+
const relevantEventTypes = this._getRelevantEventTypes(definition);
|
|
106
|
+
await eventChecker.prefetch(relevantEventTypes);
|
|
107
|
+
|
|
108
|
+
// Build entities from source events
|
|
109
|
+
const entities = [];
|
|
110
|
+
for (const sourceEvent of sourceResult.events) {
|
|
111
|
+
const entity = await this._buildEntity(name, definition, sourceEvent, eventChecker);
|
|
112
|
+
|
|
113
|
+
// Apply status filter
|
|
114
|
+
if (statusFilter && entity.status !== statusFilter) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
entities.push(entity);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return entities;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async _buildEntity(name, definition, sourceEvent, eventChecker) {
|
|
125
|
+
const entity = {
|
|
126
|
+
_type: name,
|
|
127
|
+
_sourceEvent: sourceEvent,
|
|
128
|
+
...sourceEvent.data
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Add key field explicitly
|
|
132
|
+
entity[definition.key] = sourceEvent.data[definition.key] || sourceEvent.indexed[definition.key];
|
|
133
|
+
|
|
134
|
+
// Evaluate status
|
|
135
|
+
if (definition.status) {
|
|
136
|
+
entity.status = this._evaluateStatus(entity, definition.status, eventChecker);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Evaluate computed fields
|
|
140
|
+
if (definition.computed) {
|
|
141
|
+
for (const [fieldName, computeFn] of Object.entries(definition.computed)) {
|
|
142
|
+
try {
|
|
143
|
+
entity[fieldName] = computeFn(entity, eventChecker);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`[EntityResolver] Error computing ${fieldName}:`, error);
|
|
146
|
+
entity[fieldName] = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Resolve relations (lazy - only resolve when accessed)
|
|
152
|
+
if (definition.relations) {
|
|
153
|
+
for (const [relationName, relationDef] of Object.entries(definition.relations)) {
|
|
154
|
+
Object.defineProperty(entity, relationName, {
|
|
155
|
+
get: () => this._resolveRelation(entity, relationDef),
|
|
156
|
+
enumerable: true
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return entity;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_evaluateStatus(entity, statusDef, eventChecker) {
|
|
165
|
+
// Check each status condition in order
|
|
166
|
+
for (const [statusName, condition] of Object.entries(statusDef)) {
|
|
167
|
+
if (statusName === 'default') continue;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
if (condition(entity, eventChecker)) {
|
|
171
|
+
return statusName;
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`[EntityResolver] Error evaluating status ${statusName}:`, error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return statusDef.default || 'unknown';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async _resolveRelation(entity, relationDef) {
|
|
182
|
+
const relatedEntity = this.entityAPIs[relationDef.entity];
|
|
183
|
+
if (!relatedEntity) return null;
|
|
184
|
+
|
|
185
|
+
const foreignKeyValue = entity[relationDef.foreignKey];
|
|
186
|
+
if (foreignKeyValue === undefined) return null;
|
|
187
|
+
|
|
188
|
+
if (relationDef.type === 'many') {
|
|
189
|
+
return relatedEntity.query({ [relationDef.foreignKey]: foreignKeyValue });
|
|
190
|
+
} else {
|
|
191
|
+
return relatedEntity.get(foreignKeyValue);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_getRelevantEventTypes(definition) {
|
|
196
|
+
const types = new Set([definition.source]);
|
|
197
|
+
|
|
198
|
+
// Add events referenced in status conditions
|
|
199
|
+
if (definition.status) {
|
|
200
|
+
for (const condition of Object.values(definition.status)) {
|
|
201
|
+
if (typeof condition === 'function') {
|
|
202
|
+
// Try to extract event names from function source
|
|
203
|
+
// This is a best-effort heuristic
|
|
204
|
+
const fnStr = condition.toString();
|
|
205
|
+
const matches = fnStr.match(/events\.has\(['"](\w+)['"]/g) || [];
|
|
206
|
+
for (const match of matches) {
|
|
207
|
+
const eventName = match.match(/['"](\w+)['"]/)?.[1];
|
|
208
|
+
if (eventName) types.add(eventName);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return Array.from(types);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export default EntityResolver;
|