@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025-Present, Nitrogen Labs, Inc.
|
|
3
|
+
* Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Define the types directly to avoid JSX compilation issues
|
|
7
|
+
type SimpleTranslations = Record<string, string>;
|
|
8
|
+
|
|
9
|
+
interface ComplexTranslation {
|
|
10
|
+
readonly value: string;
|
|
11
|
+
readonly locale: string;
|
|
12
|
+
readonly namespace?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type ComplexTranslations = Record<string, ComplexTranslation>;
|
|
16
|
+
|
|
17
|
+
// Test simple translations type
|
|
18
|
+
const simpleTranslations: SimpleTranslations = {
|
|
19
|
+
'welcome': 'Welcome to MetropolisJS!',
|
|
20
|
+
'hello': 'Hello {{name}}!',
|
|
21
|
+
'goodbye': 'Goodbye'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Test complex translations type
|
|
25
|
+
const complexTranslations: ComplexTranslations = {
|
|
26
|
+
'welcome:en': {
|
|
27
|
+
value: 'Welcome to MetropolisJS!',
|
|
28
|
+
locale: 'en',
|
|
29
|
+
namespace: 'common'
|
|
30
|
+
},
|
|
31
|
+
'hello:en': {
|
|
32
|
+
value: 'Hello {{name}}!',
|
|
33
|
+
locale: 'en',
|
|
34
|
+
namespace: 'common'
|
|
35
|
+
},
|
|
36
|
+
'save:en': {
|
|
37
|
+
value: 'Save',
|
|
38
|
+
locale: 'en',
|
|
39
|
+
namespace: 'buttons'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Test individual complex translation type
|
|
44
|
+
const singleTranslation: ComplexTranslation = {
|
|
45
|
+
value: 'Test translation',
|
|
46
|
+
locale: 'en',
|
|
47
|
+
namespace: 'test'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Function to demonstrate type safety
|
|
51
|
+
function processTranslations(translations: SimpleTranslations | ComplexTranslations): void {
|
|
52
|
+
if (Object.keys(translations).length === 0) {
|
|
53
|
+
console.log('No translations provided');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const firstKey = Object.keys(translations)[0];
|
|
58
|
+
const firstValue = translations[firstKey];
|
|
59
|
+
|
|
60
|
+
if (typeof firstValue === 'string') {
|
|
61
|
+
// TypeScript knows this is SimpleTranslations
|
|
62
|
+
console.log('Processing simple translations');
|
|
63
|
+
Object.entries(translations as SimpleTranslations).forEach(([key, value]) => {
|
|
64
|
+
console.log(`${key}: ${value}`);
|
|
65
|
+
});
|
|
66
|
+
} else if (typeof firstValue === 'object' && firstValue !== null && 'value' in firstValue) {
|
|
67
|
+
// TypeScript knows this is ComplexTranslations
|
|
68
|
+
console.log('Processing complex translations');
|
|
69
|
+
Object.entries(translations as ComplexTranslations).forEach(([key, translation]) => {
|
|
70
|
+
console.log(`${key}: ${translation.value} (${translation.locale}${translation.namespace ? `, ${translation.namespace}` : ''})`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Test the function with both types
|
|
76
|
+
console.log('Testing simple translations:');
|
|
77
|
+
processTranslations(simpleTranslations);
|
|
78
|
+
|
|
79
|
+
console.log('\nTesting complex translations:');
|
|
80
|
+
processTranslations(complexTranslations);
|
|
81
|
+
|
|
82
|
+
console.log('\nType test completed successfully!');
|
|
83
|
+
|
|
84
|
+
// Export for use in other files
|
|
85
|
+
export { complexTranslations, processTranslations, simpleTranslations, singleTranslation };
|
|
86
|
+
export type { ComplexTranslation, ComplexTranslations, SimpleTranslations };
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025-Present, Nitrogen Labs, Inc.
|
|
3
|
+
* Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { ComplexTranslations, Metropolis, SimpleTranslations } from '../src/index';
|
|
8
|
+
import { i18n } from '../src/utils/i18n';
|
|
9
|
+
|
|
10
|
+
// Example translations object with simple key-value pairs
|
|
11
|
+
const simpleTranslations: SimpleTranslations = {
|
|
12
|
+
'welcome': 'Welcome to MetropolisJS!',
|
|
13
|
+
'hello': 'Hello {{name}}!',
|
|
14
|
+
'goodbye': 'Goodbye',
|
|
15
|
+
'loading': 'Loading...',
|
|
16
|
+
'error': 'An error occurred',
|
|
17
|
+
'success': 'Operation completed successfully',
|
|
18
|
+
'cancel': 'Cancel',
|
|
19
|
+
'save': 'Save',
|
|
20
|
+
'delete': 'Delete',
|
|
21
|
+
'edit': 'Edit',
|
|
22
|
+
'create': 'Create',
|
|
23
|
+
'update': 'Update',
|
|
24
|
+
'search': 'Search',
|
|
25
|
+
'filter': 'Filter',
|
|
26
|
+
'sort': 'Sort',
|
|
27
|
+
'refresh': 'Refresh',
|
|
28
|
+
'back': 'Back',
|
|
29
|
+
'next': 'Next',
|
|
30
|
+
'previous': 'Previous',
|
|
31
|
+
'submit': 'Submit',
|
|
32
|
+
'reset': 'Reset',
|
|
33
|
+
'confirm': 'Confirm',
|
|
34
|
+
'yes': 'Yes',
|
|
35
|
+
'no': 'No',
|
|
36
|
+
'ok': 'OK',
|
|
37
|
+
'close': 'Close',
|
|
38
|
+
'open': 'Open',
|
|
39
|
+
'show': 'Show',
|
|
40
|
+
'hide': 'Hide',
|
|
41
|
+
'expand': 'Expand',
|
|
42
|
+
'collapse': 'Collapse',
|
|
43
|
+
'minimize': 'Minimize',
|
|
44
|
+
'maximize': 'Maximize',
|
|
45
|
+
'fullscreen': 'Fullscreen',
|
|
46
|
+
'settings': 'Settings',
|
|
47
|
+
'profile': 'Profile',
|
|
48
|
+
'account': 'Account',
|
|
49
|
+
'logout': 'Logout',
|
|
50
|
+
'login': 'Login',
|
|
51
|
+
'register': 'Register',
|
|
52
|
+
'forgot_password': 'Forgot Password?',
|
|
53
|
+
'remember_me': 'Remember Me',
|
|
54
|
+
'email': 'Email',
|
|
55
|
+
'password': 'Password',
|
|
56
|
+
'username': 'Username',
|
|
57
|
+
'first_name': 'First Name',
|
|
58
|
+
'last_name': 'Last Name',
|
|
59
|
+
'phone': 'Phone',
|
|
60
|
+
'address': 'Address',
|
|
61
|
+
'city': 'City',
|
|
62
|
+
'state': 'State',
|
|
63
|
+
'country': 'Country',
|
|
64
|
+
'zip_code': 'ZIP Code',
|
|
65
|
+
'birth_date': 'Birth Date',
|
|
66
|
+
'gender': 'Gender',
|
|
67
|
+
'male': 'Male',
|
|
68
|
+
'female': 'Female',
|
|
69
|
+
'other': 'Other',
|
|
70
|
+
'prefer_not_to_say': 'Prefer not to say'
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Example complex translations with locale and namespace support
|
|
74
|
+
const complexTranslations: ComplexTranslations = {
|
|
75
|
+
'welcome:en': {
|
|
76
|
+
value: 'Welcome to MetropolisJS!',
|
|
77
|
+
locale: 'en',
|
|
78
|
+
namespace: 'common'
|
|
79
|
+
},
|
|
80
|
+
'hello:en': {
|
|
81
|
+
value: 'Hello {{name}}!',
|
|
82
|
+
locale: 'en',
|
|
83
|
+
namespace: 'common'
|
|
84
|
+
},
|
|
85
|
+
'save:en': {
|
|
86
|
+
value: 'Save',
|
|
87
|
+
locale: 'en',
|
|
88
|
+
namespace: 'buttons'
|
|
89
|
+
},
|
|
90
|
+
'cancel:en': {
|
|
91
|
+
value: 'Cancel',
|
|
92
|
+
locale: 'en',
|
|
93
|
+
namespace: 'buttons'
|
|
94
|
+
},
|
|
95
|
+
'welcome:es': {
|
|
96
|
+
value: '¡Bienvenido a MetropolisJS!',
|
|
97
|
+
locale: 'es',
|
|
98
|
+
namespace: 'common'
|
|
99
|
+
},
|
|
100
|
+
'hello:es': {
|
|
101
|
+
value: '¡Hola {{name}}!',
|
|
102
|
+
locale: 'es',
|
|
103
|
+
namespace: 'common'
|
|
104
|
+
},
|
|
105
|
+
'save:es': {
|
|
106
|
+
value: 'Guardar',
|
|
107
|
+
locale: 'es',
|
|
108
|
+
namespace: 'buttons'
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Example React component using Metropolis with simple translations
|
|
113
|
+
const SimpleTranslationApp: React.FC = () => {
|
|
114
|
+
return (
|
|
115
|
+
<Metropolis
|
|
116
|
+
translations={simpleTranslations}
|
|
117
|
+
config={{
|
|
118
|
+
development: {
|
|
119
|
+
app: {
|
|
120
|
+
api: {
|
|
121
|
+
url: 'https://api.example.com'
|
|
122
|
+
},
|
|
123
|
+
urls: {
|
|
124
|
+
websocket: 'wss://ws.example.com'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
<div>
|
|
131
|
+
<h1>{i18n('welcome')}</h1>
|
|
132
|
+
<p>{i18n('hello', {name: 'John'})}</p>
|
|
133
|
+
<button>{i18n('save')}</button>
|
|
134
|
+
<button>{i18n('cancel')}</button>
|
|
135
|
+
<p>{i18n('loading')}</p>
|
|
136
|
+
</div>
|
|
137
|
+
</Metropolis>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Example React component using Metropolis with complex translations
|
|
142
|
+
const ComplexTranslationApp: React.FC = () => {
|
|
143
|
+
return (
|
|
144
|
+
<Metropolis
|
|
145
|
+
translations={complexTranslations}
|
|
146
|
+
config={{
|
|
147
|
+
development: {
|
|
148
|
+
app: {
|
|
149
|
+
api: {
|
|
150
|
+
url: 'https://api.example.com'
|
|
151
|
+
},
|
|
152
|
+
urls: {
|
|
153
|
+
websocket: 'wss://ws.example.com'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<div>
|
|
160
|
+
<h1>{i18n('welcome', {}, 'en', 'common')}</h1>
|
|
161
|
+
<p>{i18n('hello', {name: 'John'}, 'en', 'common')}</p>
|
|
162
|
+
<button>{i18n('save', {}, 'en', 'buttons')}</button>
|
|
163
|
+
<button>{i18n('cancel', {}, 'en', 'buttons')}</button>
|
|
164
|
+
<p>{i18n('loading')}</p>
|
|
165
|
+
</div>
|
|
166
|
+
</Metropolis>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Example of using translations in a component
|
|
171
|
+
const UserProfile: React.FC = () => {
|
|
172
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
173
|
+
const [user, setUser] = React.useState({name: 'John Doe'});
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div>
|
|
177
|
+
<h2>{i18n('profile')}</h2>
|
|
178
|
+
{isLoading ? (
|
|
179
|
+
<p>{i18n('loading')}</p>
|
|
180
|
+
) : (
|
|
181
|
+
<div>
|
|
182
|
+
<p>{i18n('hello', {name: user.name})}</p>
|
|
183
|
+
<button>{i18n('edit')}</button>
|
|
184
|
+
<button>{i18n('delete')}</button>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Example of a form component with translations
|
|
192
|
+
const LoginForm: React.FC = () => {
|
|
193
|
+
const [formData, setFormData] = React.useState({
|
|
194
|
+
email: '',
|
|
195
|
+
password: '',
|
|
196
|
+
rememberMe: false
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
// Handle form submission
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<form onSubmit={handleSubmit}>
|
|
206
|
+
<h2>{i18n('login')}</h2>
|
|
207
|
+
|
|
208
|
+
<div>
|
|
209
|
+
<label htmlFor="email">{i18n('email')}:</label>
|
|
210
|
+
<input
|
|
211
|
+
id="email"
|
|
212
|
+
type="email"
|
|
213
|
+
value={formData.email}
|
|
214
|
+
onChange={(e) => setFormData({...formData, email: e.target.value})}
|
|
215
|
+
required
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div>
|
|
220
|
+
<label htmlFor="password">{i18n('password')}:</label>
|
|
221
|
+
<input
|
|
222
|
+
id="password"
|
|
223
|
+
type="password"
|
|
224
|
+
value={formData.password}
|
|
225
|
+
onChange={(e) => setFormData({...formData, password: e.target.value})}
|
|
226
|
+
required
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div>
|
|
231
|
+
<label>
|
|
232
|
+
<input
|
|
233
|
+
type="checkbox"
|
|
234
|
+
checked={formData.rememberMe}
|
|
235
|
+
onChange={(e) => setFormData({...formData, rememberMe: e.target.checked})}
|
|
236
|
+
/>
|
|
237
|
+
{i18n('remember_me')}
|
|
238
|
+
</label>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div>
|
|
242
|
+
<button type="submit">{i18n('login')}</button>
|
|
243
|
+
<button type="button">{i18n('cancel')}</button>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div>
|
|
247
|
+
<a href="#forgot-password">{i18n('forgot_password')}</a>
|
|
248
|
+
</div>
|
|
249
|
+
</form>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Example of a multilingual app that can switch between simple and complex translations
|
|
254
|
+
const MultilingualApp: React.FC = () => {
|
|
255
|
+
const [useComplexTranslations, setUseComplexTranslations] = React.useState(false);
|
|
256
|
+
const [currentLocale, setCurrentLocale] = React.useState<'en' | 'es'>('en');
|
|
257
|
+
|
|
258
|
+
const selectedTranslations = useComplexTranslations ? complexTranslations : simpleTranslations;
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<Metropolis
|
|
262
|
+
translations={selectedTranslations}
|
|
263
|
+
config={{
|
|
264
|
+
development: {
|
|
265
|
+
app: {
|
|
266
|
+
api: {
|
|
267
|
+
url: 'https://api.example.com'
|
|
268
|
+
},
|
|
269
|
+
urls: {
|
|
270
|
+
websocket: 'wss://ws.example.com'
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<div>
|
|
277
|
+
<div style={{marginBottom: '20px'}}>
|
|
278
|
+
<label>
|
|
279
|
+
<input
|
|
280
|
+
type="checkbox"
|
|
281
|
+
checked={useComplexTranslations}
|
|
282
|
+
onChange={(e) => setUseComplexTranslations(e.target.checked)}
|
|
283
|
+
/>
|
|
284
|
+
Use Complex Translations
|
|
285
|
+
</label>
|
|
286
|
+
|
|
287
|
+
{useComplexTranslations && (
|
|
288
|
+
<div style={{marginLeft: '20px'}}>
|
|
289
|
+
<label>
|
|
290
|
+
Locale:
|
|
291
|
+
<select value={currentLocale} onChange={(e) => setCurrentLocale(e.target.value as 'en' | 'es')}>
|
|
292
|
+
<option value="en">English</option>
|
|
293
|
+
<option value="es">Español</option>
|
|
294
|
+
</select>
|
|
295
|
+
</label>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<h1>
|
|
301
|
+
{useComplexTranslations
|
|
302
|
+
? i18n('welcome', {}, currentLocale, 'common')
|
|
303
|
+
: i18n('welcome')
|
|
304
|
+
}
|
|
305
|
+
</h1>
|
|
306
|
+
|
|
307
|
+
<p>
|
|
308
|
+
{useComplexTranslations
|
|
309
|
+
? i18n('hello', {name: 'World'}, currentLocale, 'common')
|
|
310
|
+
: i18n('hello', {name: 'World'})
|
|
311
|
+
}
|
|
312
|
+
</p>
|
|
313
|
+
|
|
314
|
+
<div>
|
|
315
|
+
<button>
|
|
316
|
+
{useComplexTranslations
|
|
317
|
+
? i18n('save', {}, currentLocale, 'buttons')
|
|
318
|
+
: i18n('save')
|
|
319
|
+
}
|
|
320
|
+
</button>
|
|
321
|
+
<button>
|
|
322
|
+
{useComplexTranslations
|
|
323
|
+
? i18n('cancel', {}, currentLocale, 'buttons')
|
|
324
|
+
: i18n('cancel')
|
|
325
|
+
}
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<UserProfile />
|
|
330
|
+
<LoginForm />
|
|
331
|
+
</div>
|
|
332
|
+
</Metropolis>
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export default MultilingualApp;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# MetropolisJS Factory Pattern Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Key Benefits
|
|
8
|
+
|
|
9
|
+
1. **Functional Programming**: Pure functions instead of classes with side effects
|
|
10
|
+
2. **Better Testability**: Easier to mock and test individual functions
|
|
11
|
+
3. **Composability**: Actions can be easily combined and extended
|
|
12
|
+
4. **Dependency Injection**: Custom adapters can be injected and merged with defaults
|
|
13
|
+
5. **Backward Compatibility**: Legacy class wrappers maintain existing API compatibility
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
### Before (Class-based)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import {userActions} from '../actions/userActions';
|
|
21
|
+
|
|
22
|
+
const userActions = new userActions(flux);
|
|
23
|
+
const user = await userActions.add(userData);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### After (Factory Pattern)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import {createUserActions} from '../actions/userActions';
|
|
30
|
+
|
|
31
|
+
const userActions = createUserActions(flux);
|
|
32
|
+
const user = await userActions.add(userData);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Advanced Usage with Custom Adapters
|
|
36
|
+
|
|
37
|
+
### Custom Validation Adapter
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Custom adapter that extends default behavior
|
|
41
|
+
const customUserAdapter = (input: unknown, options?: UserAdapterOptions) => {
|
|
42
|
+
// input is already validated by default adapter
|
|
43
|
+
const user = input as any;
|
|
44
|
+
|
|
45
|
+
// Add business-specific validation
|
|
46
|
+
if (user.email && !user.email.includes('@company.com')) {
|
|
47
|
+
throw new Error('Only company emails allowed');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add computed fields
|
|
51
|
+
return {
|
|
52
|
+
...user,
|
|
53
|
+
fullName: `${user.firstName || ''} ${user.lastName || ''}`.trim(),
|
|
54
|
+
isAdmin: user.userAccess >= 3
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const userActions = createUserActions(flux, {
|
|
59
|
+
userAdapter: customUserAdapter
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Configuration-based Adapters
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const userActions = createUserActions(flux, {
|
|
67
|
+
userAdapterOptions: {
|
|
68
|
+
strict: true,
|
|
69
|
+
environment: 'production',
|
|
70
|
+
customValidation: (input) => {
|
|
71
|
+
// Additional validation logic
|
|
72
|
+
return input;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Runtime Adapter Updates
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const userActions = createUserActions(flux);
|
|
82
|
+
|
|
83
|
+
// Update adapter at runtime
|
|
84
|
+
userActions.updateUserAdapter(customUserAdapter);
|
|
85
|
+
|
|
86
|
+
// Update options at runtime
|
|
87
|
+
userActions.updateUserAdapterOptions({
|
|
88
|
+
strict: true,
|
|
89
|
+
environment: 'production'
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Available Actions
|
|
94
|
+
|
|
95
|
+
All action files now export factory functions:
|
|
96
|
+
|
|
97
|
+
- `createUserActions(flux, options?)` - User management
|
|
98
|
+
- `createPostActions(flux, options?)` - Post management
|
|
99
|
+
- `createEventActions(flux, options?)` - Event management
|
|
100
|
+
- `createMessageActions(flux, options?)` - Messaging
|
|
101
|
+
- `createImageActions(flux, options?)` - Image handling
|
|
102
|
+
- `createLocationActions(flux, options?)` - Location services
|
|
103
|
+
- `createReactionActions(flux, options?)` - Reactions
|
|
104
|
+
- `createTagActions(flux, options?)` - Tag management
|
|
105
|
+
- `createWebsocketActions(flux)` - WebSocket connections
|
|
106
|
+
|
|
107
|
+
## Adapter Options Interface
|
|
108
|
+
|
|
109
|
+
All adapters support the same options interface:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
interface AdapterOptions {
|
|
113
|
+
strict?: boolean; // Enable strict validation
|
|
114
|
+
allowPartial?: boolean; // Allow partial data
|
|
115
|
+
environment?: 'development' | 'production' | 'test';
|
|
116
|
+
customValidation?: (input: unknown) => unknown;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Migration Guide
|
|
121
|
+
|
|
122
|
+
### Step 1: Update Imports
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Old
|
|
126
|
+
import {userActions} from '../actions/userActions';
|
|
127
|
+
|
|
128
|
+
// New
|
|
129
|
+
import {createUserActions} from '../actions/userActions';
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Step 2: Update Instantiation
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Old
|
|
136
|
+
const userActions = new userActions(flux, customAdapter);
|
|
137
|
+
|
|
138
|
+
// New
|
|
139
|
+
const userActions = createUserActions(flux, {
|
|
140
|
+
userAdapter: customAdapter
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Step 3: Update useMetropolis Hook
|
|
145
|
+
|
|
146
|
+
The `useMetropolis` hook has been updated to use the factory pattern:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Old
|
|
150
|
+
return useMemo(() => ({
|
|
151
|
+
userActions: new userActions(flux, UserAdapter),
|
|
152
|
+
postActions: new PostActions(flux, PostAdapter),
|
|
153
|
+
// ...
|
|
154
|
+
}), [flux, UserAdapter, PostAdapter]);
|
|
155
|
+
|
|
156
|
+
// New
|
|
157
|
+
return useMemo(() => ({
|
|
158
|
+
userActions: createUserActions(flux, {
|
|
159
|
+
userAdapter: UserAdapter
|
|
160
|
+
}),
|
|
161
|
+
postActions: createPostActions(flux, {
|
|
162
|
+
postAdapter: PostAdapter
|
|
163
|
+
}),
|
|
164
|
+
// ...
|
|
165
|
+
}), [flux, UserAdapter, PostAdapter]);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Backward Compatibility
|
|
169
|
+
|
|
170
|
+
Legacy class wrappers are provided for backward compatibility:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Still works
|
|
174
|
+
import {userActionsClass} from '../actions/userActions';
|
|
175
|
+
const userActions = new userActionsClass(flux, options);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Testing Examples
|
|
179
|
+
|
|
180
|
+
### Unit Testing Actions
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import {createUserActions} from '../actions/userActions';
|
|
184
|
+
|
|
185
|
+
describe('userActions', () => {
|
|
186
|
+
let flux: FluxFramework;
|
|
187
|
+
let userActions: userActions;
|
|
188
|
+
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
flux = createMockFlux();
|
|
191
|
+
userActions = createUserActions(flux);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should add user with validation', async () => {
|
|
195
|
+
const userData = {username: 'test', email: 'test@example.com'};
|
|
196
|
+
const result = await userActions.add(userData);
|
|
197
|
+
expect(result).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Testing with Custom Adapters
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const mockAdapter = jest.fn((input) => ({
|
|
206
|
+
...input,
|
|
207
|
+
validated: true
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
const userActions = createUserActions(flux, {
|
|
211
|
+
userAdapter: mockAdapter
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(mockAdapter).toHaveBeenCalled();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Best Practices
|
|
218
|
+
|
|
219
|
+
1. **Use Factory Functions**: Prefer `createXxxActions()` over class constructors
|
|
220
|
+
2. **Leverage Adapter Injection**: Use custom adapters for business logic
|
|
221
|
+
3. **Runtime Updates**: Use update methods for dynamic behavior changes
|
|
222
|
+
4. **Type Safety**: Always use TypeScript interfaces for better type checking
|
|
223
|
+
5. **Error Handling**: Custom adapters should throw meaningful errors
|
|
224
|
+
|
|
225
|
+
## Performance Considerations
|
|
226
|
+
|
|
227
|
+
- Factory functions are lightweight and create minimal overhead
|
|
228
|
+
- Adapter validation is only performed when needed
|
|
229
|
+
- Options are merged efficiently without deep cloning
|
|
230
|
+
- Legacy wrappers have minimal performance impact
|
|
231
|
+
|
|
232
|
+
## Future Enhancements
|
|
233
|
+
|
|
234
|
+
The factory pattern enables future enhancements:
|
|
235
|
+
|
|
236
|
+
- **Middleware Support**: Chain multiple adapters
|
|
237
|
+
- **Plugin System**: Load adapters dynamically
|
|
238
|
+
- **Caching**: Cache validated results
|
|
239
|
+
- **Async Adapters**: Support async validation logic
|
|
240
|
+
- **Schema Evolution**: Handle multiple adapter versions
|
|
241
|
+
|
|
242
|
+
## Conclusion
|
|
243
|
+
|
|
244
|
+
The factory pattern provides a more functional, testable, and flexible approach to action management in MetropolisJS. While maintaining backward compatibility, it opens up new possibilities for customization and extension.
|
package/index.js
CHANGED
package/jest.config.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest configuration for MetropolisJS tests.
|
|
3
|
+
* Pulls the default Lex Jest config (which includes TS/Babel transforms)
|
|
4
|
+
* and overlays the project-specific settings from lex.config.mjs.
|
|
5
|
+
*/
|
|
6
|
+
import lexConfig from './lex.config.mjs';
|
|
7
|
+
import baseConfig from './node_modules/@nlabs/lex/jest.config.mjs';
|
|
8
|
+
|
|
9
|
+
const projectJest = lexConfig?.jest || {};
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
...baseConfig,
|
|
13
|
+
...projectJest,
|
|
14
|
+
testPathIgnorePatterns: [
|
|
15
|
+
'lib/__tests__/e2e/helpers/testGraphQLServer.d.ts',
|
|
16
|
+
'lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts'
|
|
17
|
+
],
|
|
18
|
+
transformIgnorePatterns: [
|
|
19
|
+
'/node_modules/(?!(?:@nlabs/utils|@nlabs/rip-hunter)/)'
|
|
20
|
+
],
|
|
21
|
+
moduleNameMapper: {
|
|
22
|
+
...baseConfig.moduleNameMapper,
|
|
23
|
+
...projectJest.moduleNameMapper
|
|
24
|
+
},
|
|
25
|
+
transform: {
|
|
26
|
+
...baseConfig.transform
|
|
27
|
+
}
|
|
28
|
+
};
|