@nlabs/metropolisjs 0.1.2 → 0.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/.github/copilot-instructions.md +167 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +112 -0
- package/GRAPHQL_VALIDATION_RESULTS.md +158 -0
- package/PERMISSION_GRAPHQL_INTEGRATION.md +293 -0
- package/PERMISSION_IMPLEMENTATION_SUMMARY.md +243 -0
- package/README.md +1123 -112
- package/SIGNIN_GRAPHQL_VALIDATION.md +125 -0
- package/VAULT_GRAPHQL_API_FINDINGS.md +312 -0
- package/docs/ACTIONS.md +63 -0
- package/docs/COLLECTIONS.md +1038 -0
- package/docs/CONNECTIONS.md +1367 -0
- package/docs/CRUD_INTEGRATION.md +702 -0
- package/examples/connections-usage.tsx +909 -0
- package/examples/content-actions-usage.ts +144 -0
- package/examples/crud-usage.tsx +847 -0
- package/examples/extensibility-usage.tsx +889 -0
- package/examples/i18n-import-examples.ts +86 -0
- package/examples/i18n-namespace-usage.ts +118 -0
- package/examples/i18next-singleton-demo.ts +187 -0
- package/examples/permission-system-usage.tsx +415 -0
- package/examples/project-details.tsx +397 -0
- package/examples/shared-i18n-usage.tsx +203 -0
- package/examples/signup-error-handling.ts +100 -0
- package/examples/translation-types.ts +86 -0
- package/examples/translation-usage.tsx +336 -0
- package/index.js +1 -0
- package/jest.config.mjs +28 -0
- package/lex.config.mjs +11 -2
- package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts +22 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts.map +1 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServer.js +197 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts +3 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts.map +1 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.js +52 -0
- package/lib/actions/appActions/appActions.d.ts +29 -0
- package/lib/actions/appActions/appActions.d.ts.map +1 -0
- package/lib/actions/appActions/appActions.js +159 -0
- package/lib/actions/connectionActions/connectionActions.d.ts +19 -0
- package/lib/actions/connectionActions/connectionActions.d.ts.map +1 -0
- package/lib/actions/connectionActions/connectionActions.js +131 -0
- package/lib/actions/contentActions/contentActions.d.ts +34 -0
- package/lib/actions/contentActions/contentActions.d.ts.map +1 -0
- package/lib/actions/contentActions/contentActions.js +314 -0
- package/lib/actions/conversationActions/conversationActions.d.ts +30 -0
- package/lib/actions/conversationActions/conversationActions.d.ts.map +1 -0
- package/lib/actions/conversationActions/conversationActions.js +225 -0
- package/lib/actions/eventActions/eventActions.d.ts +27 -7
- package/lib/actions/eventActions/eventActions.d.ts.map +1 -0
- package/lib/actions/eventActions/eventActions.js +436 -2
- package/lib/actions/groupActions/groupActions.d.ts +30 -0
- package/lib/actions/groupActions/groupActions.d.ts.map +1 -0
- package/lib/actions/groupActions/groupActions.js +229 -0
- package/lib/actions/imageActions/imageActions.d.ts +16 -10
- package/lib/actions/imageActions/imageActions.d.ts.map +1 -0
- package/lib/actions/imageActions/imageActions.js +593 -2
- package/lib/actions/index.d.ts +21 -9
- package/lib/actions/index.d.ts.map +1 -0
- package/lib/actions/index.js +22 -2
- package/lib/actions/locationActions/locationActions.d.ts +11 -9
- package/lib/actions/locationActions/locationActions.d.ts.map +1 -0
- package/lib/actions/locationActions/locationActions.js +388 -2
- package/lib/actions/messageActions/messageActions.d.ts +16 -32
- package/lib/actions/messageActions/messageActions.d.ts.map +1 -0
- package/lib/actions/messageActions/messageActions.js +235 -2
- package/lib/actions/permissionActions/permissionActions.d.ts +34 -0
- package/lib/actions/permissionActions/permissionActions.d.ts.map +1 -0
- package/lib/actions/permissionActions/permissionActions.js +330 -0
- package/lib/actions/personaActions/personaActions.d.ts +65 -0
- package/lib/actions/personaActions/personaActions.d.ts.map +1 -0
- package/lib/actions/personaActions/personaActions.js +572 -0
- package/lib/actions/postActions/postActions.d.ts +12 -32
- package/lib/actions/postActions/postActions.d.ts.map +1 -0
- package/lib/actions/postActions/postActions.js +447 -2
- package/lib/actions/reactionActions/reactionActions.d.ts +13 -27
- package/lib/actions/reactionActions/reactionActions.d.ts.map +1 -0
- package/lib/actions/reactionActions/reactionActions.js +413 -2
- package/lib/actions/sseActions/sseActions.d.ts +1 -0
- package/lib/actions/sseActions/sseActions.d.ts.map +1 -0
- package/lib/actions/sseActions/sseActions.js +148 -2
- package/lib/actions/subscriptionActions/subscriptionActions.d.ts +24 -0
- package/lib/actions/subscriptionActions/subscriptionActions.d.ts.map +1 -0
- package/lib/actions/subscriptionActions/subscriptionActions.js +329 -0
- package/lib/actions/tagActions/tagActions.d.ts +13 -31
- package/lib/actions/tagActions/tagActions.d.ts.map +1 -0
- package/lib/actions/tagActions/tagActions.js +347 -2
- package/lib/actions/translationActions/translationActions.d.ts +38 -0
- package/lib/actions/translationActions/translationActions.d.ts.map +1 -0
- package/lib/actions/translationActions/translationActions.js +189 -0
- package/lib/actions/userActions/userActions.d.ts +42 -95
- package/lib/actions/userActions/userActions.d.ts.map +1 -0
- package/lib/actions/userActions/userActions.js +844 -2
- package/lib/actions/videoActions/videoActions.d.ts +41 -0
- package/lib/actions/videoActions/videoActions.d.ts.map +1 -0
- package/lib/actions/videoActions/videoActions.js +532 -0
- package/lib/actions/websocketActions/websocketActions.d.ts +20 -21
- package/lib/actions/websocketActions/websocketActions.d.ts.map +1 -0
- package/lib/actions/websocketActions/websocketActions.js +377 -2
- package/lib/adapters/appAdapter/appAdapter.d.ts +8 -0
- package/lib/adapters/appAdapter/appAdapter.d.ts.map +1 -0
- package/lib/adapters/appAdapter/appAdapter.js +90 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.d.ts +3 -2
- package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.js +75 -2
- package/lib/adapters/contentAdapter/contentAdapter.d.ts +3 -2
- package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -0
- package/lib/adapters/contentAdapter/contentAdapter.js +107 -2
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +3 -2
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.js +75 -2
- package/lib/adapters/eventAdapter/eventAdapter.d.ts +5 -2
- package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -0
- package/lib/adapters/eventAdapter/eventAdapter.js +162 -2
- package/lib/adapters/fileAdapter/fileAdapter.d.ts +3 -2
- package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -0
- package/lib/adapters/fileAdapter/fileAdapter.js +118 -2
- package/lib/adapters/groupAdapter/groupAdapter.d.ts +28 -0
- package/lib/adapters/groupAdapter/groupAdapter.d.ts.map +1 -0
- package/lib/adapters/groupAdapter/groupAdapter.js +66 -0
- package/lib/adapters/imageAdapter/imageAdapter.d.ts +9 -2
- package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -0
- package/lib/adapters/imageAdapter/imageAdapter.js +134 -2
- package/lib/adapters/index.d.ts +34 -16
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/adapters/index.js +19 -2
- package/lib/adapters/locationAdapter/locationAdapter.d.ts +3 -2
- package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -0
- package/lib/adapters/locationAdapter/locationAdapter.js +95 -2
- package/lib/adapters/messageAdapter/messageAdapter.d.ts +23 -19
- package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -0
- package/lib/adapters/messageAdapter/messageAdapter.js +82 -2
- package/lib/adapters/permissionAdapter/permissionAdapter.d.ts +41 -0
- package/lib/adapters/permissionAdapter/permissionAdapter.d.ts.map +1 -0
- package/lib/adapters/permissionAdapter/permissionAdapter.js +120 -0
- package/lib/adapters/personaAdapter/personaAdapter.d.ts +28 -2
- package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -0
- package/lib/adapters/personaAdapter/personaAdapter.js +161 -2
- package/lib/adapters/postAdapter/postAdapter.d.ts +3 -2
- package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -0
- package/lib/adapters/postAdapter/postAdapter.js +115 -2
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +5 -2
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.js +81 -2
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +3 -2
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.js +58 -2
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +3 -2
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.js +80 -2
- package/lib/adapters/tagAdapter/tagAdapter.d.ts +3 -2
- package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -0
- package/lib/adapters/tagAdapter/tagAdapter.js +98 -2
- package/lib/adapters/translationAdapter/translationAdapter.d.ts +21 -0
- package/lib/adapters/translationAdapter/translationAdapter.d.ts.map +1 -0
- package/lib/adapters/translationAdapter/translationAdapter.js +55 -0
- package/lib/adapters/userAdapter/userAdapter.d.ts +18 -6
- package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -0
- package/lib/adapters/userAdapter/userAdapter.js +302 -2
- package/lib/adapters/videoAdapter/videoAdapter.d.ts +8 -0
- package/lib/adapters/videoAdapter/videoAdapter.d.ts.map +1 -0
- package/lib/adapters/videoAdapter/videoAdapter.js +194 -0
- package/lib/config/index.d.ts +19 -6
- package/lib/config/index.d.ts.map +1 -0
- package/lib/config/index.js +97 -2
- package/lib/constants/Collections.d.ts +44 -0
- package/lib/constants/Collections.d.ts.map +1 -0
- package/lib/constants/Collections.js +40 -0
- package/lib/constants/MetropolisConstants.d.ts +1 -0
- package/lib/constants/MetropolisConstants.d.ts.map +1 -0
- package/lib/constants/MetropolisConstants.js +15 -2
- package/lib/graphql/message.d.ts +1 -0
- package/lib/graphql/message.d.ts.map +1 -0
- package/lib/graphql/message.js +6 -4
- package/lib/graphql/notification.d.ts +1 -0
- package/lib/graphql/notification.d.ts.map +1 -0
- package/lib/graphql/notification.js +6 -4
- package/lib/graphql/session.d.ts +1 -0
- package/lib/graphql/session.d.ts.map +1 -0
- package/lib/graphql/session.js +4 -3
- package/lib/index.d.ts +45 -22
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +248 -2
- package/lib/stores/appStore.d.ts +1 -0
- package/lib/stores/appStore.d.ts.map +1 -0
- package/lib/stores/appStore.js +23 -2
- package/lib/stores/contentStore.d.ts +36 -0
- package/lib/stores/contentStore.d.ts.map +1 -0
- package/lib/stores/contentStore.js +139 -0
- package/lib/stores/conversationStore.d.ts +38 -0
- package/lib/stores/conversationStore.d.ts.map +1 -0
- package/lib/stores/conversationStore.js +117 -0
- package/lib/stores/eventStore.d.ts +4 -1
- package/lib/stores/eventStore.d.ts.map +1 -0
- package/lib/stores/eventStore.js +47 -2
- package/lib/stores/groupStore.d.ts +37 -0
- package/lib/stores/groupStore.d.ts.map +1 -0
- package/lib/stores/groupStore.js +68 -0
- package/lib/stores/imageStore.d.ts +6 -1
- package/lib/stores/imageStore.d.ts.map +1 -0
- package/lib/stores/imageStore.js +53 -2
- package/lib/stores/index.d.ts +20 -11
- package/lib/stores/index.d.ts.map +1 -0
- package/lib/stores/index.js +24 -2
- package/lib/stores/locationStore.d.ts +2 -1
- package/lib/stores/locationStore.d.ts.map +1 -0
- package/lib/stores/locationStore.js +42 -2
- package/lib/stores/messageStore.d.ts +19 -4
- package/lib/stores/messageStore.d.ts.map +1 -0
- package/lib/stores/messageStore.js +137 -2
- package/lib/stores/notificationStore.d.ts +33 -0
- package/lib/stores/notificationStore.d.ts.map +1 -0
- package/lib/stores/notificationStore.js +55 -0
- package/lib/stores/permissionStore.d.ts +37 -0
- package/lib/stores/permissionStore.d.ts.map +1 -0
- package/lib/stores/permissionStore.js +117 -0
- package/lib/stores/personaStore.d.ts +26 -0
- package/lib/stores/personaStore.d.ts.map +1 -0
- package/lib/stores/personaStore.js +118 -0
- package/lib/stores/postStore.d.ts +2 -1
- package/lib/stores/postStore.d.ts.map +1 -0
- package/lib/stores/postStore.js +53 -2
- package/lib/stores/reactionStore.d.ts +1 -0
- package/lib/stores/reactionStore.d.ts.map +1 -0
- package/lib/stores/reactionStore.js +28 -2
- package/lib/stores/subscriptionStore.d.ts +41 -0
- package/lib/stores/subscriptionStore.d.ts.map +1 -0
- package/lib/stores/subscriptionStore.js +85 -0
- package/lib/stores/tagStore.d.ts +8 -5
- package/lib/stores/tagStore.d.ts.map +1 -0
- package/lib/stores/tagStore.js +87 -2
- package/lib/stores/translationStore.d.ts +42 -0
- package/lib/stores/translationStore.d.ts.map +1 -0
- package/lib/stores/translationStore.js +122 -0
- package/lib/stores/userStore.d.ts +5 -4
- package/lib/stores/userStore.d.ts.map +1 -0
- package/lib/stores/userStore.js +319 -2
- package/lib/stores/videoStore.d.ts +39 -0
- package/lib/stores/videoStore.d.ts.map +1 -0
- package/lib/stores/videoStore.js +132 -0
- package/lib/stores/websocketStore.d.ts +1 -0
- package/lib/stores/websocketStore.d.ts.map +1 -0
- package/lib/stores/websocketStore.js +51 -2
- package/lib/types/apps.types.d.ts +17 -0
- package/lib/types/apps.types.d.ts.map +1 -0
- package/lib/types/apps.types.js +6 -0
- package/lib/types/common.types.d.ts +31 -0
- package/lib/types/common.types.d.ts.map +1 -0
- package/lib/types/common.types.js +6 -0
- package/lib/types/conversations.types.d.ts +17 -0
- package/lib/types/conversations.types.d.ts.map +1 -0
- package/lib/types/conversations.types.js +6 -0
- package/lib/types/edges.types.d.ts +38 -0
- package/lib/types/edges.types.d.ts.map +1 -0
- package/lib/types/edges.types.js +6 -0
- package/lib/types/groups.types.d.ts +19 -0
- package/lib/types/groups.types.d.ts.map +1 -0
- package/lib/types/groups.types.js +6 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +12 -0
- package/lib/types/subscriptions.types.d.ts +32 -0
- package/lib/types/subscriptions.types.d.ts.map +1 -0
- package/lib/types/subscriptions.types.js +6 -0
- package/lib/types/videos.types.d.ts +61 -0
- package/lib/types/videos.types.d.ts.map +1 -0
- package/lib/types/videos.types.js +6 -0
- package/lib/utils/MetropolisProvider.d.ts +39 -15
- package/lib/utils/MetropolisProvider.d.ts.map +1 -0
- package/lib/utils/MetropolisProvider.js +31 -2
- package/lib/utils/actionFactory.d.ts +21 -19
- package/lib/utils/actionFactory.d.ts.map +1 -0
- package/lib/utils/actionFactory.js +95 -2
- package/lib/utils/api.d.ts +5 -4
- package/lib/utils/api.d.ts.map +1 -0
- package/lib/utils/api.js +252 -6
- package/lib/utils/app.d.ts +1 -0
- package/lib/utils/app.d.ts.map +1 -0
- package/lib/utils/app.js +15 -2
- package/lib/utils/baseActionFactory.d.ts +5 -23
- package/lib/utils/baseActionFactory.d.ts.map +1 -0
- package/lib/utils/baseActionFactory.js +72 -2
- package/lib/utils/configUtils.d.ts +11 -0
- package/lib/utils/configUtils.d.ts.map +1 -0
- package/lib/utils/configUtils.js +20 -0
- package/lib/utils/dateUtils.d.ts +1 -0
- package/lib/utils/dateUtils.d.ts.map +1 -0
- package/lib/utils/dateUtils.js +11 -2
- package/lib/utils/file.d.ts +3 -1
- package/lib/utils/file.d.ts.map +1 -0
- package/lib/utils/file.js +156 -2
- package/lib/utils/i18n.d.ts +7 -0
- package/lib/utils/i18n.d.ts.map +1 -0
- package/lib/utils/i18n.js +96 -0
- package/lib/utils/index.d.ts +20 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +26 -0
- package/lib/utils/location.d.ts +4 -3
- package/lib/utils/location.d.ts.map +1 -0
- package/lib/utils/location.js +82 -2
- package/lib/utils/permissionUtils.d.ts +22 -0
- package/lib/utils/permissionUtils.d.ts.map +1 -0
- package/lib/utils/permissionUtils.js +72 -0
- package/lib/utils/requestCache.d.ts +8 -0
- package/lib/utils/requestCache.d.ts.map +1 -0
- package/lib/utils/requestCache.js +53 -0
- package/lib/utils/session.d.ts +14 -0
- package/lib/utils/session.d.ts.map +1 -0
- package/lib/utils/session.js +107 -0
- package/lib/utils/useConversationTyping.d.ts +26 -0
- package/lib/utils/useConversationTyping.d.ts.map +1 -0
- package/lib/utils/useConversationTyping.js +117 -0
- package/lib/utils/useMetropolis.d.ts +67 -12
- package/lib/utils/useMetropolis.d.ts.map +1 -0
- package/lib/utils/useMetropolis.js +311 -2
- package/lib/utils/useTranslations.d.ts +16 -0
- package/lib/utils/useTranslations.d.ts.map +1 -0
- package/lib/utils/useTranslations.js +105 -0
- package/lib/utils/validatorFactory.d.ts +3 -2
- package/lib/utils/validatorFactory.d.ts.map +1 -0
- package/lib/utils/validatorFactory.js +95 -2
- package/package.json +58 -17
- package/temp_signin.ts +73 -0
- package/lib/actions/profileActions/profileActions.d.ts +0 -50
- package/lib/actions/profileActions/profileActions.js +0 -2
- package/lib/adapters/profileAdapter/profileAdapter.d.ts +0 -32
- package/lib/adapters/profileAdapter/profileAdapter.js +0 -2
- package/lib/stores/profileStore.d.ts +0 -24
- package/lib/stores/profileStore.js +0 -2
- /package/{FACTORY_PATTERN_GUIDE.md → factoryPatternGuide.md} +0 -0
package/README.md
CHANGED
|
@@ -11,38 +11,38 @@
|
|
|
11
11
|
|
|
12
12
|
MetropolisJS is the bridge that connects your frontend dreams to backend reality. Built on the powerful combination of **Reaktor** (backend services) and **ArkhamJS** (frontend data store), MetropolisJS provides a seamless, real-time integration layer that handles everything from user authentication to real-time messaging and notifications.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Why MetropolisJS?
|
|
15
15
|
|
|
16
|
-
###
|
|
16
|
+
### **Seamless Integration**
|
|
17
17
|
|
|
18
18
|
Connect your React frontend to Reaktor-powered backend services with zero configuration headaches. MetropolisJS handles all the complex data flow, state management, and real-time communication.
|
|
19
19
|
|
|
20
|
-
###
|
|
20
|
+
### **Real-Time Everything**
|
|
21
21
|
|
|
22
22
|
Built-in WebSocket and Server-Sent Events (SSE) support for instant messaging, live notifications, and real-time data synchronization. Your users will never miss a beat.
|
|
23
23
|
|
|
24
|
-
###
|
|
24
|
+
### **Type-Safe & Reliable**
|
|
25
25
|
|
|
26
26
|
Full TypeScript support with comprehensive type definitions. Catch errors at compile time, not runtime.
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### **Developer Experience First**
|
|
29
29
|
|
|
30
30
|
Clean, intuitive APIs that make complex operations feel simple. Focus on building features, not boilerplate.
|
|
31
31
|
|
|
32
|
-
##
|
|
33
|
-
|
|
32
|
+
## What Can You Build?
|
|
34
33
|
MetropolisJS powers applications that need:
|
|
35
34
|
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
35
|
+
- **User Authentication & Authorization**
|
|
36
|
+
- **Role-Based Access Control (RBAC)** with 5-level permission system
|
|
37
|
+
- **Real-Time Messaging Systems**
|
|
38
|
+
- **Live Notifications**
|
|
39
|
+
- **Social Media Features** (posts, reactions, tags)
|
|
40
|
+
- **Location-Based Services**
|
|
41
|
+
- **Media Management** (images, files)
|
|
42
|
+
- **Event Management**
|
|
43
|
+
- **User Connections & Relationships**
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## Quick Start
|
|
46
46
|
|
|
47
47
|
### Installation
|
|
48
48
|
|
|
@@ -53,15 +53,20 @@ npm install @nlabs/metropolisjs @nlabs/arkhamjs @nlabs/arkhamjs-utils-react
|
|
|
53
53
|
### Basic Setup
|
|
54
54
|
|
|
55
55
|
```tsx
|
|
56
|
-
import
|
|
57
|
-
import {Metropolis} from '@nlabs/metropolisjs';
|
|
58
|
-
import {useMetropolis} from '@nlabs/metropolisjs';
|
|
56
|
+
import {Metropolis, useUserActions, useMessageActions, useWebsocketActions} from '@nlabs/metropolisjs';
|
|
59
57
|
|
|
60
58
|
const App = () => {
|
|
61
59
|
return (
|
|
62
60
|
<Metropolis config={{
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
development: {
|
|
62
|
+
environment: 'development',
|
|
63
|
+
app: {
|
|
64
|
+
api: {
|
|
65
|
+
url: 'http://localhost:3000/app',
|
|
66
|
+
public: 'http://localhost:3000/public'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
65
70
|
}}>
|
|
66
71
|
<YourApp />
|
|
67
72
|
</Metropolis>
|
|
@@ -69,18 +74,255 @@ const App = () => {
|
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
const YourApp = () => {
|
|
72
|
-
|
|
77
|
+
// Use specialized hooks for better performance
|
|
78
|
+
const userActions = useUserActions();
|
|
79
|
+
const messageActions = useMessageActions();
|
|
80
|
+
const websocketActions = useWebsocketActions();
|
|
73
81
|
|
|
74
82
|
// Start building amazing features!
|
|
75
83
|
return <div>Your app content</div>;
|
|
76
84
|
};
|
|
77
85
|
```
|
|
78
86
|
|
|
87
|
+
### Using Actions
|
|
88
|
+
|
|
89
|
+
MetropolisJS provides multiple ways to access actions:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// Option 1: Specialized hooks (recommended - best performance)
|
|
93
|
+
const userActions = useUserActions();
|
|
94
|
+
const postActions = usePostActions();
|
|
95
|
+
|
|
96
|
+
// Option 2: Selective creation
|
|
97
|
+
const {userActions, postActions} = useMetropolis(['user', 'post']);
|
|
98
|
+
|
|
99
|
+
// Option 3: All actions (default behavior)
|
|
100
|
+
const {userActions, postActions, messageActions} = useMetropolis();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
The `Metropolis` component accepts three main props: `config`, `adapters`, and `translations`. Here's how to configure each:
|
|
106
|
+
|
|
107
|
+
### Configuration Object
|
|
108
|
+
|
|
109
|
+
The `config` prop accepts a `MetropolisConfiguration` object that supports environment-specific settings:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<Metropolis
|
|
113
|
+
config={{
|
|
114
|
+
// Environment-specific configurations
|
|
115
|
+
development: {
|
|
116
|
+
environment: 'development',
|
|
117
|
+
app: {
|
|
118
|
+
api: {
|
|
119
|
+
url: 'http://localhost:3000/app',
|
|
120
|
+
public: 'http://localhost:3000/public',
|
|
121
|
+
uploadImage: 'http://localhost:3000/upload'
|
|
122
|
+
},
|
|
123
|
+
urls: {
|
|
124
|
+
websocket: 'ws://localhost:3000'
|
|
125
|
+
},
|
|
126
|
+
session: {
|
|
127
|
+
maxMinutes: 1440, // 24 hours
|
|
128
|
+
minMinutes: 15
|
|
129
|
+
},
|
|
130
|
+
name: 'My App',
|
|
131
|
+
version: '1.0.0'
|
|
132
|
+
},
|
|
133
|
+
isAuth: () => {
|
|
134
|
+
// Custom authentication check
|
|
135
|
+
const session = flux.getState('user.session', {});
|
|
136
|
+
return !!session.userActive;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
production: {
|
|
140
|
+
environment: 'production',
|
|
141
|
+
app: {
|
|
142
|
+
api: {
|
|
143
|
+
url: 'https://api.example.com/app',
|
|
144
|
+
public: 'https://api.example.com/public',
|
|
145
|
+
uploadImage: 'https://api.example.com/upload'
|
|
146
|
+
},
|
|
147
|
+
urls: {
|
|
148
|
+
websocket: 'wss://api.example.com'
|
|
149
|
+
},
|
|
150
|
+
session: {
|
|
151
|
+
maxMinutes: 2880, // 48 hours
|
|
152
|
+
minMinutes: 30
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
<YourApp />
|
|
159
|
+
</Metropolis>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Configuration Options
|
|
163
|
+
|
|
164
|
+
**Environment Configuration** (`MetropolisEnvironmentConfiguration`):
|
|
165
|
+
- `environment`: `'development' | 'production' | 'test' | 'local'` - Current environment
|
|
166
|
+
- `app`: Application-specific configuration
|
|
167
|
+
- `api`: API endpoint configuration
|
|
168
|
+
- `url`: Main API endpoint
|
|
169
|
+
- `public`: Public API endpoint
|
|
170
|
+
- `uploadImage`: Image upload endpoint
|
|
171
|
+
- `urls`: Additional URL configurations
|
|
172
|
+
- `websocket`: WebSocket server URL
|
|
173
|
+
- `session`: Session management settings
|
|
174
|
+
- `maxMinutes`: Maximum session duration in minutes
|
|
175
|
+
- `minMinutes`: Minimum session duration in minutes
|
|
176
|
+
- `name`: Application name
|
|
177
|
+
- `version`: Application version
|
|
178
|
+
- `isAuth`: Function that returns a boolean indicating if the user is authenticated
|
|
179
|
+
- `adapters`: Custom adapters (can also be passed as a separate prop)
|
|
180
|
+
|
|
181
|
+
### Custom Adapters
|
|
182
|
+
|
|
183
|
+
Pass custom adapters to override default data transformation behavior:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import {parseUser, parseMessage} from '@nlabs/metropolisjs';
|
|
187
|
+
|
|
188
|
+
<Metropolis
|
|
189
|
+
adapters={{
|
|
190
|
+
User: parseUser,
|
|
191
|
+
Message: parseMessage,
|
|
192
|
+
// Add other custom adapters as needed
|
|
193
|
+
Content: customContentAdapter,
|
|
194
|
+
Event: customEventAdapter,
|
|
195
|
+
Image: customImageAdapter,
|
|
196
|
+
Location: customLocationAdapter,
|
|
197
|
+
Post: customPostAdapter,
|
|
198
|
+
Persona: customPersonaAdapter,
|
|
199
|
+
Reaction: customReactionAdapter,
|
|
200
|
+
Tag: customTagAdapter,
|
|
201
|
+
Translation: customTranslationAdapter
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
<YourApp />
|
|
205
|
+
</Metropolis>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Translations
|
|
209
|
+
|
|
210
|
+
MetropolisJS supports both simple and complex translation formats:
|
|
211
|
+
|
|
212
|
+
#### Simple Translations
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<Metropolis
|
|
216
|
+
translations={{
|
|
217
|
+
'welcome': 'Welcome to MetropolisJS!',
|
|
218
|
+
'save': 'Save',
|
|
219
|
+
'cancel': 'Cancel',
|
|
220
|
+
'hello_user': 'Hello {{name}}!',
|
|
221
|
+
'items_count': 'You have {{count}} items'
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
<YourApp />
|
|
225
|
+
</Metropolis>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### Complex Translations (with locale and namespace)
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
<Metropolis
|
|
232
|
+
translations={{
|
|
233
|
+
'welcome': {
|
|
234
|
+
value: 'Welcome to MetropolisJS!',
|
|
235
|
+
locale: 'en',
|
|
236
|
+
namespace: 'common'
|
|
237
|
+
},
|
|
238
|
+
'save': {
|
|
239
|
+
value: 'Save',
|
|
240
|
+
locale: 'en',
|
|
241
|
+
namespace: 'actions'
|
|
242
|
+
}
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
<YourApp />
|
|
246
|
+
</Metropolis>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Complete Configuration Example
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import {Metropolis} from '@nlabs/metropolisjs';
|
|
253
|
+
import {parseUser, parseMessage} from '@nlabs/metropolisjs';
|
|
254
|
+
|
|
255
|
+
const App = () => {
|
|
256
|
+
return (
|
|
257
|
+
<Metropolis
|
|
258
|
+
config={{
|
|
259
|
+
development: {
|
|
260
|
+
environment: 'development',
|
|
261
|
+
app: {
|
|
262
|
+
api: {
|
|
263
|
+
url: 'http://localhost:3000/app',
|
|
264
|
+
public: 'http://localhost:3000/public'
|
|
265
|
+
},
|
|
266
|
+
urls: {
|
|
267
|
+
websocket: 'ws://localhost:3000'
|
|
268
|
+
},
|
|
269
|
+
session: {
|
|
270
|
+
maxMinutes: 1440,
|
|
271
|
+
minMinutes: 15
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
isAuth: () => {
|
|
275
|
+
// Your custom auth logic
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
production: {
|
|
280
|
+
environment: 'production',
|
|
281
|
+
app: {
|
|
282
|
+
api: {
|
|
283
|
+
url: 'https://api.example.com/app',
|
|
284
|
+
public: 'https://api.example.com/public'
|
|
285
|
+
},
|
|
286
|
+
urls: {
|
|
287
|
+
websocket: 'wss://api.example.com'
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}}
|
|
292
|
+
adapters={{
|
|
293
|
+
User: parseUser,
|
|
294
|
+
Message: parseMessage
|
|
295
|
+
}}
|
|
296
|
+
translations={{
|
|
297
|
+
'welcome': 'Welcome!',
|
|
298
|
+
'save': 'Save',
|
|
299
|
+
'cancel': 'Cancel'
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
<YourApp />
|
|
303
|
+
</Metropolis>
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Environment Detection
|
|
309
|
+
|
|
310
|
+
MetropolisJS automatically detects the environment based on:
|
|
311
|
+
|
|
312
|
+
1. The `environment` property in your config
|
|
313
|
+
2. `process.env.stage` (if set)
|
|
314
|
+
3. `process.env.NODE_ENV` (fallback)
|
|
315
|
+
4. Defaults to `'local'` if none are set
|
|
316
|
+
|
|
317
|
+
The configuration system will merge environment-specific settings with default values, allowing you to override only what you need.
|
|
318
|
+
|
|
79
319
|
### User Authentication Example
|
|
80
320
|
|
|
81
321
|
```tsx
|
|
322
|
+
import {useUserActions} from '@nlabs/metropolisjs';
|
|
323
|
+
|
|
82
324
|
const LoginForm = () => {
|
|
83
|
-
const
|
|
325
|
+
const userActions = useUserActions(); // Specialized hook - only creates user actions
|
|
84
326
|
const [username, setUsername] = useState('');
|
|
85
327
|
const [password, setPassword] = useState('');
|
|
86
328
|
|
|
@@ -115,8 +357,11 @@ const LoginForm = () => {
|
|
|
115
357
|
### Real-Time Messaging
|
|
116
358
|
|
|
117
359
|
```tsx
|
|
360
|
+
import {useMessageActions, useWebsocketActions} from '@nlabs/metropolisjs';
|
|
361
|
+
|
|
118
362
|
const ChatComponent = () => {
|
|
119
|
-
const
|
|
363
|
+
const messageActions = useMessageActions();
|
|
364
|
+
const websocketActions = useWebsocketActions();
|
|
120
365
|
const [messages, setMessages] = useState([]);
|
|
121
366
|
|
|
122
367
|
useEffect(() => {
|
|
@@ -145,9 +390,9 @@ const ChatComponent = () => {
|
|
|
145
390
|
};
|
|
146
391
|
```
|
|
147
392
|
|
|
148
|
-
##
|
|
393
|
+
## Architecture
|
|
149
394
|
|
|
150
|
-
MetropolisJS is built on a powerful three-layer architecture:
|
|
395
|
+
MetropolisJS is built on a powerful three-layer architecture with modern React patterns:
|
|
151
396
|
|
|
152
397
|
```
|
|
153
398
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
@@ -155,126 +400,766 @@ MetropolisJS is built on a powerful three-layer architecture:
|
|
|
155
400
|
│ │◄──►│ │◄──►│ Backend │
|
|
156
401
|
│ UI Layer │ │ Integration │ │ Services │
|
|
157
402
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
403
|
+
│ │
|
|
404
|
+
│ ▼
|
|
405
|
+
│ ┌─────────────────┐
|
|
406
|
+
│ │ ArkhamJS │
|
|
407
|
+
│ │ Data Store │
|
|
408
|
+
│ │ │
|
|
409
|
+
│ └─────────────────┘
|
|
410
|
+
│
|
|
411
|
+
▼
|
|
412
|
+
┌─────────────────┐
|
|
413
|
+
│ React Context │
|
|
414
|
+
│ (Config/Flux) │
|
|
415
|
+
└─────────────────┘
|
|
165
416
|
```
|
|
166
417
|
|
|
167
|
-
###
|
|
418
|
+
### Core Components
|
|
168
419
|
|
|
169
|
-
- **Actions**: Handle all API interactions and business logic
|
|
420
|
+
- **Actions**: Handle all API interactions and business logic (factory pattern)
|
|
170
421
|
- **Adapters**: Transform data between frontend and backend formats
|
|
171
422
|
- **Stores**: Manage application state with ArkhamJS
|
|
172
423
|
- **WebSocket Actions**: Handle real-time communication
|
|
173
|
-
- **Configuration**:
|
|
424
|
+
- **Configuration**: Context-based configuration (React best practices)
|
|
425
|
+
- **Hooks**: Specialized hooks for accessing actions and configuration
|
|
426
|
+
|
|
427
|
+
### Modern Architecture Features
|
|
428
|
+
|
|
429
|
+
MetropolisJS follows React best practices:
|
|
430
|
+
|
|
431
|
+
- **Context-Based Configuration**: No global state, configuration through React Context
|
|
432
|
+
- **Factory Pattern**: Functional action creation with dependency injection
|
|
433
|
+
- **Selective Action Creation**: Only create actions you need for better performance
|
|
434
|
+
- **Type Safety**: Full TypeScript support with proper type inference
|
|
435
|
+
- **Specialized Hooks**: Individual hooks for each action type
|
|
436
|
+
- **Flux State Integration**: Configuration stored in flux state for non-React code access
|
|
437
|
+
|
|
438
|
+
## Available Actions
|
|
439
|
+
|
|
440
|
+
MetropolisJS provides comprehensive actions for all your needs. Access them using specialized hooks or the main `useMetropolis()` hook:
|
|
441
|
+
|
|
442
|
+
### Specialized Hooks (Recommended)
|
|
443
|
+
|
|
444
|
+
- `useUserActions()` - Authentication, personas, user management
|
|
445
|
+
- `useMessageActions()` - Real-time messaging and conversations
|
|
446
|
+
- `usePermissionActions()` - Permission and role management (RBAC)
|
|
447
|
+
- `usePostActions()` - Social media posts and content
|
|
448
|
+
- `useReactionActions()` - Likes, reactions, and interactions
|
|
449
|
+
- `useTagActions()` - Content categorization and discovery
|
|
450
|
+
- `useEventActions()` - Event management and scheduling
|
|
451
|
+
- `useImageActions()` - Media upload and management
|
|
452
|
+
- `useLocationActions()` - Geolocation and location-based features
|
|
453
|
+
- `useWebsocketActions()` - Real-time communication setup
|
|
454
|
+
- `useContentActions()` - Content management
|
|
455
|
+
- `usePersonaActions()` - Persona management
|
|
456
|
+
- `useTranslationActions()` - Translation management
|
|
457
|
+
|
|
458
|
+
### Using Collection Actions
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
// Recommended: Use specialized hooks (only creates what you need)
|
|
462
|
+
const userActions = useUserActions();
|
|
463
|
+
const postActions = usePostActions();
|
|
464
|
+
const permissionActions = usePermissionActions();
|
|
465
|
+
|
|
466
|
+
// Alternative: Selective creation
|
|
467
|
+
const {userActions, postActions, permissionActions} = useMetropolis(['user', 'post', 'permission']);
|
|
468
|
+
|
|
469
|
+
// Alternative: All actions (creates all action types)
|
|
470
|
+
const {userActions, postActions, messageActions, permissionActions} = useMetropolis();
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Factory Pattern Guide
|
|
474
|
+
|
|
475
|
+
MetropolisJS has been refactored to use a **factory function pattern** for actions instead of class-based approaches. This provides better functional programming practices, improved testability, and enhanced flexibility through dependency injection.
|
|
476
|
+
|
|
477
|
+
### Key Benefits
|
|
478
|
+
|
|
479
|
+
1. **Functional Programming**: Pure functions instead of classes with side effects
|
|
480
|
+
2. **Better Testability**: Easier to mock and test individual functions
|
|
481
|
+
3. **Composability**: Actions can be easily combined and extended
|
|
482
|
+
4. **Dependency Injection**: Custom adapters can be injected and merged with defaults
|
|
483
|
+
5. **Backward Compatibility**: Legacy class wrappers maintain existing API compatibility
|
|
484
|
+
|
|
485
|
+
### Basic Usage
|
|
486
|
+
|
|
487
|
+
#### Before (Class-based)
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import {userActions} from '../actions/userActions';
|
|
491
|
+
|
|
492
|
+
const userActions = new userActions(flux);
|
|
493
|
+
const user = await userActions.add(userData);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### After (Factory Pattern)
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import {createUserActions} from '../actions/userActions';
|
|
500
|
+
|
|
501
|
+
const userActions = createUserActions(flux);
|
|
502
|
+
const user = await userActions.add(userData);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Advanced Usage with Custom Adapters
|
|
506
|
+
|
|
507
|
+
#### Custom Validation Adapter
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
// Custom adapter that extends default behavior
|
|
511
|
+
const customUserAdapter = (input: unknown, options?: UserAdapterOptions) => {
|
|
512
|
+
// input is already validated by default adapter
|
|
513
|
+
const user = input as any;
|
|
514
|
+
|
|
515
|
+
// Add business-specific validation
|
|
516
|
+
if (user.email && !user.email.includes('@company.com')) {
|
|
517
|
+
throw new Error('Only company emails allowed');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Add computed fields
|
|
521
|
+
return {
|
|
522
|
+
...user,
|
|
523
|
+
fullName: `${user.firstName || ''} ${user.lastName || ''}`.trim(),
|
|
524
|
+
isAdmin: user.userAccess >= 3
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const userActions = createUserActions(flux, {
|
|
529
|
+
userAdapter: customUserAdapter
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### Configuration-based Adapters
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
const userActions = createUserActions(flux, {
|
|
537
|
+
userAdapterOptions: {
|
|
538
|
+
strict: true,
|
|
539
|
+
environment: 'production',
|
|
540
|
+
customValidation: (input) => {
|
|
541
|
+
// Additional validation logic
|
|
542
|
+
return input;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### Runtime Adapter Updates
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
const userActions = createUserActions(flux);
|
|
552
|
+
|
|
553
|
+
// Update adapter at runtime
|
|
554
|
+
userActions.updateUserAdapter(customUserAdapter);
|
|
555
|
+
|
|
556
|
+
// Update options at runtime
|
|
557
|
+
userActions.updateUserAdapterOptions({
|
|
558
|
+
strict: true,
|
|
559
|
+
environment: 'production'
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Available Factory Functions
|
|
564
|
+
|
|
565
|
+
All action files now export factory functions:
|
|
566
|
+
|
|
567
|
+
- `createUserActions(flux, options?)` - User management
|
|
568
|
+
- `createPostActions(flux, options?)` - Post management
|
|
569
|
+
- `createEventActions(flux, options?)` - Event management
|
|
570
|
+
- `createMessageActions(flux, options?)` - Messaging
|
|
571
|
+
- `createImageActions(flux, options?)` - Image handling
|
|
572
|
+
- `createLocationActions(flux, options?)` - Location services
|
|
573
|
+
- `createReactionActions(flux, options?)` - Reactions
|
|
574
|
+
- `createTagActions(flux, options?)` - Tag management
|
|
575
|
+
- `createWebsocketActions(flux)` - WebSocket connections
|
|
576
|
+
|
|
577
|
+
### Adapter Options Interface
|
|
578
|
+
|
|
579
|
+
All adapters support the same options interface:
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
interface AdapterOptions {
|
|
583
|
+
strict?: boolean; // Enable strict validation
|
|
584
|
+
allowPartial?: boolean; // Allow partial data
|
|
585
|
+
environment?: 'development' | 'production' | 'test';
|
|
586
|
+
customValidation?: (input: unknown) => unknown;
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Migration Guide
|
|
591
|
+
|
|
592
|
+
#### Step 1: Update Imports
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
// Old
|
|
596
|
+
import {userActions} from '../actions/userActions';
|
|
597
|
+
|
|
598
|
+
// New
|
|
599
|
+
import {createUserActions} from '../actions/userActions';
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
#### Step 2: Update Instantiation
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
// Old
|
|
606
|
+
const userActions = new userActions(flux, customAdapter);
|
|
607
|
+
|
|
608
|
+
// New
|
|
609
|
+
const userActions = createUserActions(flux, {
|
|
610
|
+
userAdapter: customAdapter
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
#### Step 3: Using Actions in Components
|
|
615
|
+
|
|
616
|
+
The recommended approach is to use specialized hooks:
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
// Recommended: Use specialized hooks
|
|
620
|
+
import {useUserActions, usePostActions} from '@nlabs/metropolisjs';
|
|
621
|
+
|
|
622
|
+
const MyComponent = () => {
|
|
623
|
+
const userActions = useUserActions();
|
|
624
|
+
const postActions = usePostActions();
|
|
625
|
+
// ...
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// Alternative: Use useMetropolis with selective creation
|
|
629
|
+
import {useMetropolis} from '@nlabs/metropolisjs';
|
|
630
|
+
|
|
631
|
+
const MyComponent = () => {
|
|
632
|
+
const {userActions, postActions} = useMetropolis(['user', 'post']);
|
|
633
|
+
// ...
|
|
634
|
+
};
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Configuration Access
|
|
638
|
+
|
|
639
|
+
Access configuration using React hooks:
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
import {useMetropolisConfig} from '@nlabs/metropolisjs';
|
|
643
|
+
|
|
644
|
+
const MyComponent = () => {
|
|
645
|
+
const config = useMetropolisConfig();
|
|
646
|
+
const apiUrl = config.app?.api?.url;
|
|
647
|
+
// ...
|
|
648
|
+
};
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
For non-React code (actions, utilities), use:
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
import {getConfigFromFlux} from '@nlabs/metropolisjs';
|
|
655
|
+
|
|
656
|
+
const config = getConfigFromFlux(flux);
|
|
657
|
+
const apiUrl = config.app?.api?.url || '';
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Note:** The `Config` class has been removed. Use `useMetropolisConfig()` in React components or `getConfigFromFlux()` in non-React code.
|
|
661
|
+
|
|
662
|
+
### Testing Examples
|
|
663
|
+
|
|
664
|
+
#### Unit Testing Actions
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import {createUserActions} from '@nlabs/metropolisjs';
|
|
668
|
+
|
|
669
|
+
describe('userActions', () => {
|
|
670
|
+
let flux: FluxFramework;
|
|
671
|
+
let userActions: userActions;
|
|
672
|
+
|
|
673
|
+
beforeEach(() => {
|
|
674
|
+
flux = createMockFlux();
|
|
675
|
+
// Store config in flux state for actions to access
|
|
676
|
+
flux.setState('app.config', {
|
|
677
|
+
app: { api: { url: 'http://localhost:3000/app' } }
|
|
678
|
+
});
|
|
679
|
+
userActions = createUserActions(flux);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should add user with validation', async () => {
|
|
683
|
+
const userData = {username: 'test', email: 'test@example.com'};
|
|
684
|
+
const result = await userActions.add(userData);
|
|
685
|
+
expect(result).toBeDefined();
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
#### Testing with Custom Adapters
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
const mockAdapter = jest.fn((input) => ({
|
|
694
|
+
...input,
|
|
695
|
+
validated: true
|
|
696
|
+
}));
|
|
697
|
+
|
|
698
|
+
const userActions = createUserActions(flux, {
|
|
699
|
+
userAdapter: mockAdapter
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
expect(mockAdapter).toHaveBeenCalled();
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
#### Testing React Components
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
import {renderHook} from '@testing-library/react';
|
|
709
|
+
import {Metropolis, useUserActions} from '@nlabs/metropolisjs';
|
|
710
|
+
|
|
711
|
+
describe('useUserActions', () => {
|
|
712
|
+
it('should return user actions', () => {
|
|
713
|
+
const wrapper = ({children}) => (
|
|
714
|
+
<Metropolis config={{development: {app: {api: {url: 'http://localhost'}}}}}>
|
|
715
|
+
{children}
|
|
716
|
+
</Metropolis>
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
const {result} = renderHook(() => useUserActions(), {wrapper});
|
|
720
|
+
expect(result.current).toBeDefined();
|
|
721
|
+
expect(result.current.add).toBeDefined();
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Best Practices
|
|
727
|
+
|
|
728
|
+
1. **Use Specialized Hooks**: Prefer `useUserActions()` over `useMetropolis()` when you only need one action type
|
|
729
|
+
2. **Selective Creation**: Use `useMetropolis(['user', 'post'])` when you need multiple specific actions
|
|
730
|
+
3. **Context-Based Config**: Use `useMetropolisConfig()` for accessing configuration
|
|
731
|
+
4. **Leverage Adapter Injection**: Pass custom adapters through the `Metropolis` component
|
|
732
|
+
5. **Type Safety**: Always use TypeScript interfaces for better type checking
|
|
733
|
+
6. **Error Handling**: Custom adapters should throw meaningful errors
|
|
734
|
+
7. **Wrap Components**: Always wrap components using hooks with `<Metropolis>` provider
|
|
735
|
+
|
|
736
|
+
### Removed APIs
|
|
174
737
|
|
|
175
|
-
|
|
738
|
+
The following APIs have been removed:
|
|
176
739
|
|
|
177
|
-
|
|
740
|
+
- `Config.get()` - Use `useMetropolisConfig()` in React components or `getConfigFromFlux(flux)` in non-React code
|
|
741
|
+
- `Config.set()` - Pass configuration to the `<Metropolis>` component instead
|
|
178
742
|
|
|
179
|
-
|
|
180
|
-
- **messageActions**: Real-time messaging and conversations
|
|
181
|
-
- **postActions**: Social media posts and content
|
|
182
|
-
- **reactionActions**: Likes, reactions, and interactions
|
|
183
|
-
- **tagActions**: Content categorization and discovery
|
|
184
|
-
- **eventActions**: Event management and scheduling
|
|
185
|
-
- **imageActions**: Media upload and management
|
|
186
|
-
- **locationActions**: Geolocation and location-based features
|
|
187
|
-
- **websocketActions**: Real-time communication setup
|
|
743
|
+
### Performance Considerations
|
|
188
744
|
|
|
189
|
-
|
|
745
|
+
- **Specialized hooks** only create the specific action type (best performance)
|
|
746
|
+
- **Selective creation** creates only requested actions
|
|
747
|
+
- Factory functions are lightweight and create minimal overhead
|
|
748
|
+
- Adapter validation is only performed when needed
|
|
749
|
+
- Options are merged efficiently without deep cloning
|
|
750
|
+
- Better tree-shaking opportunities with specialized hooks
|
|
190
751
|
|
|
191
|
-
|
|
752
|
+
## Adapters
|
|
192
753
|
|
|
193
|
-
|
|
194
|
-
- **messageAdapter**: Chat and messaging data
|
|
195
|
-
- **postAdapter**: Social media content
|
|
196
|
-
- **eventAdapter**: Event and scheduling data
|
|
197
|
-
- **imageAdapter**: Media and file data
|
|
198
|
-
- **locationAdapter**: Geolocation data
|
|
199
|
-
- **tagAdapter**: Categorization data
|
|
200
|
-
- **reactionAdapter**: User interaction data
|
|
754
|
+
Customize data transformation with powerful adapters. Pass adapters to the `Metropolis` component:
|
|
201
755
|
|
|
202
|
-
|
|
756
|
+
- **User**: User account and authentication data
|
|
757
|
+
- **Permission**: Permission and role data with RBAC support
|
|
758
|
+
- **Message**: Chat and messaging data
|
|
759
|
+
- **Post**: Social media content
|
|
760
|
+
- **Event**: Event and scheduling data
|
|
761
|
+
- **Image**: Media and file data
|
|
762
|
+
- **Location**: Geolocation data
|
|
763
|
+
- **Tag**: Categorization data
|
|
764
|
+
- **Reaction**: User interaction data
|
|
765
|
+
- **Content**: Content management
|
|
766
|
+
- **Persona**: Persona management
|
|
767
|
+
- **Translation**: Translation data
|
|
768
|
+
|
|
769
|
+
## Real-Time Features
|
|
203
770
|
|
|
204
771
|
### WebSocket Integration
|
|
205
772
|
|
|
206
773
|
```tsx
|
|
207
|
-
|
|
774
|
+
import {useWebsocketActions} from '@nlabs/metropolisjs';
|
|
775
|
+
|
|
776
|
+
const MyComponent = () => {
|
|
777
|
+
const websocketActions = useWebsocketActions();
|
|
208
778
|
|
|
209
|
-
|
|
210
|
-
|
|
779
|
+
useEffect(() => {
|
|
780
|
+
// Initialize real-time connections
|
|
781
|
+
websocketActions.wsInit();
|
|
782
|
+
}, []);
|
|
211
783
|
|
|
212
|
-
// Messages, notifications, and data updates
|
|
213
|
-
// are automatically synchronized across all clients
|
|
784
|
+
// Messages, notifications, and data updates
|
|
785
|
+
// are automatically synchronized across all clients
|
|
786
|
+
};
|
|
214
787
|
```
|
|
215
788
|
|
|
216
789
|
### Server-Sent Events
|
|
217
790
|
|
|
218
791
|
Built-in SSE support for lightweight real-time updates without the overhead of WebSocket connections.
|
|
219
792
|
|
|
220
|
-
##
|
|
793
|
+
## Customization
|
|
221
794
|
|
|
222
|
-
### Custom Adapters
|
|
795
|
+
### Custom Data Adapters
|
|
796
|
+
|
|
797
|
+
You can customize data transformation by providing custom adapters. Adapters are functions that transform and validate data:
|
|
223
798
|
|
|
224
799
|
```tsx
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
800
|
+
// Custom adapter function
|
|
801
|
+
const customUserAdapter = (input: unknown, options?: UserAdapterOptions) => {
|
|
802
|
+
const user = input as any;
|
|
803
|
+
|
|
804
|
+
// Add custom transformation logic
|
|
805
|
+
return {
|
|
806
|
+
...user,
|
|
807
|
+
displayName: `${user.firstName || ''} ${user.lastName || ''}`.trim(),
|
|
808
|
+
customField: 'custom value',
|
|
809
|
+
isVerified: user.email?.endsWith('@company.com')
|
|
810
|
+
};
|
|
811
|
+
};
|
|
236
812
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
adapters
|
|
240
|
-
User:
|
|
241
|
-
|
|
242
|
-
}
|
|
813
|
+
// Pass adapters through the Metropolis component (recommended)
|
|
814
|
+
<Metropolis
|
|
815
|
+
adapters={{
|
|
816
|
+
User: customUserAdapter,
|
|
817
|
+
Message: customMessageAdapter
|
|
818
|
+
}}
|
|
819
|
+
>
|
|
820
|
+
<YourApp />
|
|
821
|
+
</Metropolis>
|
|
243
822
|
```
|
|
244
823
|
|
|
245
|
-
|
|
824
|
+
The adapters will be automatically used by all action hooks. For more details on the factory pattern and adapter customization, see the [Factory Pattern Guide](#-factory-pattern-guide) section above.
|
|
825
|
+
|
|
826
|
+
### Accessing Configuration
|
|
246
827
|
|
|
247
828
|
```tsx
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// Custom authentication logic
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
829
|
+
import {useMetropolisConfig} from '@nlabs/metropolisjs';
|
|
830
|
+
|
|
831
|
+
const MyComponent = () => {
|
|
832
|
+
const config = useMetropolisConfig();
|
|
833
|
+
const apiUrl = config.app?.api?.url;
|
|
834
|
+
const websocketUrl = config.app?.urls?.websocket;
|
|
835
|
+
|
|
836
|
+
// Use configuration values
|
|
837
|
+
return <div>API: {apiUrl}</div>;
|
|
260
838
|
};
|
|
261
839
|
```
|
|
262
840
|
|
|
263
|
-
|
|
841
|
+
**Note:** `useMetropolisConfig()` must be used within a component wrapped by `<Metropolis>`.
|
|
264
842
|
|
|
265
|
-
|
|
266
|
-
- **Intelligent caching** with ArkhamJS
|
|
267
|
-
- **Optimistic updates** for instant UI feedback
|
|
268
|
-
- **Connection pooling** for WebSocket efficiency
|
|
269
|
-
- **Lazy loading** support for large datasets
|
|
843
|
+
## Performance Features
|
|
270
844
|
|
|
271
|
-
|
|
845
|
+
- **Selective Action Creation** - Only create actions you need with specialized hooks
|
|
846
|
+
- **Debounced API calls** - Prevent excessive requests
|
|
847
|
+
- **Intelligent caching** - With ArkhamJS
|
|
848
|
+
- **Optimistic updates** - Instant UI feedback
|
|
849
|
+
- **Connection pooling** - WebSocket efficiency
|
|
850
|
+
- **Lazy loading** - Support for large datasets
|
|
851
|
+
- **Tree-shaking friendly** - Better bundle optimization
|
|
852
|
+
|
|
853
|
+
## Security
|
|
272
854
|
|
|
273
855
|
- **Automatic token refresh** for seamless sessions
|
|
274
856
|
- **Secure WebSocket connections** with authentication
|
|
275
857
|
- **Input validation** and sanitization
|
|
276
858
|
- **CSRF protection** built-in
|
|
277
859
|
- **Session management** with configurable timeouts
|
|
860
|
+
- **5-Level RBAC permission system** for granular access control
|
|
861
|
+
|
|
862
|
+
## Permission System (RBAC)
|
|
863
|
+
|
|
864
|
+
MetropolisJS includes a comprehensive 5-level Role-Based Access Control (RBAC) system that integrates seamlessly with the Reaktor backend. This system provides granular control over user permissions and access levels throughout your application.
|
|
865
|
+
|
|
866
|
+
### Permission Levels
|
|
867
|
+
|
|
868
|
+
The system defines five hierarchical permission levels:
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
enum PermissionLevel {
|
|
872
|
+
GUEST = 0, // Unauthenticated users
|
|
873
|
+
USER = 1, // Authenticated users
|
|
874
|
+
MODERATOR = 2, // Content moderators
|
|
875
|
+
ADMIN = 3, // Application administrators
|
|
876
|
+
SUPER_ADMIN = 4 // System administrators
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
| Level | Name | Value | Description |
|
|
881
|
+
|-------|------|-------|-------------|
|
|
882
|
+
| 0 | Guest | `PermissionLevel.GUEST` | Unauthenticated users with limited read access |
|
|
883
|
+
| 1 | User | `PermissionLevel.USER` | Authenticated users who can create and edit their own content |
|
|
884
|
+
| 2 | Moderator | `PermissionLevel.MODERATOR` | Can moderate content and manage users |
|
|
885
|
+
| 3 | Admin | `PermissionLevel.ADMIN` | Full access to application features |
|
|
886
|
+
| 4 | Super Admin | `PermissionLevel.SUPER_ADMIN` | Complete system access, can manage admins |
|
|
887
|
+
|
|
888
|
+
### Using the Permission System
|
|
889
|
+
|
|
890
|
+
#### 1. Permission Hook
|
|
891
|
+
|
|
892
|
+
The `usePermissions()` hook provides comprehensive permission checking capabilities:
|
|
893
|
+
|
|
894
|
+
```tsx
|
|
895
|
+
import { usePermissions, PermissionLevel } from '@nlabs/metropolisjs';
|
|
896
|
+
|
|
897
|
+
const MyComponent = () => {
|
|
898
|
+
const {
|
|
899
|
+
userLevel, // Current user's permission level
|
|
900
|
+
isGuest, // Boolean checks
|
|
901
|
+
isUser,
|
|
902
|
+
isModerator,
|
|
903
|
+
isAdmin,
|
|
904
|
+
isSuperAdmin,
|
|
905
|
+
hasPermission, // Function to check specific level
|
|
906
|
+
checkResource // Function to check resource-specific permissions
|
|
907
|
+
} = usePermissions();
|
|
908
|
+
|
|
909
|
+
return (
|
|
910
|
+
<div>
|
|
911
|
+
<p>Your level: {userLevel}</p>
|
|
912
|
+
{isAdmin && <button>Admin Panel</button>}
|
|
913
|
+
{hasPermission(PermissionLevel.MODERATOR) && (
|
|
914
|
+
<button>Moderate Content</button>
|
|
915
|
+
)}
|
|
916
|
+
</div>
|
|
917
|
+
);
|
|
918
|
+
};
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
#### 2. Permission Guard Component
|
|
922
|
+
|
|
923
|
+
Conditionally render components based on permission requirements:
|
|
924
|
+
|
|
925
|
+
```tsx
|
|
926
|
+
import { PermissionGuard, PermissionLevel } from '@nlabs/metropolisjs';
|
|
927
|
+
|
|
928
|
+
const ProtectedContent = () => {
|
|
929
|
+
return (
|
|
930
|
+
<>
|
|
931
|
+
<PermissionGuard
|
|
932
|
+
requiredLevel={PermissionLevel.USER}
|
|
933
|
+
fallback={<p>Please log in to view this content</p>}
|
|
934
|
+
>
|
|
935
|
+
<p>This content is visible to authenticated users</p>
|
|
936
|
+
</PermissionGuard>
|
|
937
|
+
|
|
938
|
+
<PermissionGuard
|
|
939
|
+
requiredLevel={PermissionLevel.ADMIN}
|
|
940
|
+
fallback={null} // Hides content completely
|
|
941
|
+
>
|
|
942
|
+
<button>Admin Settings</button>
|
|
943
|
+
</PermissionGuard>
|
|
944
|
+
|
|
945
|
+
<PermissionGuard
|
|
946
|
+
requiredLevel={PermissionLevel.MODERATOR}
|
|
947
|
+
resource="posts" // Resource-specific permission
|
|
948
|
+
fallback={<p>Moderator access required</p>}
|
|
949
|
+
>
|
|
950
|
+
<button>Moderate Posts</button>
|
|
951
|
+
</PermissionGuard>
|
|
952
|
+
</>
|
|
953
|
+
);
|
|
954
|
+
};
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
#### 3. Managing Permissions with Actions
|
|
958
|
+
|
|
959
|
+
Use `usePermissionActions()` to manage permissions programmatically:
|
|
960
|
+
|
|
961
|
+
```tsx
|
|
962
|
+
import { usePermissionActions, PermissionLevel } from '@nlabs/metropolisjs';
|
|
963
|
+
|
|
964
|
+
const PermissionManager = () => {
|
|
965
|
+
const permissionActions = usePermissionActions();
|
|
966
|
+
|
|
967
|
+
const grantModeratorRole = async (userId: string) => {
|
|
968
|
+
try {
|
|
969
|
+
const permission = await permissionActions.add({
|
|
970
|
+
userId,
|
|
971
|
+
name: 'Moderator Role',
|
|
972
|
+
level: PermissionLevel.MODERATOR,
|
|
973
|
+
resource: 'posts',
|
|
974
|
+
description: 'Can moderate posts and comments'
|
|
975
|
+
});
|
|
976
|
+
console.log('Permission granted:', permission);
|
|
977
|
+
} catch (error) {
|
|
978
|
+
console.error('Failed to grant permission:', error);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
const checkUserAccess = async (userId: string) => {
|
|
983
|
+
const hasAccess = await permissionActions.check(
|
|
984
|
+
userId,
|
|
985
|
+
'posts',
|
|
986
|
+
PermissionLevel.MODERATOR
|
|
987
|
+
);
|
|
988
|
+
return hasAccess;
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
const loadUserPermissions = async (userId: string) => {
|
|
992
|
+
const permissions = await permissionActions.listByUser(userId);
|
|
993
|
+
return permissions;
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
return (
|
|
997
|
+
<div>
|
|
998
|
+
<button onClick={() => grantModeratorRole('user123')}>
|
|
999
|
+
Grant Moderator Permission
|
|
1000
|
+
</button>
|
|
1001
|
+
</div>
|
|
1002
|
+
);
|
|
1003
|
+
};
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
#### 4. Resource-Specific Permissions
|
|
1007
|
+
|
|
1008
|
+
The permission system supports resource-specific access control:
|
|
1009
|
+
|
|
1010
|
+
```tsx
|
|
1011
|
+
import { usePermissions, PermissionLevel } from '@nlabs/metropolisjs';
|
|
1012
|
+
|
|
1013
|
+
const ResourceProtectedComponent = () => {
|
|
1014
|
+
const { checkResource } = usePermissions();
|
|
1015
|
+
|
|
1016
|
+
const canEditPosts = checkResource('posts', PermissionLevel.MODERATOR);
|
|
1017
|
+
const canDeleteUsers = checkResource('users', PermissionLevel.ADMIN);
|
|
1018
|
+
const canViewReports = checkResource('reports', PermissionLevel.MODERATOR);
|
|
1019
|
+
|
|
1020
|
+
return (
|
|
1021
|
+
<div>
|
|
1022
|
+
{canEditPosts && <button>Edit Post</button>}
|
|
1023
|
+
{canDeleteUsers && <button>Delete User</button>}
|
|
1024
|
+
{canViewReports && <button>View Reports</button>}
|
|
1025
|
+
</div>
|
|
1026
|
+
);
|
|
1027
|
+
};
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
### Backward Compatibility
|
|
1031
|
+
|
|
1032
|
+
The permission system is fully backward compatible with the existing `userAccess` field in the User model. The `userAccess` number (0-4) directly maps to the `PermissionLevel` enum values, ensuring seamless integration with existing code.
|
|
1033
|
+
|
|
1034
|
+
### Available Permission Actions
|
|
1035
|
+
|
|
1036
|
+
The `usePermissionActions()` hook provides the following methods:
|
|
1037
|
+
|
|
1038
|
+
- **`add(permissionData)`** - Grant a new permission
|
|
1039
|
+
- **`check(userId, resource, requiredLevel)`** - Check if a user has access
|
|
1040
|
+
- **`itemById(permissionId)`** - Retrieve a specific permission
|
|
1041
|
+
- **`list(from?, to?)`** - List all permissions with pagination
|
|
1042
|
+
- **`listByUser(userId)`** - Get all permissions for a specific user
|
|
1043
|
+
- **`remove(permissionId)`** - Revoke a permission
|
|
1044
|
+
- **`update(permission)`** - Update an existing permission
|
|
1045
|
+
|
|
1046
|
+
### Integration with Reaktor Backend
|
|
1047
|
+
|
|
1048
|
+
The permission system integrates with the Reaktor backend through GraphQL mutations and queries:
|
|
1049
|
+
|
|
1050
|
+
```graphql
|
|
1051
|
+
# Grant permission
|
|
1052
|
+
mutation {
|
|
1053
|
+
permissions {
|
|
1054
|
+
add(permission: {
|
|
1055
|
+
userId: "user123"
|
|
1056
|
+
level: 2
|
|
1057
|
+
resource: "posts"
|
|
1058
|
+
name: "Moderator"
|
|
1059
|
+
}) {
|
|
1060
|
+
permissionId
|
|
1061
|
+
level
|
|
1062
|
+
resource
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
# Check permission
|
|
1068
|
+
query {
|
|
1069
|
+
permissions {
|
|
1070
|
+
check(
|
|
1071
|
+
userId: "user123"
|
|
1072
|
+
resource: "posts"
|
|
1073
|
+
requiredLevel: 2
|
|
1074
|
+
)
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
# List user permissions
|
|
1079
|
+
query {
|
|
1080
|
+
permissions {
|
|
1081
|
+
listByUser(userId: "user123") {
|
|
1082
|
+
permissionId
|
|
1083
|
+
name
|
|
1084
|
+
level
|
|
1085
|
+
resource
|
|
1086
|
+
description
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
### Best Practices
|
|
1093
|
+
|
|
1094
|
+
1. **Use Permission Guards for UI**: Wrap sensitive UI elements with `<PermissionGuard>` components
|
|
1095
|
+
2. **Check Permissions in Actions**: Always verify permissions before executing sensitive operations
|
|
1096
|
+
3. **Resource-Specific Permissions**: Use resource-based permissions for fine-grained control
|
|
1097
|
+
4. **Leverage Boolean Helpers**: Use `isAdmin`, `isModerator`, etc. for cleaner code
|
|
1098
|
+
5. **Test Permission Logic**: Write tests for permission checks to ensure security
|
|
1099
|
+
6. **Document Required Levels**: Clearly document what permission level each feature requires
|
|
1100
|
+
|
|
1101
|
+
### Example: Complete Permission Workflow
|
|
1102
|
+
|
|
1103
|
+
```tsx
|
|
1104
|
+
import {
|
|
1105
|
+
Metropolis,
|
|
1106
|
+
usePermissions,
|
|
1107
|
+
usePermissionActions,
|
|
1108
|
+
PermissionGuard,
|
|
1109
|
+
PermissionLevel
|
|
1110
|
+
} from '@nlabs/metropolisjs';
|
|
1111
|
+
|
|
1112
|
+
const App = () => {
|
|
1113
|
+
return (
|
|
1114
|
+
<Metropolis config={{/* your config */}}>
|
|
1115
|
+
<Dashboard />
|
|
1116
|
+
</Metropolis>
|
|
1117
|
+
);
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
const Dashboard = () => {
|
|
1121
|
+
const { isAdmin, isModerator, userLevel } = usePermissions();
|
|
1122
|
+
const permissionActions = usePermissionActions();
|
|
1123
|
+
|
|
1124
|
+
return (
|
|
1125
|
+
<div>
|
|
1126
|
+
<h1>Dashboard - Level: {userLevel}</h1>
|
|
1127
|
+
|
|
1128
|
+
{/* Everyone sees this */}
|
|
1129
|
+
<section>
|
|
1130
|
+
<h2>Public Content</h2>
|
|
1131
|
+
</section>
|
|
1132
|
+
|
|
1133
|
+
{/* Only authenticated users */}
|
|
1134
|
+
<PermissionGuard requiredLevel={PermissionLevel.USER}>
|
|
1135
|
+
<section>
|
|
1136
|
+
<h2>User Content</h2>
|
|
1137
|
+
<button>Create Post</button>
|
|
1138
|
+
</section>
|
|
1139
|
+
</PermissionGuard>
|
|
1140
|
+
|
|
1141
|
+
{/* Only moderators and above */}
|
|
1142
|
+
{isModerator && (
|
|
1143
|
+
<section>
|
|
1144
|
+
<h2>Moderation Tools</h2>
|
|
1145
|
+
<button>Review Content</button>
|
|
1146
|
+
</section>
|
|
1147
|
+
)}
|
|
1148
|
+
|
|
1149
|
+
{/* Only admins */}
|
|
1150
|
+
{isAdmin && (
|
|
1151
|
+
<section>
|
|
1152
|
+
<h2>Admin Panel</h2>
|
|
1153
|
+
<button>Manage Users</button>
|
|
1154
|
+
<button>System Settings</button>
|
|
1155
|
+
</section>
|
|
1156
|
+
)}
|
|
1157
|
+
</div>
|
|
1158
|
+
);
|
|
1159
|
+
};
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
For more detailed examples, see [`examples/permission-system-usage.tsx`](./examples/permission-system-usage.tsx).
|
|
278
1163
|
|
|
279
1164
|
## 📦 Installation & Setup
|
|
280
1165
|
|
|
@@ -296,13 +1181,15 @@ npm install --save-dev @types/react @types/node
|
|
|
296
1181
|
|
|
297
1182
|
### Environment Setup
|
|
298
1183
|
|
|
299
|
-
|
|
300
|
-
# Set your environment variables
|
|
301
|
-
export NODE_ENV=development
|
|
302
|
-
export REAKTOR_API_URL=your-reaktor-backend-url
|
|
303
|
-
```
|
|
1184
|
+
MetropolisJS automatically detects the environment from:
|
|
304
1185
|
|
|
305
|
-
|
|
1186
|
+
- `process.env.stage` (if set)
|
|
1187
|
+
- `process.env.NODE_ENV` (fallback)
|
|
1188
|
+
- Defaults to `'local'` if none are set
|
|
1189
|
+
|
|
1190
|
+
Configure your environment-specific settings in the `config` prop of the `Metropolis` component.
|
|
1191
|
+
|
|
1192
|
+
## Contributing
|
|
306
1193
|
|
|
307
1194
|
We love contributions! Here's how you can help:
|
|
308
1195
|
|
|
@@ -312,21 +1199,145 @@ We love contributions! Here's how you can help:
|
|
|
312
1199
|
4. **Push** to the branch (`git push origin feature/amazing-feature`)
|
|
313
1200
|
5. **Open** a Pull Request
|
|
314
1201
|
|
|
315
|
-
##
|
|
1202
|
+
## License
|
|
316
1203
|
|
|
317
1204
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
318
1205
|
|
|
319
|
-
##
|
|
1206
|
+
## Additional Documentation
|
|
1207
|
+
|
|
1208
|
+
- **[Factory Pattern Guide](./factoryPatternGuide.md)** - Detailed guide on using the factory pattern
|
|
1209
|
+
- **[Architecture Analysis](./ARCHITECTURE_ANALYSIS.md)** - Deep dive into the architecture
|
|
1210
|
+
- **[Changelog](./CHANGELOG.md)** - Complete list of changes and improvements
|
|
320
1211
|
|
|
321
|
-
|
|
1212
|
+
## Support
|
|
1213
|
+
|
|
1214
|
+
- **Documentation**: See above for detailed guides
|
|
322
1215
|
- **Issues**: [GitHub Issues](https://github.com/nitrogenlabs/metropolisjs/issues)
|
|
323
1216
|
- **Discussions**: [GitHub Discussions](https://github.com/nitrogenlabs/metropolisjs/discussions)
|
|
324
1217
|
- **Email**: <giraldo@nitrogenlabs.com>
|
|
325
1218
|
|
|
326
|
-
##
|
|
1219
|
+
## About Nitrogen Labs
|
|
327
1220
|
|
|
328
1221
|
MetropolisJS is proudly developed by [Nitrogen Labs](http://nitrogenlabs.com), a team passionate about building powerful, developer-friendly tools that make web development faster, more reliable, and more enjoyable.
|
|
329
1222
|
|
|
330
1223
|
---
|
|
331
1224
|
|
|
332
1225
|
**Ready to build the future?** Start with MetropolisJS today and experience the power of seamless frontend-backend integration! 🚀
|
|
1226
|
+
|
|
1227
|
+
## CRUD Operations
|
|
1228
|
+
|
|
1229
|
+
MetropolisJS now provides comprehensive CRUD (Create, Read, Update, Delete) operations for all Reaktor collections:
|
|
1230
|
+
|
|
1231
|
+
### Supported Collections
|
|
1232
|
+
|
|
1233
|
+
- **users** - User accounts and authentication data
|
|
1234
|
+
- **posts** - User posts and content
|
|
1235
|
+
- **groups** - Group entities and communities
|
|
1236
|
+
- **messages** - Direct messages
|
|
1237
|
+
- **conversations** - Conversation threads
|
|
1238
|
+
- **files** - File attachments
|
|
1239
|
+
- **images** - Image media
|
|
1240
|
+
- **videos** - Video media
|
|
1241
|
+
- **apps** - Application entities
|
|
1242
|
+
- **personas** - Extended user persona details
|
|
1243
|
+
- **tags** - Tag entities for categorization
|
|
1244
|
+
|
|
1245
|
+
### Basic CRUD Example
|
|
1246
|
+
|
|
1247
|
+
```tsx
|
|
1248
|
+
import {useFlux} from '@nlabs/arkhamjs-utils-react';
|
|
1249
|
+
import {createPostActions, createGroupActions} from '@nlabs/metropolisjs';
|
|
1250
|
+
|
|
1251
|
+
const MyComponent = () => {
|
|
1252
|
+
const flux = useFlux();
|
|
1253
|
+
const postActions = createPostActions(flux);
|
|
1254
|
+
const groupActions = createGroupActions(flux);
|
|
1255
|
+
|
|
1256
|
+
// Create
|
|
1257
|
+
const createPost = async () => {
|
|
1258
|
+
const post = await postActions.add({
|
|
1259
|
+
content: "Hello World",
|
|
1260
|
+
userId: "user123",
|
|
1261
|
+
name: "My First Post"
|
|
1262
|
+
});
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
// Read
|
|
1266
|
+
const getPost = async (postId: string) => {
|
|
1267
|
+
const post = await postActions.itemById(postId);
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// Update
|
|
1271
|
+
const updatePost = async (postId: string) => {
|
|
1272
|
+
await postActions.update({
|
|
1273
|
+
postId,
|
|
1274
|
+
content: "Updated content"
|
|
1275
|
+
});
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
// Delete
|
|
1279
|
+
const deletePost = async (postId: string) => {
|
|
1280
|
+
await postActions.delete(postId);
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
// List with pagination
|
|
1284
|
+
const listPosts = async () => {
|
|
1285
|
+
const posts = await postActions.listByLatest(0, 10);
|
|
1286
|
+
};
|
|
1287
|
+
};
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
### Relationship Management
|
|
1291
|
+
|
|
1292
|
+
Connect and manage relationships between collections:
|
|
1293
|
+
|
|
1294
|
+
```tsx
|
|
1295
|
+
import {createConnectionActions, CONNECTION_TYPES} from '@nlabs/metropolisjs';
|
|
1296
|
+
|
|
1297
|
+
const connectionActions = createConnectionActions(flux);
|
|
1298
|
+
|
|
1299
|
+
// Add user to group
|
|
1300
|
+
await connectionActions.addConnection(
|
|
1301
|
+
'users',
|
|
1302
|
+
userId,
|
|
1303
|
+
'groups',
|
|
1304
|
+
groupId,
|
|
1305
|
+
CONNECTION_TYPES.MEMBER
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
// Get all connections
|
|
1309
|
+
const connections = await connectionActions.getConnections('users', userId);
|
|
1310
|
+
|
|
1311
|
+
// Remove connection
|
|
1312
|
+
await connectionActions.removeConnection('users', userId, 'groups', groupId);
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
### Extensible Fields
|
|
1316
|
+
|
|
1317
|
+
All CRUD operations support custom fields:
|
|
1318
|
+
|
|
1319
|
+
```tsx
|
|
1320
|
+
const post = await postActions.add({
|
|
1321
|
+
content: "Hello",
|
|
1322
|
+
userId: "user123",
|
|
1323
|
+
// Custom fields
|
|
1324
|
+
customField1: "value",
|
|
1325
|
+
customField2: 42,
|
|
1326
|
+
metadata: {
|
|
1327
|
+
source: "mobile",
|
|
1328
|
+
version: "1.0"
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
```
|
|
1332
|
+
|
|
1333
|
+
### Documentation
|
|
1334
|
+
|
|
1335
|
+
For comprehensive guides and examples, see:
|
|
1336
|
+
|
|
1337
|
+
- [CRUD Integration Guide](./docs/CRUD_INTEGRATION.md) - Complete guide to CRUD operations
|
|
1338
|
+
- [Actions Reference](./docs/ACTIONS.md) - Action families, hooks, creators, and method map
|
|
1339
|
+
- [Collections Reference](./docs/COLLECTIONS.md) - Detailed information about each collection
|
|
1340
|
+
- [Connections Guide](./docs/CONNECTIONS.md) - Managing relationships between collections
|
|
1341
|
+
- [CRUD Examples](./examples/crud-usage.tsx) - Practical code examples
|
|
1342
|
+
- [Connection Examples](./examples/connections-usage.tsx) - Relationship management examples
|
|
1343
|
+
- [Extensibility Examples](./examples/extensibility-usage.tsx) - Custom field examples
|