@nlabs/metropolisjs 1.0.7 → 1.1.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/CHANGELOG.md +7 -0
- package/README.md +128 -42
- package/docs/ACTIONS.md +116 -3
- package/docs/assets/metropolisjs-logo.png +0 -0
- package/docs/assets/metropolisjs-mark.svg +7 -0
- package/examples/crud-usage.tsx +1 -1
- package/examples/factory-pattern-usage.ts +85 -78
- package/examples/signup-error-handling.ts +25 -24
- package/factoryPatternGuide.md +90 -174
- package/lib/actions/awsRumActions/awsRumActions.d.ts +8 -1
- package/lib/actions/awsRumActions/awsRumActions.js +9 -5
- package/lib/actions/translationActions/translationActions.d.ts +2 -11
- package/lib/actions/translationActions/translationActions.js +2 -2
- package/lib/actions/userActions/userActions.d.ts +7 -5
- package/lib/actions/userActions/userActions.js +111 -64
- package/lib/index.d.ts +4 -9
- package/lib/index.js +19 -10
- package/lib/stores/tagStore.d.ts +1 -0
- package/lib/stores/tagStore.js +1 -1
- package/lib/stores/userStore.d.ts +1 -0
- package/lib/stores/userStore.js +2 -1
- package/lib/utils/actionFactory.d.ts +52 -6
- package/lib/utils/actionFactory.js +8 -6
- package/lib/utils/api.d.ts +7 -0
- package/lib/utils/api.js +18 -1
- package/lib/utils/baseActionFactory.d.ts +1 -1
- package/lib/utils/index.d.ts +1 -1
- package/lib/utils/index.js +2 -2
- package/lib/utils/session.d.ts +1 -1
- package/package.json +16 -14
- package/tsconfig.examples.json +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.7
|
|
4
|
+
|
|
5
|
+
- Flush pending RUM analytics with the Beacon API when a page is hidden or unloaded.
|
|
6
|
+
- Fall back to the normal unauthenticated RUM request when beacon delivery is unavailable or declined.
|
|
7
|
+
- Expose `flush({useBeacon: true})` and `rumBeaconRequest(...)` for explicit terminal delivery.
|
|
8
|
+
- Document RUM endpoint configuration, beacon lifecycle behavior, fallback semantics, and `useAwsRum()` usage.
|
|
9
|
+
|
|
3
10
|
## 1.0.3
|
|
4
11
|
|
|
5
12
|
- Send the public RUM `analyticsId` to Reaktor in the JSON mutation payload.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# MetropolisJS: Seamless Frontend-Backend Integration Framework
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img src="https://raw.githubusercontent.com/nitrogenlabs/metropolisjs/main/docs/assets/metropolisjs-
|
|
4
|
+
<img src="https://raw.githubusercontent.com/nitrogenlabs/metropolisjs/main/docs/assets/metropolisjs-mark.svg" alt="MetropolisJS winged V logo" width="432">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
> **The Ultimate Frontend Integration Library for Modern Web Applications**
|
|
@@ -155,7 +155,7 @@ The `Metropolis` component accepts three main props: `config`, `adapters`, and `
|
|
|
155
155
|
|
|
156
156
|
### RUM analytics
|
|
157
157
|
|
|
158
|
-
Configure the public analytics identifier returned by Reaktor under `app.rum.analyticsId`. MetropolisJS sends
|
|
158
|
+
Configure the public analytics identifier returned by Reaktor under `app.rum.analyticsId` and the collection endpoint under `app.api.endpoints.rum`. MetropolisJS sends the identifier in the JSON batch body; it is not placed in the URL or an authorization header.
|
|
159
159
|
|
|
160
160
|
```tsx
|
|
161
161
|
<Metropolis
|
|
@@ -163,9 +163,18 @@ Configure the public analytics identifier returned by Reaktor under `app.rum.ana
|
|
|
163
163
|
production: {
|
|
164
164
|
app: {
|
|
165
165
|
name: 'My App',
|
|
166
|
+
api: {
|
|
167
|
+
endpoints: {
|
|
168
|
+
rum: 'https://events.example.com/track'
|
|
169
|
+
}
|
|
170
|
+
},
|
|
166
171
|
rum: {
|
|
167
172
|
analyticsId: '00000000-0000-4000-8000-000000000000',
|
|
168
|
-
|
|
173
|
+
debounceMs: 250,
|
|
174
|
+
dedupeMs: 1000,
|
|
175
|
+
enabled: true,
|
|
176
|
+
respectPrivacySignals: true,
|
|
177
|
+
throttleMs: 1000
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
180
|
}
|
|
@@ -174,7 +183,47 @@ Configure the public analytics identifier returned by Reaktor under `app.rum.ana
|
|
|
174
183
|
</Metropolis>
|
|
175
184
|
```
|
|
176
185
|
|
|
177
|
-
`app.rum
|
|
186
|
+
Configure the endpoint at `app.api.endpoints.rum`. RUM delivery is unauthenticated and each batch contains `analyticsId` and up to 50 sanitized events.
|
|
187
|
+
|
|
188
|
+
#### Beacon delivery
|
|
189
|
+
|
|
190
|
+
When the page is hidden or receives `pagehide`, the `Metropolis` provider automatically flushes pending RUM events with `navigator.sendBeacon()`. This gives terminal analytics a chance to finish without delaying navigation or page shutdown.
|
|
191
|
+
|
|
192
|
+
Beacon delivery is best-effort:
|
|
193
|
+
|
|
194
|
+
- If `navigator.sendBeacon` is unavailable, throws, or declines the payload, MetropolisJS immediately falls back to its normal asynchronous RUM request.
|
|
195
|
+
- A batch accepted by `sendBeacon` is not sent again through the normal request path.
|
|
196
|
+
- Scheduled and explicit `flush()` calls continue to use the normal request path unless `useBeacon` is requested.
|
|
197
|
+
- Privacy signals and the `enabled` option are respected for both delivery paths.
|
|
198
|
+
|
|
199
|
+
Use the specialized hook when an application needs to track or flush events directly:
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import {useAwsRum} from '@nlabs/metropolisjs';
|
|
203
|
+
|
|
204
|
+
const SaveButton = () => {
|
|
205
|
+
const rum = useAwsRum();
|
|
206
|
+
|
|
207
|
+
const onSave = () => {
|
|
208
|
+
rum.track({
|
|
209
|
+
name: 'settings_saved',
|
|
210
|
+
path: '/settings',
|
|
211
|
+
properties: {section: 'profile'},
|
|
212
|
+
type: 'click'
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return <button onClick={onSave}>Save</button>;
|
|
217
|
+
};
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
For an application-controlled terminal flush, request beacon delivery explicitly:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
await rum.flush({useBeacon: true});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Calling `flush({useBeacon: true})` is safe in non-browser environments and older browsers because it falls back to the normal RUM request when the Beacon API cannot be used.
|
|
178
227
|
|
|
179
228
|
### Configuration Object
|
|
180
229
|
|
|
@@ -401,7 +450,7 @@ const LoginForm = () => {
|
|
|
401
450
|
|
|
402
451
|
const handleLogin = async () => {
|
|
403
452
|
try {
|
|
404
|
-
const session = await userActions.signIn(
|
|
453
|
+
const session = await userActions.signIn({password, username});
|
|
405
454
|
console.log('User logged in successfully!', session);
|
|
406
455
|
} catch (error) {
|
|
407
456
|
console.error('Login failed:', error);
|
|
@@ -427,6 +476,41 @@ const LoginForm = () => {
|
|
|
427
476
|
};
|
|
428
477
|
```
|
|
429
478
|
|
|
479
|
+
### Billing Setup Sessions
|
|
480
|
+
|
|
481
|
+
Billing cards are collected through a hosted setup session, so raw card details never pass through application code. Start the flow with an authenticated user action and redirect the browser to the returned checkout URL:
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
import {useUserActions} from '@nlabs/metropolisjs';
|
|
485
|
+
|
|
486
|
+
const AddBillingCardButton = () => {
|
|
487
|
+
const userActions = useUserActions();
|
|
488
|
+
|
|
489
|
+
const addBillingCard = async () => {
|
|
490
|
+
const returnUrl = `${window.location.origin}/settings/billing/complete`;
|
|
491
|
+
const checkoutUrl = await userActions.createBillingSetupSession(returnUrl);
|
|
492
|
+
window.location.assign(checkoutUrl);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
return <button onClick={addBillingCard}>Add billing card</button>;
|
|
496
|
+
};
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
On the return page, read the provider's setup-session identifier and complete the flow. The action returns the updated user, refreshes the matching session data, dispatches the standard user update event, and clears related user request caches:
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
const sessionId = new URLSearchParams(window.location.search).get('session_id');
|
|
503
|
+
|
|
504
|
+
if(sessionId) {
|
|
505
|
+
const user = await userActions.completeBillingSetupSession(sessionId, [
|
|
506
|
+
'stripeCardBrand',
|
|
507
|
+
'stripeCardLast4'
|
|
508
|
+
]);
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Use `deleteBillingCard()` to remove the saved billing method. Both completion and deletion return sanitized billing metadata; MetropolisJS does not accept raw card numbers or tokens.
|
|
513
|
+
|
|
430
514
|
### Real-Time Messaging
|
|
431
515
|
|
|
432
516
|
```tsx
|
|
@@ -505,6 +589,7 @@ MetropolisJS provides comprehensive actions for all your needs. Access them usin
|
|
|
505
589
|
|
|
506
590
|
### Specialized Hooks (Recommended)
|
|
507
591
|
|
|
592
|
+
- `useAwsRum()` - Analytics tracking, batching, and terminal beacon delivery
|
|
508
593
|
- `useUserActions()` - Authentication, personas, user management
|
|
509
594
|
- `useMessageActions()` - Real-time messaging and conversations
|
|
510
595
|
- `usePermissionActions()` - Permission and role management (RBAC)
|
|
@@ -547,21 +632,34 @@ MetropolisJS uses a **factory function pattern** for actions. This provides func
|
|
|
547
632
|
### Basic Usage
|
|
548
633
|
|
|
549
634
|
```typescript
|
|
550
|
-
import {createUserActions} from '
|
|
635
|
+
import {createAction, createActions, createUserActions} from '@nlabs/metropolisjs';
|
|
551
636
|
|
|
552
637
|
const userActions = createUserActions(flux);
|
|
553
|
-
const user = await userActions.
|
|
638
|
+
const user = await userActions.addUser(userData);
|
|
639
|
+
|
|
640
|
+
const postActions = createAction('post', flux);
|
|
641
|
+
const post = await postActions.add({content: 'Hello!'});
|
|
642
|
+
|
|
643
|
+
const actions = createActions(['user', 'post', 'message'], flux);
|
|
644
|
+
await actions.message.sendMessage({
|
|
645
|
+
content: 'Welcome!',
|
|
646
|
+
recipientId: user.userId
|
|
647
|
+
});
|
|
554
648
|
```
|
|
555
649
|
|
|
650
|
+
Factory results preserve their selected types. `createAction('post', flux)` returns `PostActions`, while `createActions(['user', 'post'], flux)` returns only typed `user` and `post` keys. `createAllActions(flux)` returns the complete `ActionMap`.
|
|
651
|
+
|
|
556
652
|
### Advanced Usage with Custom Adapters
|
|
557
653
|
|
|
558
654
|
#### Custom Validation Adapter
|
|
559
655
|
|
|
560
656
|
```typescript
|
|
657
|
+
import type {User} from '@nlabs/metropolisjs';
|
|
658
|
+
|
|
561
659
|
// Custom adapter that extends default behavior
|
|
562
|
-
const customUserAdapter = (input: unknown
|
|
660
|
+
const customUserAdapter = (input: unknown): User => {
|
|
563
661
|
// input is already validated by default adapter
|
|
564
|
-
const user = input as
|
|
662
|
+
const user = input as User;
|
|
565
663
|
|
|
566
664
|
// Add business-specific validation
|
|
567
665
|
if (user.email && !user.email.includes('@company.com')) {
|
|
@@ -572,7 +670,7 @@ const customUserAdapter = (input: unknown, options?: UserAdapterOptions) => {
|
|
|
572
670
|
return {
|
|
573
671
|
...user,
|
|
574
672
|
fullName: `${user.firstName || ''} ${user.lastName || ''}`.trim(),
|
|
575
|
-
isAdmin: user.userAccess >= 3
|
|
673
|
+
isAdmin: (user.userAccess || 0) >= 3
|
|
576
674
|
};
|
|
577
675
|
};
|
|
578
676
|
|
|
@@ -638,31 +736,7 @@ interface AdapterOptions {
|
|
|
638
736
|
}
|
|
639
737
|
```
|
|
640
738
|
|
|
641
|
-
###
|
|
642
|
-
|
|
643
|
-
#### Step 1: Update Imports
|
|
644
|
-
|
|
645
|
-
```typescript
|
|
646
|
-
// Old
|
|
647
|
-
import {userActions} from '../actions/userActions';
|
|
648
|
-
|
|
649
|
-
// New
|
|
650
|
-
import {createUserActions} from '../actions/userActions';
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
#### Step 2: Update Instantiation
|
|
654
|
-
|
|
655
|
-
```typescript
|
|
656
|
-
// Old
|
|
657
|
-
const userActions = new userActions(flux, customAdapter);
|
|
658
|
-
|
|
659
|
-
// New
|
|
660
|
-
const userActions = createUserActions(flux, {
|
|
661
|
-
userAdapter: customAdapter
|
|
662
|
-
});
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
#### Step 3: Using Actions in Components
|
|
739
|
+
### Using Actions in Components
|
|
666
740
|
|
|
667
741
|
The recommended approach is to use specialized hooks:
|
|
668
742
|
|
|
@@ -715,11 +789,11 @@ const apiUrl = config.app?.api?.url || '';
|
|
|
715
789
|
#### Unit Testing Actions
|
|
716
790
|
|
|
717
791
|
```typescript
|
|
718
|
-
import {createUserActions} from '@nlabs/metropolisjs';
|
|
792
|
+
import {createUserActions, type UserActions} from '@nlabs/metropolisjs';
|
|
719
793
|
|
|
720
794
|
describe('userActions', () => {
|
|
721
795
|
let flux: FluxFramework;
|
|
722
|
-
let userActions:
|
|
796
|
+
let userActions: UserActions;
|
|
723
797
|
|
|
724
798
|
beforeEach(() => {
|
|
725
799
|
flux = createMockFlux();
|
|
@@ -732,7 +806,7 @@ describe('userActions', () => {
|
|
|
732
806
|
|
|
733
807
|
it('should add user with validation', async () => {
|
|
734
808
|
const userData = {username: 'test', email: 'test@example.com'};
|
|
735
|
-
const result = await userActions.
|
|
809
|
+
const result = await userActions.addUser(userData);
|
|
736
810
|
expect(result).toBeDefined();
|
|
737
811
|
});
|
|
738
812
|
});
|
|
@@ -741,7 +815,7 @@ describe('userActions', () => {
|
|
|
741
815
|
#### Testing with Custom Adapters
|
|
742
816
|
|
|
743
817
|
```typescript
|
|
744
|
-
const mockAdapter =
|
|
818
|
+
const mockAdapter = vi.fn((input) => ({
|
|
745
819
|
...input,
|
|
746
820
|
validated: true
|
|
747
821
|
}));
|
|
@@ -1217,8 +1291,8 @@ For more detailed examples, see [`examples/permission-system-usage.tsx`](./examp
|
|
|
1217
1291
|
### Prerequisites
|
|
1218
1292
|
|
|
1219
1293
|
- Node.js 16+
|
|
1220
|
-
- React
|
|
1221
|
-
- TypeScript
|
|
1294
|
+
- React 19+
|
|
1295
|
+
- TypeScript 7+
|
|
1222
1296
|
|
|
1223
1297
|
### Full Installation
|
|
1224
1298
|
|
|
@@ -1242,7 +1316,18 @@ Configure your environment-specific settings in the `config` prop of the `Metrop
|
|
|
1242
1316
|
|
|
1243
1317
|
## Contributing
|
|
1244
1318
|
|
|
1245
|
-
|
|
1319
|
+
Before opening a pull request, run the same quality gates used for source, tests, examples, and the published declarations:
|
|
1320
|
+
|
|
1321
|
+
```bash
|
|
1322
|
+
npm run lint
|
|
1323
|
+
npm run typecheck
|
|
1324
|
+
npm test
|
|
1325
|
+
npm run build
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
`npm run typecheck` checks the production source, unit and integration tests, lint inputs, and every file under `examples/`.
|
|
1329
|
+
|
|
1330
|
+
To contribute:
|
|
1246
1331
|
|
|
1247
1332
|
1. **Fork** the repository
|
|
1248
1333
|
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
|
@@ -1256,6 +1341,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
1256
1341
|
|
|
1257
1342
|
## Additional Documentation
|
|
1258
1343
|
|
|
1344
|
+
- **[Actions Reference](./docs/ACTIONS.md)** - Action families, hooks, factories, and RUM beacon delivery
|
|
1259
1345
|
- **[Factory Pattern Guide](./factoryPatternGuide.md)** - Detailed guide on using the factory pattern
|
|
1260
1346
|
- **[Architecture Analysis](./ARCHITECTURE_ANALYSIS.md)** - Deep dive into the architecture
|
|
1261
1347
|
- **[Changelog](./CHANGELOG.md)** - Complete list of changes and improvements
|
package/docs/ACTIONS.md
CHANGED
|
@@ -7,8 +7,9 @@ This reference documents every action family exposed by MetropolisJS, how to acc
|
|
|
7
7
|
Use specialized hooks in React components:
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
|
-
import {useUserActions, usePostActions, useMessageActions, useRestActions} from '@nlabs/metropolisjs';
|
|
10
|
+
import {useAwsRum, useUserActions, usePostActions, useMessageActions, useRestActions} from '@nlabs/metropolisjs';
|
|
11
11
|
|
|
12
|
+
const rum = useAwsRum();
|
|
12
13
|
const userActions = useUserActions();
|
|
13
14
|
const postActions = usePostActions();
|
|
14
15
|
const messageActions = useMessageActions();
|
|
@@ -25,6 +26,26 @@ const postActions = createPostActions(flux);
|
|
|
25
26
|
const restActions = createRestActions(flux);
|
|
26
27
|
```
|
|
27
28
|
|
|
29
|
+
### Typed Consolidated Factories
|
|
30
|
+
|
|
31
|
+
The consolidated factories preserve the selected action types without casts:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {createAction, createActions, createAllActions} from '@nlabs/metropolisjs';
|
|
35
|
+
|
|
36
|
+
const userActions = createAction('user', flux);
|
|
37
|
+
await userActions.addUser({username: 'ada'});
|
|
38
|
+
|
|
39
|
+
const actions = createActions(['user', 'post', 'message'], flux);
|
|
40
|
+
await actions.post.add({content: 'Hello!'});
|
|
41
|
+
await actions.message.sendMessage({content: 'Welcome!'});
|
|
42
|
+
|
|
43
|
+
const allActions = createAllActions(flux);
|
|
44
|
+
await allActions.permission.list();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`createAction()` maps its key to the corresponding action interface, `createActions()` returns exactly the requested keys, and `createAllActions()` returns the complete exported `ActionMap`.
|
|
48
|
+
|
|
28
49
|
## Action Families
|
|
29
50
|
|
|
30
51
|
Each row links to:
|
|
@@ -37,7 +58,8 @@ Each row links to:
|
|
|
37
58
|
These action families are available through specialized hooks when present, `useMetropolis([...])`, `createAction(...)`, `createActions(...)`, and direct creators.
|
|
38
59
|
|
|
39
60
|
| Family | Hook | Factory Key | Creator | Typical Methods | Source |
|
|
40
|
-
| --- | --- | --- | --- | --- |
|
|
61
|
+
| --- | --- | --- | --- | --- | --- |
|
|
62
|
+
| AWS RUM | `useAwsRum` | `awsRum` | `createAwsRumActions` | `track`, `flush`, `destroy` | [awsRumActions.ts](../src/actions/awsRumActions/awsRumActions.ts) |
|
|
41
63
|
| Content | `useContentActions` | `content` | `createContentActions` | `add`, `itemById`, `itemByKey`, `listByCategory`, `list`, `update`, `delete` | [contentActions.ts](../src/actions/contentActions/contentActions.ts) |
|
|
42
64
|
| CRM | `useCrmActions` | `crm` | `createCrmActions` | `mailingLists`, `createMailingList`, `createSupportTicket`, `supportTickets`, `customerOrdersByUser` | [crmActions.ts](../src/actions/crmActions/crmActions.ts) |
|
|
43
65
|
| Event | `useEventActions` | `event` | `createEventActions` | `addEvent`, `getEvent`, `getEventsByTags`, `getEventsByReactions`, `updateEvent`, `deleteEvent` | [eventActions.ts](../src/actions/eventActions/eventActions.ts) |
|
|
@@ -54,7 +76,7 @@ These action families are available through specialized hooks when present, `use
|
|
|
54
76
|
| Subscription | `useSubscriptionActions` | `subscription` | `createSubscriptionActions` | `addPlan`, `getPlanByItem`, `addSubscription`, `getSubscriptionByItem`, `getSubscriptionListByUser`, `deleteSubscription` | [subscriptionActions.ts](../src/actions/subscriptionActions/subscriptionActions.ts) |
|
|
55
77
|
| Tag | `useTagActions` | `tag` | `createTagActions` | `addTag`, `addTagToItem`, `getTags`, `updateTag`, `deleteTag`, `deleteTagFromItem` | [tagActions.ts](../src/actions/tagActions/tagActions.ts) |
|
|
56
78
|
| Translation | `useTranslationActions` | `translation` | `createTranslationActions` | `addTranslations`, `getTranslation`, `getTranslations`, `hasTranslation`, `queueTranslationKey`, `processPendingTranslations` | [translationActions.ts](../src/actions/translationActions/translationActions.ts) |
|
|
57
|
-
| User | `useUserActions` | `user` | `createUserActions` | `signIn`, `signUp`, `session`, `
|
|
79
|
+
| User | `useUserActions` | `user` | `createUserActions` | `signIn`, `signUp`, `session`, `createBillingSetupSession`, `completeBillingSetupSession`, `deleteBillingCard`, `itemById`, `updateUser` | [userActions.ts](../src/actions/userActions/userActions.ts) |
|
|
58
80
|
| Video | `useVideoActions` | `video` | `createVideoActions` | `add`, `itemById`, `list`, `update`, `delete` | [videoActions.ts](../src/actions/videoActions/videoActions.ts) |
|
|
59
81
|
| Websocket | `useWebsocketActions` | `websocket` | `createWebsocketActions` | `wsInit`, `wsSend`, `onOpen`, `onReceive`, `onClose`, `onError` | [websocketActions.ts](../src/actions/websocketActions/websocketActions.ts) |
|
|
60
82
|
|
|
@@ -68,6 +90,85 @@ These creators are exported directly from MetropolisJS, but are not currently pa
|
|
|
68
90
|
| Connection | `createConnectionActions` | `addConnection`, `getConnections`, `removeConnection` | [connectionActions.ts](../src/actions/connectionActions/connectionActions.ts) |
|
|
69
91
|
| Conversation | `createConversationActions` | `add`, `itemById`, `list`, `update`, `delete` | [conversationActions.ts](../src/actions/conversationActions/conversationActions.ts) |
|
|
70
92
|
|
|
93
|
+
## AWS RUM Actions
|
|
94
|
+
|
|
95
|
+
Use `useAwsRum()` to queue sanitized analytics events and flush them to the configured RUM endpoint:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import {useAwsRum} from '@nlabs/metropolisjs';
|
|
99
|
+
|
|
100
|
+
const AnalyticsExample = () => {
|
|
101
|
+
const rum = useAwsRum();
|
|
102
|
+
|
|
103
|
+
const trackCheckout = () => {
|
|
104
|
+
rum.track({
|
|
105
|
+
name: 'checkout_started',
|
|
106
|
+
path: '/checkout',
|
|
107
|
+
properties: {source: 'cart'},
|
|
108
|
+
type: 'click'
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return <button onClick={trackCheckout}>Checkout</button>;
|
|
113
|
+
};
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Configure the action through the `Metropolis` provider:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<Metropolis
|
|
120
|
+
config={{
|
|
121
|
+
production: {
|
|
122
|
+
app: {
|
|
123
|
+
api: {
|
|
124
|
+
endpoints: {
|
|
125
|
+
rum: 'https://events.example.com/track'
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
rum: {
|
|
129
|
+
analyticsId: 'my-public-analytics-id',
|
|
130
|
+
enabled: true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<App />
|
|
137
|
+
</Metropolis>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`flush()` uses the normal asynchronous request path. `flush({useBeacon: true})` first asks the browser to queue the JSON batch with `navigator.sendBeacon()`, then falls back to the normal request if the Beacon API is unavailable or declines the payload.
|
|
141
|
+
|
|
142
|
+
The `Metropolis` provider requests beacon delivery automatically on `pagehide` and when `document.visibilityState` changes to `hidden`. A batch accepted by the Beacon API is not submitted a second time. All RUM delivery remains subject to `enabled`, `respectPrivacySignals`, batching, throttling, deduplication, and event sanitization.
|
|
143
|
+
|
|
144
|
+
## User Billing Setup Sessions
|
|
145
|
+
|
|
146
|
+
Use the authenticated user actions to collect a billing method through the hosted setup flow:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import {createUserActions} from '@nlabs/metropolisjs';
|
|
150
|
+
|
|
151
|
+
const userActions = createUserActions(flux);
|
|
152
|
+
const checkoutUrl = await userActions.createBillingSetupSession(
|
|
153
|
+
'https://app.example.com/settings/billing/complete'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
window.location.assign(checkoutUrl);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
After the billing provider redirects back, complete the session with its identifier:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const user = await userActions.completeBillingSetupSession(
|
|
163
|
+
setupSessionId,
|
|
164
|
+
['stripeCardBrand', 'stripeCardLast4']
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`createBillingSetupSession(returnUrl)` validates the return URL and resolves to the hosted checkout URL. `completeBillingSetupSession(sessionId, userProps?, requestOptions?)` validates the identifier, returns the updated user, synchronizes the active session when it belongs to that user, dispatches `USER_UPDATE_ITEM_SUCCESS`, and clears related request caches. `deleteBillingCard(userProps?, requestOptions?)` removes the stored method through the same authenticated action family.
|
|
169
|
+
|
|
170
|
+
These APIs exchange setup-session identifiers and sanitized billing metadata only. They do not accept raw card details.
|
|
171
|
+
|
|
71
172
|
## REST Actions
|
|
72
173
|
|
|
73
174
|
Use REST actions for external APIs that are not represented in Reaktor. REST actions delegate to `@nlabs/rip-hunter`, share Metropolis network/session handling, and can target either a configured endpoint key or an absolute URL.
|
|
@@ -120,6 +221,18 @@ All action creators are re-exported from:
|
|
|
120
221
|
|
|
121
222
|
- [src/actions/index.ts](../src/actions/index.ts)
|
|
122
223
|
|
|
224
|
+
The package root also exports every creator, action interface, `ActionMap`, and the consolidated factory functions.
|
|
225
|
+
|
|
123
226
|
All specialized hooks are exposed from:
|
|
124
227
|
|
|
125
228
|
- [src/utils/useMetropolis.ts](../src/utils/useMetropolis.ts)
|
|
229
|
+
|
|
230
|
+
## Development Type Checks
|
|
231
|
+
|
|
232
|
+
Run the complete TypeScript gate with:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm run typecheck
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
This validates the production source, tests, lint inputs, and examples. Run `npm run lint`, `npm test`, and `npm run build` before publishing.
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">MetropolisJS</title>
|
|
3
|
+
<desc id="desc">Purple winged V mark representing the connection between frontend and backend systems.</desc>
|
|
4
|
+
<path
|
|
5
|
+
d="M600 280C585 280 575 270 565 250C525 170 480 100 415 70C318 25 205 60 125 155C82 206 55 278 40 340C95 250 170 205 245 205C365 205 470 360 555 505C570 532 582 548 600 548C618 548 630 532 645 505C730 360 835 205 955 205C1030 205 1105 250 1160 340C1145 278 1118 206 1075 155C995 60 882 25 785 70C720 100 675 170 635 250C625 270 615 280 600 280Z"
|
|
6
|
+
fill="#712CF9"/>
|
|
7
|
+
</svg>
|
package/examples/crud-usage.tsx
CHANGED
|
@@ -700,7 +700,7 @@ export const EventManagerExample = () => {
|
|
|
700
700
|
<p>
|
|
701
701
|
{new Date(event.startDate!).toLocaleString()} - {new Date(event.endDate!).toLocaleString()}
|
|
702
702
|
</p>
|
|
703
|
-
<p>Location: {event.location}</p>
|
|
703
|
+
<p>Location: {typeof event.location === 'string' ? event.location : event.location?.address}</p>
|
|
704
704
|
<button onClick={() => handleDeleteEvent(event.eventId!)}>Delete</button>
|
|
705
705
|
</div>
|
|
706
706
|
))}
|