@nlabs/metropolisjs 1.0.8 → 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/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-logo.png" alt="MetropolisJS logo" width="432">
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**
@@ -183,7 +183,7 @@ Configure the public analytics identifier returned by Reaktor under `app.rum.ana
183
183
  </Metropolis>
184
184
  ```
185
185
 
186
- The endpoint is resolved from `app.api.endpoints.rum`, falling back to `app.api.rum`. RUM delivery is unauthenticated and each batch contains `analyticsId` and up to 50 sanitized events.
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
187
 
188
188
  #### Beacon delivery
189
189
 
@@ -450,7 +450,7 @@ const LoginForm = () => {
450
450
 
451
451
  const handleLogin = async () => {
452
452
  try {
453
- const session = await userActions.signIn(username, password);
453
+ const session = await userActions.signIn({password, username});
454
454
  console.log('User logged in successfully!', session);
455
455
  } catch (error) {
456
456
  console.error('Login failed:', error);
@@ -476,6 +476,41 @@ const LoginForm = () => {
476
476
  };
477
477
  ```
478
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
+
479
514
  ### Real-Time Messaging
480
515
 
481
516
  ```tsx
@@ -597,21 +632,34 @@ MetropolisJS uses a **factory function pattern** for actions. This provides func
597
632
  ### Basic Usage
598
633
 
599
634
  ```typescript
600
- import {createUserActions} from '../actions/userActions';
635
+ import {createAction, createActions, createUserActions} from '@nlabs/metropolisjs';
601
636
 
602
637
  const userActions = createUserActions(flux);
603
- const user = await userActions.add(userData);
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
+ });
604
648
  ```
605
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
+
606
652
  ### Advanced Usage with Custom Adapters
607
653
 
608
654
  #### Custom Validation Adapter
609
655
 
610
656
  ```typescript
657
+ import type {User} from '@nlabs/metropolisjs';
658
+
611
659
  // Custom adapter that extends default behavior
612
- const customUserAdapter = (input: unknown, options?: UserAdapterOptions) => {
660
+ const customUserAdapter = (input: unknown): User => {
613
661
  // input is already validated by default adapter
614
- const user = input as any;
662
+ const user = input as User;
615
663
 
616
664
  // Add business-specific validation
617
665
  if (user.email && !user.email.includes('@company.com')) {
@@ -622,7 +670,7 @@ const customUserAdapter = (input: unknown, options?: UserAdapterOptions) => {
622
670
  return {
623
671
  ...user,
624
672
  fullName: `${user.firstName || ''} ${user.lastName || ''}`.trim(),
625
- isAdmin: user.userAccess >= 3
673
+ isAdmin: (user.userAccess || 0) >= 3
626
674
  };
627
675
  };
628
676
 
@@ -688,31 +736,7 @@ interface AdapterOptions {
688
736
  }
689
737
  ```
690
738
 
691
- ### Migration Guide
692
-
693
- #### Step 1: Update Imports
694
-
695
- ```typescript
696
- // Old
697
- import {userActions} from '../actions/userActions';
698
-
699
- // New
700
- import {createUserActions} from '../actions/userActions';
701
- ```
702
-
703
- #### Step 2: Update Instantiation
704
-
705
- ```typescript
706
- // Old
707
- const userActions = new userActions(flux, customAdapter);
708
-
709
- // New
710
- const userActions = createUserActions(flux, {
711
- userAdapter: customAdapter
712
- });
713
- ```
714
-
715
- #### Step 3: Using Actions in Components
739
+ ### Using Actions in Components
716
740
 
717
741
  The recommended approach is to use specialized hooks:
718
742
 
@@ -765,11 +789,11 @@ const apiUrl = config.app?.api?.url || '';
765
789
  #### Unit Testing Actions
766
790
 
767
791
  ```typescript
768
- import {createUserActions} from '@nlabs/metropolisjs';
792
+ import {createUserActions, type UserActions} from '@nlabs/metropolisjs';
769
793
 
770
794
  describe('userActions', () => {
771
795
  let flux: FluxFramework;
772
- let userActions: userActions;
796
+ let userActions: UserActions;
773
797
 
774
798
  beforeEach(() => {
775
799
  flux = createMockFlux();
@@ -782,7 +806,7 @@ describe('userActions', () => {
782
806
 
783
807
  it('should add user with validation', async () => {
784
808
  const userData = {username: 'test', email: 'test@example.com'};
785
- const result = await userActions.add(userData);
809
+ const result = await userActions.addUser(userData);
786
810
  expect(result).toBeDefined();
787
811
  });
788
812
  });
@@ -791,7 +815,7 @@ describe('userActions', () => {
791
815
  #### Testing with Custom Adapters
792
816
 
793
817
  ```typescript
794
- const mockAdapter = jest.fn((input) => ({
818
+ const mockAdapter = vi.fn((input) => ({
795
819
  ...input,
796
820
  validated: true
797
821
  }));
@@ -1267,8 +1291,8 @@ For more detailed examples, see [`examples/permission-system-usage.tsx`](./examp
1267
1291
  ### Prerequisites
1268
1292
 
1269
1293
  - Node.js 16+
1270
- - React 18+
1271
- - TypeScript 4.5+
1294
+ - React 19+
1295
+ - TypeScript 7+
1272
1296
 
1273
1297
  ### Full Installation
1274
1298
 
@@ -1292,7 +1316,18 @@ Configure your environment-specific settings in the `config` prop of the `Metrop
1292
1316
 
1293
1317
  ## Contributing
1294
1318
 
1295
- We love contributions! Here's how you can help:
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:
1296
1331
 
1297
1332
  1. **Fork** the repository
1298
1333
  2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
package/docs/ACTIONS.md CHANGED
@@ -26,6 +26,26 @@ const postActions = createPostActions(flux);
26
26
  const restActions = createRestActions(flux);
27
27
  ```
28
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
+
29
49
  ## Action Families
30
50
 
31
51
  Each row links to:
@@ -56,7 +76,7 @@ These action families are available through specialized hooks when present, `use
56
76
  | Subscription | `useSubscriptionActions` | `subscription` | `createSubscriptionActions` | `addPlan`, `getPlanByItem`, `addSubscription`, `getSubscriptionByItem`, `getSubscriptionListByUser`, `deleteSubscription` | [subscriptionActions.ts](../src/actions/subscriptionActions/subscriptionActions.ts) |
57
77
  | Tag | `useTagActions` | `tag` | `createTagActions` | `addTag`, `addTagToItem`, `getTags`, `updateTag`, `deleteTag`, `deleteTagFromItem` | [tagActions.ts](../src/actions/tagActions/tagActions.ts) |
58
78
  | Translation | `useTranslationActions` | `translation` | `createTranslationActions` | `addTranslations`, `getTranslation`, `getTranslations`, `hasTranslation`, `queueTranslationKey`, `processPendingTranslations` | [translationActions.ts](../src/actions/translationActions/translationActions.ts) |
59
- | User | `useUserActions` | `user` | `createUserActions` | `signIn`, `signUp`, `session`, `refreshSession`, `itemById`, `listByLatest`, `updateUser` | [userActions.ts](../src/actions/userActions/userActions.ts) |
79
+ | User | `useUserActions` | `user` | `createUserActions` | `signIn`, `signUp`, `session`, `createBillingSetupSession`, `completeBillingSetupSession`, `deleteBillingCard`, `itemById`, `updateUser` | [userActions.ts](../src/actions/userActions/userActions.ts) |
60
80
  | Video | `useVideoActions` | `video` | `createVideoActions` | `add`, `itemById`, `list`, `update`, `delete` | [videoActions.ts](../src/actions/videoActions/videoActions.ts) |
61
81
  | Websocket | `useWebsocketActions` | `websocket` | `createWebsocketActions` | `wsInit`, `wsSend`, `onOpen`, `onReceive`, `onClose`, `onError` | [websocketActions.ts](../src/actions/websocketActions/websocketActions.ts) |
62
82
 
@@ -121,6 +141,34 @@ Configure the action through the `Metropolis` provider:
121
141
 
122
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.
123
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
+
124
172
  ## REST Actions
125
173
 
126
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.
@@ -173,6 +221,18 @@ All action creators are re-exported from:
173
221
 
174
222
  - [src/actions/index.ts](../src/actions/index.ts)
175
223
 
224
+ The package root also exports every creator, action interface, `ActionMap`, and the consolidated factory functions.
225
+
176
226
  All specialized hooks are exposed from:
177
227
 
178
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>
@@ -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
  ))}
@@ -1,27 +1,33 @@
1
1
  /**
2
2
  * Example: Using the Factory Pattern in MetropolisJS
3
- *
3
+ *
4
4
  * This file demonstrates how to use the new consolidated action factory
5
5
  * instead of individual createXxxActions functions.
6
6
  */
7
7
 
8
- import type { FluxFramework } from '@nlabs/arkhamjs';
9
- import { createAction, createActions, createAllActions } from '../src/utils/actionFactory';
8
+ import {createAction, createActions, createAllActions} from '../src/utils/actionFactory.js';
9
+
10
+ import type {FluxFramework} from '@nlabs/arkhamjs';
11
+ import type {PostType} from '../src/adapters/postAdapter/postAdapter.js';
12
+ import type {User} from '../src/adapters/userAdapter/userAdapter.js';
13
+
14
+ const getErrorMessage = (error: unknown): string => (
15
+ error instanceof Error ? error.message : 'An unknown error occurred'
16
+ );
10
17
 
11
18
  // Example 1: Basic Usage
12
19
  export const basicUsage = (flux: FluxFramework) => {
13
20
  // Create actions using consolidated factory functions
14
- const userActions = createAction('user', flux) as any;
15
- const postActions = createAction('post', flux) as any;
16
- const eventActions = createAction('event', flux) as any;
21
+ const userActions = createAction('user', flux);
22
+ const postActions = createAction('post', flux);
17
23
 
18
24
  // Use actions normally
19
25
  const addUser = async () => {
20
- const user = await userActions.add({
21
- username: 'john_doe',
26
+ const user = await userActions.addUser({
22
27
  email: 'john@example.com',
23
28
  firstName: 'John',
24
- lastName: 'Doe'
29
+ lastName: 'Doe',
30
+ username: 'john_doe'
25
31
  });
26
32
  return user;
27
33
  };
@@ -40,20 +46,21 @@ export const basicUsage = (flux: FluxFramework) => {
40
46
  // Example 2: Custom Adapter with Business Logic
41
47
  export const customAdapterUsage = (flux: FluxFramework) => {
42
48
  // Custom user adapter that adds business logic
43
- const businessUserAdapter = (input: unknown, options?: any) => {
44
- const user = input as any;
45
-
49
+ const businessUserAdapter = (input: unknown): User => {
50
+ const user = input as User;
51
+ const email = user.email || '';
52
+
46
53
  // Business validation
47
- if (user.email && !user.email.includes('@company.com')) {
54
+ if(email && !email.includes('@company.com')) {
48
55
  throw new Error('Only company emails are allowed');
49
56
  }
50
-
57
+
51
58
  // Add computed fields
52
59
  return {
53
60
  ...user,
61
+ department: email.split('@')[0]?.split('.')[1] || 'general',
54
62
  fullName: `${user.firstName || ''} ${user.lastName || ''}`.trim(),
55
- isAdmin: user.userAccess >= 3,
56
- department: user.email?.split('@')[0]?.split('.')[1] || 'general'
63
+ isAdmin: (user.userAccess || 0) >= 3
57
64
  };
58
65
  };
59
66
 
@@ -61,8 +68,8 @@ export const customAdapterUsage = (flux: FluxFramework) => {
61
68
  const userActions = createAction('user', flux, {
62
69
  userAdapter: businessUserAdapter,
63
70
  userAdapterOptions: {
64
- strict: true,
65
- environment: 'production'
71
+ environment: 'production',
72
+ strict: true
66
73
  }
67
74
  });
68
75
 
@@ -71,73 +78,72 @@ export const customAdapterUsage = (flux: FluxFramework) => {
71
78
 
72
79
  // Example 3: Runtime Adapter Updates
73
80
  export const runtimeUpdates = (flux: FluxFramework) => {
74
- const userActions = createAction('user', flux) as any;
81
+ const userActions = createAction('user', flux);
75
82
 
76
83
  // Update adapter at runtime
77
84
  const updateToStrictMode = () => {
78
85
  userActions.updateUserAdapterOptions({
79
- strict: true,
80
- environment: 'production'
86
+ environment: 'production',
87
+ strict: true
81
88
  });
82
89
  };
83
90
 
84
91
  // Add custom validation at runtime
85
92
  const addCustomValidation = () => {
86
- userActions.updateUserAdapter((input, options) => {
87
- const user = input as any;
88
-
93
+ userActions.updateUserAdapter((input) => {
94
+ const user = input as User;
95
+
89
96
  // Additional runtime validation
90
- if (user.age && user.age < 18) {
97
+ if(user.age && user.age < 18) {
91
98
  throw new Error('User must be 18 or older');
92
99
  }
93
-
100
+
94
101
  return user;
95
102
  });
96
103
  };
97
104
 
98
- return {updateToStrictMode, addCustomValidation, userActions};
105
+ return {addCustomValidation, updateToStrictMode, userActions};
99
106
  };
100
107
 
101
108
  // Example 4: Testing with Mock Adapters
102
109
  export const testingExample = (flux: FluxFramework) => {
103
110
  // Mock adapter for testing
104
- const mockUserAdapter = Object.assign((input: unknown) => {
105
- mockUserAdapter.calls.push(input);
111
+ const calls: unknown[] = [];
112
+ const mockUserAdapter = (input: unknown) => {
113
+ calls.push(input);
106
114
  return {
107
115
  ...(input as Record<string, unknown>),
108
116
  id: 'mock-user-id',
109
- validated: true,
110
- timestamp: new Date().toISOString()
117
+ timestamp: new Date().toISOString(),
118
+ validated: true
111
119
  };
112
- }, {
113
- calls: [] as unknown[]
114
- });
120
+ };
115
121
 
116
122
  const userActions = createAction('user', flux, {
117
123
  userAdapter: mockUserAdapter
118
- }) as any;
124
+ });
119
125
 
120
126
  // Test that adapter was called
121
127
  const testUserCreation = async () => {
122
- const user = await userActions.add({
123
- username: 'test_user',
124
- email: 'test@example.com'
128
+ const user = await userActions.addUser({
129
+ email: 'test@example.com',
130
+ username: 'test_user'
125
131
  });
126
132
 
127
- if(mockUserAdapter.calls.length === 0 || user.id !== 'mock-user-id' || user.validated !== true) {
133
+ if(calls.length === 0 || user.id !== 'mock-user-id' || user.validated !== true) {
128
134
  throw new Error('Mock adapter validation failed');
129
135
  }
130
136
  };
131
137
 
132
- return {userActions, testUserCreation};
138
+ return {testUserCreation, userActions};
133
139
  };
134
140
 
135
141
  // Example 5: Multiple Adapters with Different Configurations
136
142
  export const multipleAdapters = (flux: FluxFramework) => {
137
143
  // User adapter with strict validation
138
144
  const strictUserAdapter = (input: unknown) => {
139
- const user = input as any;
140
- if (!user.username || !user.email) {
145
+ const user = input as User;
146
+ if(!user.username || !user.email) {
141
147
  throw new Error('Username and email are required');
142
148
  }
143
149
  return user;
@@ -145,8 +151,8 @@ export const multipleAdapters = (flux: FluxFramework) => {
145
151
 
146
152
  // Post adapter with content validation
147
153
  const contentPostAdapter = (input: unknown) => {
148
- const post = input as any;
149
- if (post.content && post.content.length > 1000) {
154
+ const post = input as PostType;
155
+ if(post.content && post.content.length > 1000) {
150
156
  throw new Error('Post content too long');
151
157
  }
152
158
  return post;
@@ -156,44 +162,44 @@ export const multipleAdapters = (flux: FluxFramework) => {
156
162
  const userActions = createAction('user', flux, {
157
163
  userAdapter: strictUserAdapter,
158
164
  userAdapterOptions: {strict: true}
159
- }) as any;
165
+ });
160
166
 
161
167
  const postActions = createAction('post', flux, {
162
168
  postAdapter: contentPostAdapter,
163
169
  postAdapterOptions: {environment: 'development'}
164
- }) as any;
170
+ });
165
171
 
166
- return {userActions, postActions};
172
+ return {postActions, userActions};
167
173
  };
168
174
 
169
175
  // Example 6: Error Handling
170
176
  export const errorHandling = (flux: FluxFramework) => {
171
177
  const userActions = createAction('user', flux, {
172
178
  userAdapterOptions: {
173
- strict: true,
174
179
  customValidation: (input) => {
175
- const user = input as any;
176
-
180
+ const user = input as User;
181
+
177
182
  // Custom error handling
178
- if (user.username && user.username.length < 3) {
183
+ if(user.username && user.username.length < 3) {
179
184
  throw new Error('Username must be at least 3 characters');
180
185
  }
181
-
182
- if (user.email && !user.email.includes('@')) {
186
+
187
+ if(user.email && !user.email.includes('@')) {
183
188
  throw new Error('Invalid email format');
184
189
  }
185
-
190
+
186
191
  return user;
187
- }
192
+ },
193
+ strict: true
188
194
  }
189
- }) as any;
195
+ });
190
196
 
191
- const createUserWithErrorHandling = async (userData: any) => {
197
+ const createUserWithErrorHandling = async (userData: Partial<User>) => {
192
198
  try {
193
- const user = await userActions.add(userData);
199
+ const user = await userActions.addUser(userData);
194
200
  return {success: true, user};
195
- } catch (error) {
196
- return {success: false, error: error.message};
201
+ } catch(error) {
202
+ return {error: getErrorMessage(error), success: false};
197
203
  }
198
204
  };
199
205
 
@@ -204,45 +210,46 @@ export const errorHandling = (flux: FluxFramework) => {
204
210
  export const multipleActionsExample = (flux: FluxFramework) => {
205
211
  // Create multiple actions at once
206
212
  const actions = createActions(['user', 'post', 'message'], flux, {
207
- user: {
208
- userAdapterOptions: { strict: true }
209
- },
210
213
  post: {
211
- postAdapter: (input: any) => {
212
- if (input.content && input.content.length > 1000) {
214
+ postAdapter: (input: unknown) => {
215
+ const post = input as PostType;
216
+ if(post.content && post.content.length > 1000) {
213
217
  throw new Error('Post content too long');
214
218
  }
215
- return input;
219
+ return post;
216
220
  }
221
+ },
222
+ user: {
223
+ userAdapterOptions: {strict: true}
217
224
  }
218
225
  });
219
226
 
220
- const createUserAndPost = async (userData: any, postData: any) => {
221
- const user = await actions.user.add(userData);
227
+ const createUserAndPost = async (userData: Partial<User>, postData: Partial<PostType>) => {
228
+ const user = await actions.user.addUser(userData);
222
229
  const post = await actions.post.add({
223
230
  ...postData,
224
231
  userId: user.userId
225
232
  });
226
- return { user, post };
233
+ return {post, user};
227
234
  };
228
235
 
229
- return { actions, createUserAndPost };
236
+ return {actions, createUserAndPost};
230
237
  };
231
238
 
232
239
  // Example 8: All Actions Creation
233
240
  export const allActionsExample = (flux: FluxFramework) => {
234
241
  // Create all available actions
235
242
  const allActions = createAllActions(flux, {
236
- user: { userAdapterOptions: { strict: true } },
237
- post: { postAdapterOptions: { environment: 'production' } },
238
- image: { imageAdapterOptions: { allowPartial: true } }
243
+ image: {imageAdapterOptions: {allowPartial: true}},
244
+ post: {postAdapterOptions: {environment: 'production'}},
245
+ user: {userAdapterOptions: {strict: true}}
239
246
  });
240
247
 
241
248
  const comprehensiveWorkflow = async () => {
242
249
  // Create user
243
- const user = await allActions.user.add({
244
- username: 'jane_doe',
245
- email: 'jane@example.com'
250
+ const user = await allActions.user.addUser({
251
+ email: 'jane@example.com',
252
+ username: 'jane_doe'
246
253
  });
247
254
 
248
255
  // Create post
@@ -263,8 +270,8 @@ export const allActionsExample = (flux: FluxFramework) => {
263
270
  recipientId: user.userId
264
271
  });
265
272
 
266
- return { user, post, image };
273
+ return {image, post, user};
267
274
  };
268
275
 
269
- return { allActions, comprehensiveWorkflow };
276
+ return {allActions, comprehensiveWorkflow};
270
277
  };