@nlabs/metropolisjs 0.1.1 → 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 +1343 -0
- 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/factory-pattern-usage.ts +283 -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/factoryPatternGuide.md +244 -0
- package/index.js +1 -0
- package/jest.config.mjs +28 -0
- package/lex.config.mjs +26 -0
- 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 +65 -0
- package/lib/actions/eventActions/eventActions.d.ts.map +1 -0
- package/lib/actions/eventActions/eventActions.js +436 -0
- 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 +62 -0
- package/lib/actions/imageActions/imageActions.d.ts.map +1 -0
- package/lib/actions/imageActions/imageActions.js +593 -0
- package/lib/actions/index.d.ts +21 -0
- package/lib/actions/index.d.ts.map +1 -0
- package/lib/actions/index.js +22 -0
- package/lib/actions/locationActions/locationActions.d.ts +60 -0
- package/lib/actions/locationActions/locationActions.d.ts.map +1 -0
- package/lib/actions/locationActions/locationActions.js +388 -0
- package/lib/actions/messageActions/messageActions.d.ts +38 -0
- package/lib/actions/messageActions/messageActions.d.ts.map +1 -0
- package/lib/actions/messageActions/messageActions.js +235 -0
- 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 +36 -0
- package/lib/actions/postActions/postActions.d.ts.map +1 -0
- package/lib/actions/postActions/postActions.js +447 -0
- package/lib/actions/reactionActions/reactionActions.d.ts +38 -0
- package/lib/actions/reactionActions/reactionActions.d.ts.map +1 -0
- package/lib/actions/reactionActions/reactionActions.js +413 -0
- package/lib/actions/sseActions/sseActions.d.ts +38 -0
- package/lib/actions/sseActions/sseActions.d.ts.map +1 -0
- package/lib/actions/sseActions/sseActions.js +148 -0
- 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 +37 -0
- package/lib/actions/tagActions/tagActions.d.ts.map +1 -0
- package/lib/actions/tagActions/tagActions.js +347 -0
- 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 +78 -0
- package/lib/actions/userActions/userActions.d.ts.map +1 -0
- package/lib/actions/userActions/userActions.js +844 -0
- 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 +30 -0
- package/lib/actions/websocketActions/websocketActions.d.ts.map +1 -0
- package/lib/actions/websocketActions/websocketActions.js +377 -0
- 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 +21 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.js +75 -0
- package/lib/adapters/contentAdapter/contentAdapter.d.ts +32 -0
- package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -0
- package/lib/adapters/contentAdapter/contentAdapter.js +107 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +24 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.js +75 -0
- package/lib/adapters/eventAdapter/eventAdapter.d.ts +46 -0
- package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -0
- package/lib/adapters/eventAdapter/eventAdapter.js +162 -0
- package/lib/adapters/fileAdapter/fileAdapter.d.ts +30 -0
- package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -0
- package/lib/adapters/fileAdapter/fileAdapter.js +118 -0
- 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 +38 -0
- package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -0
- package/lib/adapters/imageAdapter/imageAdapter.js +134 -0
- package/lib/adapters/index.d.ts +34 -0
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/adapters/index.js +19 -0
- package/lib/adapters/locationAdapter/locationAdapter.d.ts +28 -0
- package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -0
- package/lib/adapters/locationAdapter/locationAdapter.js +95 -0
- package/lib/adapters/messageAdapter/messageAdapter.d.ts +31 -0
- package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -0
- package/lib/adapters/messageAdapter/messageAdapter.js +82 -0
- 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 +44 -0
- package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -0
- package/lib/adapters/personaAdapter/personaAdapter.js +161 -0
- package/lib/adapters/postAdapter/postAdapter.d.ts +33 -0
- package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -0
- package/lib/adapters/postAdapter/postAdapter.js +115 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +23 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.js +81 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +17 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.js +58 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +24 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.js +80 -0
- package/lib/adapters/tagAdapter/tagAdapter.d.ts +21 -0
- package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -0
- package/lib/adapters/tagAdapter/tagAdapter.js +98 -0
- 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 +67 -0
- package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -0
- package/lib/adapters/userAdapter/userAdapter.js +302 -0
- 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 +44 -0
- package/lib/config/index.d.ts.map +1 -0
- package/lib/config/index.js +97 -0
- 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 +15 -0
- package/lib/constants/MetropolisConstants.d.ts.map +1 -0
- package/lib/constants/MetropolisConstants.js +15 -0
- package/lib/graphql/message.d.ts +3 -0
- package/lib/graphql/message.d.ts.map +1 -0
- package/lib/graphql/message.js +20 -0
- package/lib/graphql/notification.d.ts +3 -0
- package/lib/graphql/notification.d.ts.map +1 -0
- package/lib/graphql/notification.js +20 -0
- package/lib/graphql/session.d.ts +2 -0
- package/lib/graphql/session.d.ts.map +1 -0
- package/lib/graphql/session.js +17 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +248 -0
- package/lib/stores/appStore.d.ts +23 -0
- package/lib/stores/appStore.d.ts.map +1 -0
- package/lib/stores/appStore.js +23 -0
- 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 +37 -0
- package/lib/stores/eventStore.d.ts.map +1 -0
- package/lib/stores/eventStore.js +47 -0
- 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 +40 -0
- package/lib/stores/imageStore.d.ts.map +1 -0
- package/lib/stores/imageStore.js +53 -0
- package/lib/stores/index.d.ts +24 -0
- package/lib/stores/index.d.ts.map +1 -0
- package/lib/stores/index.js +24 -0
- package/lib/stores/locationStore.d.ts +34 -0
- package/lib/stores/locationStore.d.ts.map +1 -0
- package/lib/stores/locationStore.js +42 -0
- package/lib/stores/messageStore.d.ts +51 -0
- package/lib/stores/messageStore.d.ts.map +1 -0
- package/lib/stores/messageStore.js +137 -0
- 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 +34 -0
- package/lib/stores/postStore.d.ts.map +1 -0
- package/lib/stores/postStore.js +53 -0
- package/lib/stores/reactionStore.d.ts +25 -0
- package/lib/stores/reactionStore.d.ts.map +1 -0
- package/lib/stores/reactionStore.js +28 -0
- 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 +34 -0
- package/lib/stores/tagStore.d.ts.map +1 -0
- package/lib/stores/tagStore.js +87 -0
- 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 +74 -0
- package/lib/stores/userStore.d.ts.map +1 -0
- package/lib/stores/userStore.js +319 -0
- 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 +24 -0
- package/lib/stores/websocketStore.d.ts.map +1 -0
- package/lib/stores/websocketStore.js +51 -0
- 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 +58 -0
- package/lib/utils/MetropolisProvider.d.ts.map +1 -0
- package/lib/utils/MetropolisProvider.js +31 -0
- package/lib/utils/actionFactory.d.ts +24 -0
- package/lib/utils/actionFactory.d.ts.map +1 -0
- package/lib/utils/actionFactory.js +95 -0
- package/lib/utils/api.d.ts +44 -0
- package/lib/utils/api.d.ts.map +1 -0
- package/lib/utils/api.js +253 -0
- package/lib/utils/app.d.ts +7 -0
- package/lib/utils/app.d.ts.map +1 -0
- package/lib/utils/app.js +15 -0
- package/lib/utils/baseActionFactory.d.ts +16 -0
- package/lib/utils/baseActionFactory.d.ts.map +1 -0
- package/lib/utils/baseActionFactory.js +72 -0
- 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 +3 -0
- package/lib/utils/dateUtils.d.ts.map +1 -0
- package/lib/utils/dateUtils.js +11 -0
- package/lib/utils/file.d.ts +3 -0
- package/lib/utils/file.d.ts.map +1 -0
- package/lib/utils/file.js +156 -0
- 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 +10 -0
- package/lib/utils/location.d.ts.map +1 -0
- package/lib/utils/location.js +82 -0
- 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 -0
- package/lib/utils/useMetropolis.d.ts.map +1 -0
- package/lib/utils/useMetropolis.js +311 -0
- 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 +49 -0
- package/lib/utils/validatorFactory.d.ts.map +1 -0
- package/lib/utils/validatorFactory.js +95 -0
- package/package.json +75 -28
- package/temp_signin.ts +73 -0
- package/tsconfig.lint.json +25 -0
- package/tsconfig.test.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,1343 @@
|
|
|
1
|
+
# MetropolisJS: Seamless Frontend-Backend Integration Framework
|
|
2
|
+
|
|
3
|
+
> **The Ultimate Frontend Integration Library for Modern Web Applications**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@nlabs/metropolisjs)
|
|
6
|
+
[](https://www.npmjs.com/package/@nlabs/metropolisjs)
|
|
7
|
+
[](https://github.com/nitrogenlabs/metropolisjs/issues)
|
|
8
|
+
[](https://github.com/ellerbrock/typescript-badges/)
|
|
9
|
+
[](http://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://discord.gg/Ttgev58)
|
|
11
|
+
|
|
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
|
+
|
|
14
|
+
## Why MetropolisJS?
|
|
15
|
+
|
|
16
|
+
### **Seamless Integration**
|
|
17
|
+
|
|
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
|
+
|
|
20
|
+
### **Real-Time Everything**
|
|
21
|
+
|
|
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
|
+
|
|
24
|
+
### **Type-Safe & Reliable**
|
|
25
|
+
|
|
26
|
+
Full TypeScript support with comprehensive type definitions. Catch errors at compile time, not runtime.
|
|
27
|
+
|
|
28
|
+
### **Developer Experience First**
|
|
29
|
+
|
|
30
|
+
Clean, intuitive APIs that make complex operations feel simple. Focus on building features, not boilerplate.
|
|
31
|
+
|
|
32
|
+
## What Can You Build?
|
|
33
|
+
MetropolisJS powers applications that need:
|
|
34
|
+
|
|
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
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @nlabs/metropolisjs @nlabs/arkhamjs @nlabs/arkhamjs-utils-react
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Basic Setup
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import {Metropolis, useUserActions, useMessageActions, useWebsocketActions} from '@nlabs/metropolisjs';
|
|
57
|
+
|
|
58
|
+
const App = () => {
|
|
59
|
+
return (
|
|
60
|
+
<Metropolis config={{
|
|
61
|
+
development: {
|
|
62
|
+
environment: 'development',
|
|
63
|
+
app: {
|
|
64
|
+
api: {
|
|
65
|
+
url: 'http://localhost:3000/app',
|
|
66
|
+
public: 'http://localhost:3000/public'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}}>
|
|
71
|
+
<YourApp />
|
|
72
|
+
</Metropolis>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const YourApp = () => {
|
|
77
|
+
// Use specialized hooks for better performance
|
|
78
|
+
const userActions = useUserActions();
|
|
79
|
+
const messageActions = useMessageActions();
|
|
80
|
+
const websocketActions = useWebsocketActions();
|
|
81
|
+
|
|
82
|
+
// Start building amazing features!
|
|
83
|
+
return <div>Your app content</div>;
|
|
84
|
+
};
|
|
85
|
+
```
|
|
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
|
+
|
|
319
|
+
### User Authentication Example
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
import {useUserActions} from '@nlabs/metropolisjs';
|
|
323
|
+
|
|
324
|
+
const LoginForm = () => {
|
|
325
|
+
const userActions = useUserActions(); // Specialized hook - only creates user actions
|
|
326
|
+
const [username, setUsername] = useState('');
|
|
327
|
+
const [password, setPassword] = useState('');
|
|
328
|
+
|
|
329
|
+
const handleLogin = async () => {
|
|
330
|
+
try {
|
|
331
|
+
const session = await userActions.signIn(username, password);
|
|
332
|
+
console.log('User logged in successfully!', session);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error('Login failed:', error);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<form onSubmit={handleLogin}>
|
|
340
|
+
<input
|
|
341
|
+
value={username}
|
|
342
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
343
|
+
placeholder="Username"
|
|
344
|
+
/>
|
|
345
|
+
<input
|
|
346
|
+
type="password"
|
|
347
|
+
value={password}
|
|
348
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
349
|
+
placeholder="Password"
|
|
350
|
+
/>
|
|
351
|
+
<button type="submit">Sign In</button>
|
|
352
|
+
</form>
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Real-Time Messaging
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
import {useMessageActions, useWebsocketActions} from '@nlabs/metropolisjs';
|
|
361
|
+
|
|
362
|
+
const ChatComponent = () => {
|
|
363
|
+
const messageActions = useMessageActions();
|
|
364
|
+
const websocketActions = useWebsocketActions();
|
|
365
|
+
const [messages, setMessages] = useState([]);
|
|
366
|
+
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
// Initialize WebSocket connection
|
|
369
|
+
websocketActions.wsInit();
|
|
370
|
+
|
|
371
|
+
// Load existing messages
|
|
372
|
+
messageActions.list().then(setMessages);
|
|
373
|
+
}, []);
|
|
374
|
+
|
|
375
|
+
const sendMessage = async (content) => {
|
|
376
|
+
await messageActions.add({ content });
|
|
377
|
+
// Message automatically appears in real-time for all connected users!
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<div>
|
|
382
|
+
{messages.map(message => (
|
|
383
|
+
<div key={message.messageId}>{message.content}</div>
|
|
384
|
+
))}
|
|
385
|
+
<button onClick={() => sendMessage('Hello World!')}>
|
|
386
|
+
Send Message
|
|
387
|
+
</button>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
};
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Architecture
|
|
394
|
+
|
|
395
|
+
MetropolisJS is built on a powerful three-layer architecture with modern React patterns:
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
399
|
+
│ React App │ │ MetropolisJS │ │ Reaktor │
|
|
400
|
+
│ │◄──►│ │◄──►│ Backend │
|
|
401
|
+
│ UI Layer │ │ Integration │ │ Services │
|
|
402
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
403
|
+
│ │
|
|
404
|
+
│ ▼
|
|
405
|
+
│ ┌─────────────────┐
|
|
406
|
+
│ │ ArkhamJS │
|
|
407
|
+
│ │ Data Store │
|
|
408
|
+
│ │ │
|
|
409
|
+
│ └─────────────────┘
|
|
410
|
+
│
|
|
411
|
+
▼
|
|
412
|
+
┌─────────────────┐
|
|
413
|
+
│ React Context │
|
|
414
|
+
│ (Config/Flux) │
|
|
415
|
+
└─────────────────┘
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Core Components
|
|
419
|
+
|
|
420
|
+
- **Actions**: Handle all API interactions and business logic (factory pattern)
|
|
421
|
+
- **Adapters**: Transform data between frontend and backend formats
|
|
422
|
+
- **Stores**: Manage application state with ArkhamJS
|
|
423
|
+
- **WebSocket Actions**: Handle real-time communication
|
|
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
|
|
737
|
+
|
|
738
|
+
The following APIs have been removed:
|
|
739
|
+
|
|
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
|
|
742
|
+
|
|
743
|
+
### Performance Considerations
|
|
744
|
+
|
|
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
|
|
751
|
+
|
|
752
|
+
## Adapters
|
|
753
|
+
|
|
754
|
+
Customize data transformation with powerful adapters. Pass adapters to the `Metropolis` component:
|
|
755
|
+
|
|
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
|
|
770
|
+
|
|
771
|
+
### WebSocket Integration
|
|
772
|
+
|
|
773
|
+
```tsx
|
|
774
|
+
import {useWebsocketActions} from '@nlabs/metropolisjs';
|
|
775
|
+
|
|
776
|
+
const MyComponent = () => {
|
|
777
|
+
const websocketActions = useWebsocketActions();
|
|
778
|
+
|
|
779
|
+
useEffect(() => {
|
|
780
|
+
// Initialize real-time connections
|
|
781
|
+
websocketActions.wsInit();
|
|
782
|
+
}, []);
|
|
783
|
+
|
|
784
|
+
// Messages, notifications, and data updates
|
|
785
|
+
// are automatically synchronized across all clients
|
|
786
|
+
};
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### Server-Sent Events
|
|
790
|
+
|
|
791
|
+
Built-in SSE support for lightweight real-time updates without the overhead of WebSocket connections.
|
|
792
|
+
|
|
793
|
+
## Customization
|
|
794
|
+
|
|
795
|
+
### Custom Data Adapters
|
|
796
|
+
|
|
797
|
+
You can customize data transformation by providing custom adapters. Adapters are functions that transform and validate data:
|
|
798
|
+
|
|
799
|
+
```tsx
|
|
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
|
+
};
|
|
812
|
+
|
|
813
|
+
// Pass adapters through the Metropolis component (recommended)
|
|
814
|
+
<Metropolis
|
|
815
|
+
adapters={{
|
|
816
|
+
User: customUserAdapter,
|
|
817
|
+
Message: customMessageAdapter
|
|
818
|
+
}}
|
|
819
|
+
>
|
|
820
|
+
<YourApp />
|
|
821
|
+
</Metropolis>
|
|
822
|
+
```
|
|
823
|
+
|
|
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
|
|
827
|
+
|
|
828
|
+
```tsx
|
|
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>;
|
|
838
|
+
};
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**Note:** `useMetropolisConfig()` must be used within a component wrapped by `<Metropolis>`.
|
|
842
|
+
|
|
843
|
+
## Performance Features
|
|
844
|
+
|
|
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
|
|
854
|
+
|
|
855
|
+
- **Automatic token refresh** for seamless sessions
|
|
856
|
+
- **Secure WebSocket connections** with authentication
|
|
857
|
+
- **Input validation** and sanitization
|
|
858
|
+
- **CSRF protection** built-in
|
|
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).
|
|
1163
|
+
|
|
1164
|
+
## 📦 Installation & Setup
|
|
1165
|
+
|
|
1166
|
+
### Prerequisites
|
|
1167
|
+
|
|
1168
|
+
- Node.js 16+
|
|
1169
|
+
- React 18+
|
|
1170
|
+
- TypeScript 4.5+
|
|
1171
|
+
|
|
1172
|
+
### Full Installation
|
|
1173
|
+
|
|
1174
|
+
```bash
|
|
1175
|
+
# Install MetropolisJS and dependencies
|
|
1176
|
+
npm install @nlabs/metropolisjs @nlabs/arkhamjs @nlabs/arkhamjs-utils-react
|
|
1177
|
+
|
|
1178
|
+
# For development
|
|
1179
|
+
npm install --save-dev @types/react @types/node
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
### Environment Setup
|
|
1183
|
+
|
|
1184
|
+
MetropolisJS automatically detects the environment from:
|
|
1185
|
+
|
|
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
|
|
1193
|
+
|
|
1194
|
+
We love contributions! Here's how you can help:
|
|
1195
|
+
|
|
1196
|
+
1. **Fork** the repository
|
|
1197
|
+
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
|
1198
|
+
3. **Commit** your changes (`git commit -m 'Add amazing feature'`)
|
|
1199
|
+
4. **Push** to the branch (`git push origin feature/amazing-feature`)
|
|
1200
|
+
5. **Open** a Pull Request
|
|
1201
|
+
|
|
1202
|
+
## License
|
|
1203
|
+
|
|
1204
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
1205
|
+
|
|
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
|
|
1211
|
+
|
|
1212
|
+
## Support
|
|
1213
|
+
|
|
1214
|
+
- **Documentation**: See above for detailed guides
|
|
1215
|
+
- **Issues**: [GitHub Issues](https://github.com/nitrogenlabs/metropolisjs/issues)
|
|
1216
|
+
- **Discussions**: [GitHub Discussions](https://github.com/nitrogenlabs/metropolisjs/discussions)
|
|
1217
|
+
- **Email**: <giraldo@nitrogenlabs.com>
|
|
1218
|
+
|
|
1219
|
+
## About Nitrogen Labs
|
|
1220
|
+
|
|
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.
|
|
1222
|
+
|
|
1223
|
+
---
|
|
1224
|
+
|
|
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
|