@adobe-commerce/elsie 1.4.1-alpha007 → 1.4.1-alpha009
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/bin/builders/gql/index.js +8 -4
- package/bin/builders/gql/validate.js +11 -10
- package/package.json +1 -1
- package/src/docs/API/event-bus.mdx +17 -234
|
@@ -77,20 +77,24 @@ module.exports = async function generateResourceBuilder(yargs) {
|
|
|
77
77
|
alias: 's',
|
|
78
78
|
describe: 'Path to the source code containing GraphQL operations',
|
|
79
79
|
type: 'array',
|
|
80
|
-
string: true,
|
|
81
80
|
demandOption: true,
|
|
82
81
|
})
|
|
82
|
+
.option('excluded', {
|
|
83
|
+
alias: 'x',
|
|
84
|
+
describe: 'Paths to exclude from validation',
|
|
85
|
+
type: 'array',
|
|
86
|
+
demandOption: false,
|
|
87
|
+
})
|
|
83
88
|
.option('endpoints', {
|
|
84
89
|
alias: 'e',
|
|
85
90
|
describe: 'Path to GraphQL endpoints',
|
|
86
91
|
type: 'array',
|
|
87
|
-
string: true,
|
|
88
92
|
demandOption: true,
|
|
89
93
|
});
|
|
90
94
|
},
|
|
91
95
|
async (argv) => {
|
|
92
|
-
const { source, endpoints } = argv;
|
|
93
|
-
await validate(source, endpoints);
|
|
96
|
+
const { source, excluded, endpoints } = argv;
|
|
97
|
+
await validate(source, endpoints, excluded);
|
|
94
98
|
},
|
|
95
99
|
)
|
|
96
100
|
.demandCommand(1, 1, 'choose a command: types, mocks or validate');
|
|
@@ -5,17 +5,20 @@ const parser = require('@babel/parser');
|
|
|
5
5
|
const traverse = require('@babel/traverse');
|
|
6
6
|
const { getIntrospectionQuery, buildClientSchema, parse, validate } = require('graphql');
|
|
7
7
|
|
|
8
|
-
async function walk(dir, collected = []) {
|
|
8
|
+
async function walk(dir, excludedPaths = [], collected = []) {
|
|
9
|
+
if (excludedPaths.includes(dir)) return collected;
|
|
10
|
+
|
|
9
11
|
const dirents = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
10
12
|
|
|
11
13
|
for (const d of dirents) {
|
|
12
14
|
const full = path.resolve(dir, d.name);
|
|
13
15
|
|
|
14
16
|
if (d.isDirectory()) {
|
|
15
|
-
|
|
17
|
+
if (excludedPaths.includes(full)) continue;
|
|
16
18
|
if (d.name === 'node_modules' || d.name.startsWith('.')) continue;
|
|
17
|
-
await walk(full, collected);
|
|
19
|
+
await walk(full, excludedPaths, collected);
|
|
18
20
|
} else if (/\.(c?m?js|ts|tsx)$/.test(d.name)) {
|
|
21
|
+
if (excludedPaths.includes(full)) continue;
|
|
19
22
|
collected.push(full);
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -97,10 +100,10 @@ async function validateGqlOperations(endpoint, operation) {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
async function getAllOperations(directories) {
|
|
103
|
+
async function getAllOperations(directories, excludedPaths = []) {
|
|
101
104
|
let fullContent = '';
|
|
102
105
|
for (const directory of directories) {
|
|
103
|
-
const files = await walk(path.resolve(directory));
|
|
106
|
+
const files = await walk(path.resolve(directory), excludedPaths.map(p => path.resolve(p)));
|
|
104
107
|
for (const f of files) {
|
|
105
108
|
const code = await fsPromises.readFile(f, 'utf8');
|
|
106
109
|
|
|
@@ -120,11 +123,9 @@ async function getAllOperations(directories) {
|
|
|
120
123
|
return fullContent;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
module.exports = async function main(sources, endpoints) {
|
|
126
|
+
module.exports = async function main(sources, endpoints, excluded) {
|
|
126
127
|
for (const endpoint of endpoints) {
|
|
127
|
-
const operations = await getAllOperations(sources);
|
|
128
|
+
const operations = await getAllOperations(sources, excluded);
|
|
128
129
|
if (!operations) {
|
|
129
130
|
console.error('No GraphQL operations found in the specified directories.');
|
|
130
131
|
process.exitCode = 0;
|
|
@@ -132,4 +133,4 @@ module.exports = async function main(sources, endpoints) {
|
|
|
132
133
|
}
|
|
133
134
|
await validateGqlOperations(endpoint, operations);
|
|
134
135
|
}
|
|
135
|
-
}
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -5,265 +5,48 @@ import { Meta, Unstyled } from '@storybook/blocks';
|
|
|
5
5
|
|
|
6
6
|
# Event Bus
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Import
|
|
11
|
-
|
|
12
|
-
From drop-in project using the SDK
|
|
8
|
+
## Usage
|
|
13
9
|
|
|
14
10
|
```ts
|
|
11
|
+
// from drop-in project (SDK)
|
|
15
12
|
import { events } from '@adobe-commerce/elsie/lib';
|
|
16
|
-
```
|
|
17
|
-
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```js
|
|
14
|
+
// from host site
|
|
22
15
|
import { events } from '@dropins/tools/event-bus.js';
|
|
23
16
|
```
|
|
24
17
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
### Subscribe to Events
|
|
28
|
-
|
|
29
|
-
Subscribe to events and receive notifications when they occur.
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
const eventListener = events.on('<event>', (payload) => {
|
|
33
|
-
// Handle the event payload
|
|
34
|
-
console.log('Event received:', payload);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Stop listening to the event
|
|
38
|
-
eventListener.off();
|
|
39
|
-
```
|
|
18
|
+
## Methods
|
|
40
19
|
|
|
41
|
-
|
|
20
|
+
### Listener
|
|
42
21
|
```ts
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (cartData) {
|
|
46
|
-
console.log(`Cart has ${cartData.totalQuantity} items`);
|
|
47
|
-
updateCartUI(cartData);
|
|
48
|
-
} else {
|
|
49
|
-
console.log('Cart is empty');
|
|
50
|
-
showEmptyCart();
|
|
51
|
-
}
|
|
22
|
+
const onEvent = events.on('<event>', (payload) => {
|
|
23
|
+
//...handle payload
|
|
52
24
|
});
|
|
53
25
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Emit Events
|
|
59
|
-
|
|
60
|
-
Broadcast events to all listeners across your application.
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
events.emit('<event>', payload);
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**Examples:**
|
|
67
|
-
```ts
|
|
68
|
-
// Emit cart data
|
|
69
|
-
const cartData = {
|
|
70
|
-
id: 'cart-123',
|
|
71
|
-
totalQuantity: 2,
|
|
72
|
-
items: [
|
|
73
|
-
{ uid: 'item-1', quantity: 1, sku: 'PROD-001', name: 'Product Name' }
|
|
74
|
-
]
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
events.emit('cart/data', cartData);
|
|
26
|
+
// Stop listening to event
|
|
27
|
+
onEvent.off();
|
|
78
28
|
```
|
|
79
29
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
Retrieve the most recent payload for a specific event.
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
const lastPayload = events.lastPayload('<event>');
|
|
86
|
-
```
|
|
30
|
+
### Emit
|
|
87
31
|
|
|
88
|
-
**Example:**
|
|
89
32
|
```ts
|
|
90
|
-
|
|
91
|
-
const currentCart = events.lastPayload('cart/data');
|
|
92
|
-
|
|
93
|
-
if (currentCart) {
|
|
94
|
-
console.log('Current cart total:', currentCart.totalQuantity);
|
|
95
|
-
}
|
|
33
|
+
events.emit('<event>', <payload>);
|
|
96
34
|
```
|
|
97
35
|
|
|
98
|
-
###
|
|
99
|
-
|
|
100
|
-
Turn on console logging to debug event flow.
|
|
36
|
+
### Logging
|
|
101
37
|
|
|
102
38
|
```ts
|
|
103
|
-
// Enable logging
|
|
39
|
+
// Enable logging
|
|
104
40
|
events.enableLogger(true);
|
|
105
|
-
```
|
|
106
41
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
### Eager Loading
|
|
110
|
-
|
|
111
|
-
Execute the event handler immediately with the last known payload when subscribing. This is useful for getting the current state without waiting for the next event.
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
// Handler will execute immediately if there's a previous payload
|
|
115
|
-
const listener = events.on('cart/data', (cartData) => {
|
|
116
|
-
console.log('Cart data received:', cartData);
|
|
117
|
-
}, { eager: true });
|
|
42
|
+
// Disable logging
|
|
43
|
+
events.enableLogger(false);
|
|
118
44
|
```
|
|
119
45
|
|
|
120
|
-
|
|
121
|
-
- Initialize UI components with current state
|
|
122
|
-
- Avoid waiting for the first event emission
|
|
123
|
-
- Ensure components have the latest data on mount
|
|
124
|
-
|
|
125
|
-
### Event Scoping
|
|
126
|
-
|
|
127
|
-
Create namespaced events to avoid conflicts between different parts of your application.
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
// Subscribe to a scoped event
|
|
131
|
-
const scopedListener = events.on('data/update', (data) => {
|
|
132
|
-
console.log('Scoped data received:', data);
|
|
133
|
-
}, { scope: 'feature-a' });
|
|
134
|
-
|
|
135
|
-
// Emit a scoped event
|
|
136
|
-
events.emit('data/update', payload, { scope: 'feature-a' });
|
|
137
|
-
|
|
138
|
-
// Get last payload for a scoped event
|
|
139
|
-
const lastScopedData = events.lastPayload('data/update', { scope: 'feature-a' });
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**Scoped Event Names:**
|
|
143
|
-
When using scopes, the actual event name becomes `scope/event`. For example:
|
|
144
|
-
- `'feature-a/data/update'` instead of `'data/update'`
|
|
145
|
-
- `'module-b/user/action'` instead of `'user/action'`
|
|
146
|
-
|
|
147
|
-
**Use Cases:**
|
|
148
|
-
- Separate different features or modules
|
|
149
|
-
- Different contexts within the same application
|
|
150
|
-
- Component-specific event handling
|
|
151
|
-
|
|
152
|
-
### Combining Options
|
|
153
|
-
|
|
154
|
-
Use both eager loading and scoping together for powerful event handling.
|
|
46
|
+
### Get Latest Payload
|
|
155
47
|
|
|
156
48
|
```ts
|
|
157
|
-
|
|
158
|
-
const listener = events.on('locale', (locale) => {
|
|
159
|
-
console.log('Current locale:', locale);
|
|
160
|
-
}, {
|
|
161
|
-
eager: true,
|
|
162
|
-
scope: 'user-preferences'
|
|
163
|
-
});
|
|
49
|
+
events.lastPayload('<event>'): EventPayload | undefined;
|
|
164
50
|
```
|
|
165
51
|
|
|
166
|
-
## Event-Driven Drop-ins
|
|
167
|
-
|
|
168
|
-
The Event Bus enables drop-ins to be truly event-driven, allowing for loose coupling between components and seamless communication across the application.
|
|
169
|
-
|
|
170
|
-
### Container-to-Container Communication
|
|
171
|
-
|
|
172
|
-
Containers can react to changes from other Containers, enabling complex interactions without direct dependencies.
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
// Product Container: Emits when a product is added to cart
|
|
176
|
-
function ProductContainer() {
|
|
177
|
-
const handleAddToCart = (product) => {
|
|
178
|
-
// Add to cart logic...
|
|
179
|
-
|
|
180
|
-
// Notify other containers about the cart change
|
|
181
|
-
events.emit('cart/data', updatedCartData);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
return (
|
|
185
|
-
<button onClick={() => handleAddToCart(product)}>
|
|
186
|
-
Add to Cart
|
|
187
|
-
</button>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Cart Container: Reacts to cart changes from any source
|
|
192
|
-
function CartContainer() {
|
|
193
|
-
useEffect(() => {
|
|
194
|
-
const cartListener = events.on('cart/data', (cartData) => {
|
|
195
|
-
updateCartDisplay(cartData);
|
|
196
|
-
updateCartBadge(cartData.totalQuantity);
|
|
197
|
-
}, { eager: true });
|
|
198
|
-
|
|
199
|
-
return () => cartListener.off();
|
|
200
|
-
}, []);
|
|
201
|
-
|
|
202
|
-
return <CartDisplay />;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Mini Cart Container: Also reacts to the same cart changes
|
|
206
|
-
function MiniCartContainer() {
|
|
207
|
-
useEffect(() => {
|
|
208
|
-
const cartListener = events.on('cart/data', (cartData) => {
|
|
209
|
-
updateMiniCart(cartData);
|
|
210
|
-
}, { eager: true });
|
|
211
|
-
|
|
212
|
-
return () => cartListener.off();
|
|
213
|
-
}, []);
|
|
214
|
-
|
|
215
|
-
return <MiniCart />;
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Storefront Communication
|
|
220
|
-
|
|
221
|
-
Drop-ins can communicate data changes to the storefront, enabling seamless integration with the host application.
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
// Authentication Container: Notifies storefront of login/logout
|
|
225
|
-
function AuthContainer() {
|
|
226
|
-
const handleLogin = (userData) => {
|
|
227
|
-
// Login logic...
|
|
228
|
-
|
|
229
|
-
// Notify storefront of authentication change
|
|
230
|
-
events.emit('authenticated', true);
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const handleLogout = () => {
|
|
234
|
-
// Logout logic...
|
|
235
|
-
|
|
236
|
-
// Notify storefront of authentication change
|
|
237
|
-
events.emit('authenticated', false);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
return <AuthForm onLogin={handleLogin} onLogout={handleLogout} />;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Storefront can listen for authentication changes
|
|
244
|
-
// This would be in the host application
|
|
245
|
-
const authListener = events.on('authenticated', (isAuthenticated) => {
|
|
246
|
-
if (isAuthenticated) {
|
|
247
|
-
showUserMenu();
|
|
248
|
-
enableCheckout();
|
|
249
|
-
} else {
|
|
250
|
-
hideUserMenu();
|
|
251
|
-
disableCheckout();
|
|
252
|
-
}
|
|
253
|
-
}, { eager: true });
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
## Best Practices
|
|
259
|
-
|
|
260
|
-
1. **Always unsubscribe** from events when components unmount to prevent memory leaks
|
|
261
|
-
2. **Use scopes** to organize events by feature or component
|
|
262
|
-
3. **Enable eager loading** when you need immediate access to current state
|
|
263
|
-
4. **Use descriptive event names** that clearly indicate what data they contain
|
|
264
|
-
5. **Handle null/undefined payloads** gracefully in your event handlers
|
|
265
|
-
6. **Enable logging during development** to debug event flow
|
|
266
|
-
7. **Keep event payloads lightweight** to avoid performance issues
|
|
267
|
-
8. **Document your event contracts** so other developers know what to expect
|
|
268
|
-
|
|
269
52
|
</Unstyled>
|