@monygroupcorp/micro-web3 0.1.3 → 1.2.1
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/package.json +2 -2
- package/rollup.config.cjs +1 -1
- package/src/components/FloatingWalletButton/FloatingWalletButton.js +53 -21
- package/src/components/SettingsModal/SettingsModal.js +371 -0
- package/src/components/SyncProgressBar/SyncProgressBar.js +238 -0
- package/src/index.js +15 -1
- 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,238 @@
|
|
|
1
|
+
// src/components/SyncProgressBar/SyncProgressBar.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SyncProgressBar - UI component showing indexer sync status.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { SyncProgressBar } from '@monygroupcorp/micro-web3/components';
|
|
8
|
+
* const progressBar = new SyncProgressBar({ indexer, eventBus });
|
|
9
|
+
* progressBar.mount(document.getElementById('sync-status'));
|
|
10
|
+
*/
|
|
11
|
+
class SyncProgressBar {
|
|
12
|
+
constructor({ indexer, eventBus }) {
|
|
13
|
+
if (!indexer) {
|
|
14
|
+
throw new Error('SyncProgressBar requires indexer instance');
|
|
15
|
+
}
|
|
16
|
+
if (!eventBus) {
|
|
17
|
+
throw new Error('SyncProgressBar requires eventBus instance');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.indexer = indexer;
|
|
21
|
+
this.eventBus = eventBus;
|
|
22
|
+
this.element = null;
|
|
23
|
+
this.unsubscribers = [];
|
|
24
|
+
|
|
25
|
+
this.state = {
|
|
26
|
+
status: 'initializing',
|
|
27
|
+
progress: 0,
|
|
28
|
+
currentBlock: 0,
|
|
29
|
+
latestBlock: 0,
|
|
30
|
+
error: null
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
mount(container) {
|
|
35
|
+
if (typeof container === 'string') {
|
|
36
|
+
container = document.querySelector(container);
|
|
37
|
+
}
|
|
38
|
+
if (!container) {
|
|
39
|
+
throw new Error('SyncProgressBar: Invalid container');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Create element
|
|
43
|
+
this.element = document.createElement('div');
|
|
44
|
+
this.element.className = 'mw3-sync-progress';
|
|
45
|
+
container.appendChild(this.element);
|
|
46
|
+
|
|
47
|
+
// Subscribe to events
|
|
48
|
+
this._setupListeners();
|
|
49
|
+
|
|
50
|
+
// Initial render
|
|
51
|
+
this._updateState(this.indexer.sync.getStatus());
|
|
52
|
+
this._render();
|
|
53
|
+
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
unmount() {
|
|
58
|
+
// Cleanup subscriptions
|
|
59
|
+
for (const unsub of this.unsubscribers) {
|
|
60
|
+
unsub();
|
|
61
|
+
}
|
|
62
|
+
this.unsubscribers = [];
|
|
63
|
+
|
|
64
|
+
// Remove element
|
|
65
|
+
if (this.element && this.element.parentNode) {
|
|
66
|
+
this.element.parentNode.removeChild(this.element);
|
|
67
|
+
}
|
|
68
|
+
this.element = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_setupListeners() {
|
|
72
|
+
const listeners = [
|
|
73
|
+
['indexer:syncProgress', (data) => {
|
|
74
|
+
this._updateState({
|
|
75
|
+
status: 'syncing',
|
|
76
|
+
progress: data.progress,
|
|
77
|
+
currentBlock: data.currentBlock,
|
|
78
|
+
latestBlock: data.latestBlock
|
|
79
|
+
});
|
|
80
|
+
}],
|
|
81
|
+
['indexer:syncComplete', () => {
|
|
82
|
+
this._updateState({ status: 'synced', progress: 1 });
|
|
83
|
+
}],
|
|
84
|
+
['indexer:error', (data) => {
|
|
85
|
+
this._updateState({ status: 'error', error: data.message });
|
|
86
|
+
}],
|
|
87
|
+
['indexer:paused', () => {
|
|
88
|
+
this._updateState({ status: 'paused' });
|
|
89
|
+
}],
|
|
90
|
+
['indexer:resumed', () => {
|
|
91
|
+
this._updateState({ status: 'syncing' });
|
|
92
|
+
}]
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (const [event, handler] of listeners) {
|
|
96
|
+
const unsub = this.eventBus.on(event, handler);
|
|
97
|
+
this.unsubscribers.push(unsub);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_updateState(updates) {
|
|
102
|
+
this.state = { ...this.state, ...updates };
|
|
103
|
+
this._render();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_render() {
|
|
107
|
+
if (!this.element) return;
|
|
108
|
+
|
|
109
|
+
const { status, progress, currentBlock, latestBlock, error } = this.state;
|
|
110
|
+
|
|
111
|
+
// Update class for state
|
|
112
|
+
this.element.className = `mw3-sync-progress mw3-sync-progress--${status}`;
|
|
113
|
+
|
|
114
|
+
if (status === 'syncing') {
|
|
115
|
+
const percent = Math.round(progress * 100);
|
|
116
|
+
this.element.innerHTML = `
|
|
117
|
+
<div class="mw3-sync-progress__bar" style="width: ${percent}%"></div>
|
|
118
|
+
<span class="mw3-sync-progress__text">Syncing... ${percent}%</span>
|
|
119
|
+
`;
|
|
120
|
+
} else if (status === 'synced') {
|
|
121
|
+
this.element.innerHTML = `
|
|
122
|
+
<span class="mw3-sync-progress__text">Synced to block ${latestBlock.toLocaleString()}</span>
|
|
123
|
+
`;
|
|
124
|
+
} else if (status === 'error') {
|
|
125
|
+
this.element.innerHTML = `
|
|
126
|
+
<span class="mw3-sync-progress__text">Sync failed: ${error || 'Unknown error'}</span>
|
|
127
|
+
<button class="mw3-sync-progress__retry">Retry</button>
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
// Add retry handler
|
|
131
|
+
const retryBtn = this.element.querySelector('.mw3-sync-progress__retry');
|
|
132
|
+
if (retryBtn) {
|
|
133
|
+
retryBtn.onclick = () => this.indexer.sync.resync();
|
|
134
|
+
}
|
|
135
|
+
} else if (status === 'paused') {
|
|
136
|
+
this.element.innerHTML = `
|
|
137
|
+
<span class="mw3-sync-progress__text">Sync paused</span>
|
|
138
|
+
<button class="mw3-sync-progress__resume">Resume</button>
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
const resumeBtn = this.element.querySelector('.mw3-sync-progress__resume');
|
|
142
|
+
if (resumeBtn) {
|
|
143
|
+
resumeBtn.onclick = () => this.indexer.sync.resume();
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
this.element.innerHTML = `
|
|
147
|
+
<span class="mw3-sync-progress__text">Initializing...</span>
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Inject default styles into document.
|
|
154
|
+
* Call once at app startup if not using custom CSS.
|
|
155
|
+
*/
|
|
156
|
+
static injectStyles() {
|
|
157
|
+
if (document.getElementById('mw3-sync-progress-styles')) return;
|
|
158
|
+
|
|
159
|
+
const style = document.createElement('style');
|
|
160
|
+
style.id = 'mw3-sync-progress-styles';
|
|
161
|
+
style.textContent = `
|
|
162
|
+
.mw3-sync-progress {
|
|
163
|
+
position: relative;
|
|
164
|
+
height: 24px;
|
|
165
|
+
background: var(--mw3-sync-bg, #f0f0f0);
|
|
166
|
+
border-radius: 4px;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
font-family: system-ui, sans-serif;
|
|
169
|
+
font-size: 12px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.mw3-sync-progress__bar {
|
|
173
|
+
position: absolute;
|
|
174
|
+
top: 0;
|
|
175
|
+
left: 0;
|
|
176
|
+
height: 100%;
|
|
177
|
+
background: var(--mw3-sync-bar, #4caf50);
|
|
178
|
+
transition: width 0.3s ease;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.mw3-sync-progress__text {
|
|
182
|
+
position: relative;
|
|
183
|
+
z-index: 1;
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
height: 100%;
|
|
188
|
+
color: var(--mw3-sync-text, #333);
|
|
189
|
+
padding: 0 8px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mw3-sync-progress--synced {
|
|
193
|
+
background: var(--mw3-sync-bar, #4caf50);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.mw3-sync-progress--synced .mw3-sync-progress__text {
|
|
197
|
+
color: white;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.mw3-sync-progress--error {
|
|
201
|
+
background: var(--mw3-sync-error, #f44336);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.mw3-sync-progress--error .mw3-sync-progress__text {
|
|
205
|
+
color: white;
|
|
206
|
+
justify-content: space-between;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.mw3-sync-progress__retry,
|
|
210
|
+
.mw3-sync-progress__resume {
|
|
211
|
+
background: rgba(255,255,255,0.2);
|
|
212
|
+
border: 1px solid rgba(255,255,255,0.4);
|
|
213
|
+
border-radius: 3px;
|
|
214
|
+
color: white;
|
|
215
|
+
padding: 2px 8px;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
font-size: 11px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.mw3-sync-progress__retry:hover,
|
|
221
|
+
.mw3-sync-progress__resume:hover {
|
|
222
|
+
background: rgba(255,255,255,0.3);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.mw3-sync-progress--paused {
|
|
226
|
+
background: var(--mw3-sync-paused, #ff9800);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.mw3-sync-progress--paused .mw3-sync-progress__text {
|
|
230
|
+
color: white;
|
|
231
|
+
justify-content: space-between;
|
|
232
|
+
}
|
|
233
|
+
`;
|
|
234
|
+
document.head.appendChild(style);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { SyncProgressBar };
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,10 @@ 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';
|
|
@@ -19,6 +23,8 @@ import { BondingCurve } from './components/BondingCurve/BondingCurve.js';
|
|
|
19
23
|
import { PriceDisplay } from './components/Display/PriceDisplay.js';
|
|
20
24
|
import { BalanceDisplay } from './components/Display/BalanceDisplay.js';
|
|
21
25
|
import { MessagePopup } from './components/Util/MessagePopup.js';
|
|
26
|
+
import { SyncProgressBar } from './components/SyncProgressBar/SyncProgressBar.js';
|
|
27
|
+
import { SettingsModal } from './components/SettingsModal/SettingsModal.js';
|
|
22
28
|
|
|
23
29
|
export {
|
|
24
30
|
// Services
|
|
@@ -27,6 +33,12 @@ export {
|
|
|
27
33
|
ContractCache,
|
|
28
34
|
PriceService,
|
|
29
35
|
IpfsService,
|
|
36
|
+
EventIndexer,
|
|
37
|
+
|
|
38
|
+
// Storage adapters and settings
|
|
39
|
+
IndexedDBAdapter,
|
|
40
|
+
MemoryAdapter,
|
|
41
|
+
IndexerSettings,
|
|
30
42
|
|
|
31
43
|
// UI Components
|
|
32
44
|
FloatingWalletButton,
|
|
@@ -41,5 +53,7 @@ export {
|
|
|
41
53
|
BondingCurve,
|
|
42
54
|
PriceDisplay,
|
|
43
55
|
BalanceDisplay,
|
|
44
|
-
MessagePopup
|
|
56
|
+
MessagePopup,
|
|
57
|
+
SyncProgressBar,
|
|
58
|
+
SettingsModal
|
|
45
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;
|