@nlabs/metropolisjs 0.3.4 → 0.3.5

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.
Files changed (117) hide show
  1. package/.github/copilot-instructions.md +55 -167
  2. package/AGENTS.md +52 -10
  3. package/README.md +52 -2
  4. package/docs/ACTIONS.md +84 -22
  5. package/docs/assets/metropolisjs-logo.png +0 -0
  6. package/examples/factory-pattern-usage.ts +15 -10
  7. package/lex.config.mjs +5 -8
  8. package/lib/actions/appActions/appActions.d.ts.map +1 -1
  9. package/lib/actions/appActions/appActions.js +77 -6
  10. package/lib/actions/crmActions/crmActions.d.ts +87 -0
  11. package/lib/actions/crmActions/crmActions.d.ts.map +1 -0
  12. package/lib/actions/crmActions/crmActions.js +338 -0
  13. package/lib/actions/index.d.ts +2 -0
  14. package/lib/actions/index.d.ts.map +1 -1
  15. package/lib/actions/index.js +3 -1
  16. package/lib/actions/restActions/restActions.d.ts +19 -0
  17. package/lib/actions/restActions/restActions.d.ts.map +1 -0
  18. package/lib/actions/restActions/restActions.js +40 -0
  19. package/lib/actions/userActions/userActions.d.ts +12 -0
  20. package/lib/actions/userActions/userActions.d.ts.map +1 -1
  21. package/lib/actions/userActions/userActions.js +20 -4
  22. package/lib/adapters/appAdapter/appAdapter.d.ts +2 -2
  23. package/lib/adapters/appAdapter/appAdapter.d.ts.map +1 -1
  24. package/lib/adapters/arangoAdapter/arangoAdapter.d.ts +2 -2
  25. package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -1
  26. package/lib/adapters/contentAdapter/contentAdapter.d.ts +2 -2
  27. package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -1
  28. package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +2 -2
  29. package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -1
  30. package/lib/adapters/eventAdapter/eventAdapter.d.ts +2 -2
  31. package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -1
  32. package/lib/adapters/fileAdapter/fileAdapter.d.ts +2 -2
  33. package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -1
  34. package/lib/adapters/groupAdapter/groupAdapter.d.ts +2 -2
  35. package/lib/adapters/groupAdapter/groupAdapter.d.ts.map +1 -1
  36. package/lib/adapters/imageAdapter/imageAdapter.d.ts +2 -2
  37. package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -1
  38. package/lib/adapters/locationAdapter/locationAdapter.d.ts +2 -2
  39. package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -1
  40. package/lib/adapters/messageAdapter/messageAdapter.d.ts +2 -2
  41. package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -1
  42. package/lib/adapters/permissionAdapter/permissionAdapter.d.ts +2 -2
  43. package/lib/adapters/permissionAdapter/permissionAdapter.d.ts.map +1 -1
  44. package/lib/adapters/personaAdapter/personaAdapter.d.ts +2 -2
  45. package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -1
  46. package/lib/adapters/postAdapter/postAdapter.d.ts +2 -2
  47. package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -1
  48. package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +2 -2
  49. package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -1
  50. package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +2 -2
  51. package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -1
  52. package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +2 -2
  53. package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -1
  54. package/lib/adapters/tagAdapter/tagAdapter.d.ts +2 -2
  55. package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -1
  56. package/lib/adapters/translationAdapter/translationAdapter.d.ts +2 -2
  57. package/lib/adapters/translationAdapter/translationAdapter.d.ts.map +1 -1
  58. package/lib/adapters/userAdapter/userAdapter.d.ts +2 -2
  59. package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -1
  60. package/lib/adapters/videoAdapter/videoAdapter.d.ts +2 -2
  61. package/lib/adapters/videoAdapter/videoAdapter.d.ts.map +1 -1
  62. package/lib/config/index.d.ts +1 -0
  63. package/lib/config/index.d.ts.map +1 -1
  64. package/lib/config/index.js +1 -1
  65. package/lib/constants/Collections.d.ts +16 -0
  66. package/lib/constants/Collections.d.ts.map +1 -1
  67. package/lib/constants/Collections.js +17 -1
  68. package/lib/index.d.ts +4 -2
  69. package/lib/index.d.ts.map +1 -1
  70. package/lib/index.js +5 -3
  71. package/lib/stores/appStore.d.ts +10 -0
  72. package/lib/stores/appStore.d.ts.map +1 -1
  73. package/lib/stores/appStore.js +12 -2
  74. package/lib/tests/actionTestScenarios.d.ts +20 -0
  75. package/lib/tests/actionTestScenarios.d.ts.map +1 -0
  76. package/lib/tests/actionTestScenarios.js +851 -0
  77. package/lib/tests/testGraphQLServer.d.ts.map +1 -0
  78. package/lib/tests/testGraphQLServer.js +197 -0
  79. package/lib/tests/testGraphQLServerSimple.d.ts.map +1 -0
  80. package/lib/tests/testGraphQLServerSimple.js +52 -0
  81. package/lib/types/crm.types.d.ts +100 -0
  82. package/lib/types/crm.types.d.ts.map +1 -0
  83. package/lib/types/crm.types.js +6 -0
  84. package/lib/types/index.d.ts +1 -0
  85. package/lib/types/index.d.ts.map +1 -1
  86. package/lib/types/index.js +2 -1
  87. package/lib/utils/actionFactory.d.ts +6 -4
  88. package/lib/utils/actionFactory.d.ts.map +1 -1
  89. package/lib/utils/actionFactory.js +9 -1
  90. package/lib/utils/api.d.ts +14 -2
  91. package/lib/utils/api.d.ts.map +1 -1
  92. package/lib/utils/api.js +114 -32
  93. package/lib/utils/baseActionFactory.d.ts +2 -2
  94. package/lib/utils/dateUtils.d.ts +1 -1
  95. package/lib/utils/dateUtils.d.ts.map +1 -1
  96. package/lib/utils/index.d.ts +2 -2
  97. package/lib/utils/index.d.ts.map +1 -1
  98. package/lib/utils/index.js +3 -3
  99. package/lib/utils/location.d.ts +1 -1
  100. package/lib/utils/location.d.ts.map +1 -1
  101. package/lib/utils/requestCache.d.ts +1 -1
  102. package/lib/utils/requestCache.d.ts.map +1 -1
  103. package/lib/utils/useMetropolis.d.ts +2 -0
  104. package/lib/utils/useMetropolis.d.ts.map +1 -1
  105. package/lib/utils/useMetropolis.js +19 -1
  106. package/lib/utils/validatorFactory.d.ts +1 -1
  107. package/lib/utils/validatorFactory.d.ts.map +1 -1
  108. package/package.json +9 -9
  109. package/tsconfig.lint.json +2 -2
  110. package/tsconfig.test.json +3 -2
  111. package/jest.config.mjs +0 -28
  112. package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts.map +0 -1
  113. package/lib/__tests__/e2e/helpers/testGraphQLServer.js +0 -197
  114. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts.map +0 -1
  115. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.js +0 -52
  116. /package/lib/{__tests__/e2e/helpers → tests}/testGraphQLServer.d.ts +0 -0
  117. /package/lib/{__tests__/e2e/helpers → tests}/testGraphQLServerSimple.d.ts +0 -0
@@ -1,167 +1,55 @@
1
- # MetropolisJS AI Coding Assistant Instructions
2
-
3
- ## Project Overview
4
- MetropolisJS is a React library that provides seamless frontend-backend integration for applications using Reaktor backend services and ArkhamJS state management. It handles authentication, real-time messaging, content management, and social features through a unified API.
5
-
6
- ## Architecture Patterns
7
-
8
- ### Core Components
9
- - **Stores**: ArkhamJS-based state management with domain-specific stores (`users`, `posts`, `messages`, etc.)
10
- - **Actions**: Factory function pattern using `createUserActions(flux)`, `createPostActions(flux)`, etc.
11
- - **Adapters**: Zod-validated data parsers for API responses (`parseUser`, `parsePost`, etc.)
12
- - **Configuration**: Environment-specific config objects with API endpoints, WebSocket URLs, and session settings
13
-
14
- ### Key Conventions
15
-
16
- #### 1. Action Creation Pattern
17
- ```typescript
18
- // Correct: Use factory functions
19
- import {createUserActions} from '@nlabs/metropolisjs';
20
- const userActions = createUserActions(flux);
21
-
22
- // Avoid: Direct instantiation
23
- const userActions = new UserActions(flux);
24
- ```
25
-
26
- #### 2. GraphQL Mutation Structure
27
- All GraphQL mutations are wrapped in domain fields:
28
- ```graphql
29
- mutation UsersSignIn($user: UserInput!, $expires: Int) {
30
- users {
31
- signIn(user: $user, expires: $expires) {
32
- token
33
- expires
34
- }
35
- }
36
- }
37
- ```
38
-
39
- #### 3. Store Constants Pattern
40
- Each store defines action type constants:
41
- ```typescript
42
- export const USER_CONSTANTS = {
43
- SIGN_IN_SUCCESS: 'USER_SIGN_IN_SUCCESS',
44
- SIGN_IN_ERROR: 'USER_SIGN_IN_ERROR',
45
- // ... etc
46
- };
47
- ```
48
-
49
- #### 4. Adapter Validation
50
- Use Zod schemas for data validation in adapters:
51
- ```typescript
52
- import {z} from 'zod';
53
- const userSchema = z.object({
54
- id: z.string(),
55
- email: z.string().email(),
56
- // ...
57
- });
58
- ```
59
-
60
- ## Development Workflow
61
-
62
- ### Build Commands
63
- - `npm run build` - Compile TypeScript to `lib/` using @nlabs/lex
64
- - `npm run dev` - Development build with watch mode
65
- - `npm run test` - Run Jest tests with @nlabs/lex configuration
66
- - `npm run lint` - ESLint with auto-fix using @nlabs/lex
67
-
68
- ### Testing Setup
69
- - Uses Jest with jsdom environment
70
- - Tests located in `__tests__/` directories alongside source
71
- - Mock setup in `__mocks__/` for external dependencies
72
-
73
- ### TypeScript Configuration
74
- - Strict mode disabled (`"strict": false`)
75
- - ESNext modules with bundler resolution
76
- - Declaration files emitted to `lib/` directory
77
-
78
- ## Coding Conventions
79
-
80
- ### Comments
81
- - Do not write comments in code except for TODOs, copyrights, and lint disables
82
- - Variables and code should be self-explanatory with clear, descriptive names
83
- - Use meaningful variable names that explain their purpose
84
- - Structure code to be readable without additional comments
85
-
86
- ## Integration Points
87
-
88
- ### External Dependencies
89
- - **@nlabs/arkhamjs**: State management framework
90
- - **@nlabs/arkhamjs-utils-react**: React hooks for ArkhamJS
91
- - **i18next**: Internationalization
92
- - **sockette**: WebSocket client
93
- - **zod**: Schema validation
94
-
95
- ### Real-time Features
96
- - WebSocket connections via `websocketStore`
97
- - Server-Sent Events (SSE) for notifications
98
- - Automatic reconnection and state synchronization
99
-
100
- ### Authentication Flow
101
- - Session management through `user.session` state
102
- - Token refresh via `refreshSession()` API calls
103
- - Environment-specific auth checks via config
104
-
105
- ## Code Organization
106
-
107
- ### Directory Structure
108
- ```
109
- src/
110
- ├── actions/ # Factory-based action creators
111
- ├── adapters/ # Data parsing and validation
112
- ├── stores/ # ArkhamJS store definitions
113
- ├── config/ # Environment configuration
114
- ├── utils/ # Shared utilities (API, i18n, etc.)
115
- └── constants/ # Application constants
116
- ```
117
-
118
- ### File Naming
119
- - Actions: `userActions.ts`, `postActions.ts`
120
- - Stores: `userStore.ts`, `postStore.ts`
121
- - Adapters: `userAdapter.ts`, `postAdapter.ts`
122
- - Tests: `userActions.test.ts` alongside implementation
123
-
124
- ## Common Patterns
125
-
126
- ### Error Handling
127
- ```typescript
128
- try {
129
- const result = await userActions.signIn({username, password});
130
- // Handle success
131
- } catch (error) {
132
- // Error dispatched to store with *_ERROR constant
133
- console.error('Sign in failed:', error);
134
- }
135
- ```
136
-
137
- ### State Access
138
- ```typescript
139
- import {useFlux} from '@nlabs/arkhamjs-utils-react';
140
-
141
- const MyComponent = () => {
142
- const flux = useFlux();
143
- const user = flux.getState('user.item', {});
144
- // ...
145
- };
146
- ```
147
-
148
- ### Custom Adapters
149
- ```typescript
150
- const customUserAdapter = (input: unknown) => {
151
- const user = parseUser(input); // Use default parser first
152
- // Add custom business logic
153
- return {...user, computedField: 'value'};
154
- };
155
-
156
- const userActions = createUserActions(flux, {
157
- userAdapter: customUserAdapter
158
- });
159
- ```
160
-
161
- ## Key Files to Reference
162
- - `src/index.tsx` - Main library exports and Metropolis component
163
- - `src/stores/index.ts` - All store exports
164
- - `src/actions/index.ts` - All action exports
165
- - `src/adapters/index.ts` - All adapter exports
166
- - `src/utils/api.ts` - GraphQL API utilities
167
- - `factoryPatternGuide.md` - Detailed factory pattern documentation
1
+ # AI Coding Assistant Instructions
2
+
3
+ ## Shared NLabs Stack
4
+ - Use Lex (`@nlabs/lex`) for CLI workflows, building, compiling, deploying, and testing projects.
5
+ - Use Vitest for unit and integration tests.
6
+ - Use Playwright for end-to-end tests.
7
+ - Use GothamJS (`@nlabs/gothamjs`) for the presentation layer. Obtain components, views, routing, Tailwind setup, and default styles from GothamJS before creating custom project-level components.
8
+ - Use React v19 for frontend work.
9
+ - Use Tailwind v4 for CSS styles.
10
+ - Use MetropolisJS (`@nlabs/metropolisjs`) as the starting point for all frontend API integration. Create custom data types, actions, queries, or mutations only after MetropolisJS has been exhausted.
11
+ - Use Rip-Hunter to access API endpoints.
12
+ - Use `fetch` through Rip-Hunter rather than direct project-level `fetch` calls.
13
+ - Use ArkhamJS (`@nlabs/arkhamjs`) as the frontend data store. Store data, including session-storage-backed data, through Flux actions and event listeners.
14
+ - Prefer listening for Flux events over chaining `.then(...)` to Flux action promises.
15
+ - Flux actions update the data store before dispatching their events.
16
+ - Multiple listeners may listen for the same Flux action event.
17
+ - Read persistent data from the data store, and read event-specific data directly from the Flux action event when appropriate.
18
+ - Send all data as JSON.
19
+ - Use Reaktor (`@nlabs/reaktor`) for actions that interact with the ArangoDB database.
20
+
21
+ ## Database
22
+ - All projects use the ArangoDB instance at `https://db.reaktor.io:8529`.
23
+ - ArangoDB is a document NoSQL database with SQL-style query and graph database features.
24
+ - When adding or deleting documents, check whether associated graph edges also need to be added or deleted.
25
+
26
+ ## Project Structure
27
+ - Web apps should contain at least one microsite.
28
+ - Web apps should include a `src/ui` folder.
29
+ - Web apps with a backend should include a `src/api` folder or microsite.
30
+ - Library projects may use a root source folder without `src/ui` or `src/api`.
31
+ - Shared utility/helper functions should live in a shared file when used in more than one place.
32
+ - Keep one component per file.
33
+ - Each component should have a sibling test file named `MyComponent.test.ts` or `MyComponent.test.tsx`.
34
+ - Group components by component folder, for example `src/ui/components/MyComponent/MyComponent.tsx`.
35
+
36
+ ## Package Scripts
37
+ - All projects should have at least `start`, `test`, and `update` scripts.
38
+ - If a project includes both `api` and `ui` workspaces, use `concurrently` in `start`:
39
+ `"start": "concurrently \"npm run start -w api\" \"npm run start -w ui\""`
40
+ - Use this workspace test script:
41
+ `"test": "npm run test --workspaces --ignore-scripts"`
42
+ - Use this workspace update script:
43
+ `"update": "npm run update --workspaces"`
44
+
45
+ ## Testing
46
+ - Maintain at least 90% unit test coverage.
47
+ - Components that require other components should have integration tests.
48
+ - Provide at least one Playwright e2e test for each happy path.
49
+
50
+ ## Code Style
51
+ - Use arrow functions for functions.
52
+ - Correctly type all variables, props, and arguments unless TypeScript inference already provides the correct type.
53
+ - Sort props and object keys alphabetically.
54
+ - Check and fix all ESLint errors and warnings.
55
+ - Use `eslint-config-styleguide` for linting rules.
package/AGENTS.md CHANGED
@@ -1,13 +1,55 @@
1
- # AGENTS.md
1
+ # AI Coding Assistant Instructions
2
2
 
3
- ## Core Rule
4
- - Treat shared data contracts as strict and canonical.
5
- - Do not add legacy aliases, compatibility shims, fallback field names, or dual-write behavior.
3
+ ## Shared NLabs Stack
4
+ - Use Lex (`@nlabs/lex`) for CLI workflows, building, compiling, deploying, and testing projects.
5
+ - Use Vitest for unit and integration tests.
6
+ - Use Playwright for end-to-end tests.
7
+ - Use GothamJS (`@nlabs/gothamjs`) for the presentation layer. Obtain components, views, routing, Tailwind setup, and default styles from GothamJS before creating custom project-level components.
8
+ - Use React v19 for frontend work.
9
+ - Use Tailwind v4 for CSS styles.
10
+ - Use MetropolisJS (`@nlabs/metropolisjs`) as the starting point for all frontend API integration. Create custom data types, actions, queries, or mutations only after MetropolisJS has been exhausted.
11
+ - Use Rip-Hunter to access API endpoints.
12
+ - Use `fetch` through Rip-Hunter rather than direct project-level `fetch` calls.
13
+ - Use ArkhamJS (`@nlabs/arkhamjs`) as the frontend data store. Store data, including session-storage-backed data, through Flux actions and event listeners.
14
+ - Prefer listening for Flux events over chaining `.then(...)` to Flux action promises.
15
+ - Flux actions update the data store before dispatching their events.
16
+ - Multiple listeners may listen for the same Flux action event.
17
+ - Read persistent data from the data store, and read event-specific data directly from the Flux action event when appropriate.
18
+ - Send all data as JSON.
19
+ - Use Reaktor (`@nlabs/reaktor`) for actions that interact with the ArangoDB database.
6
20
 
7
- ## Required Patterns
8
- - When a shared contract changes, update MetropolisJS to the canonical field names and update callers instead of supporting both shapes.
9
- - Keep API payloads aligned with the current Reaktor schema.
21
+ ## Database
22
+ - All projects use the ArangoDB instance at `https://db.reaktor.io:8529`.
23
+ - ArangoDB is a document NoSQL database with SQL-style query and graph database features.
24
+ - When adding or deleting documents, check whether associated graph edges also need to be added or deleted.
10
25
 
11
- ## Prohibited Patterns
12
- - No temporary backward-compatibility layers for field renames.
13
- - No adapters that emit both old and new field names for the same value.
26
+ ## Project Structure
27
+ - Web apps should contain at least one microsite.
28
+ - Web apps should include a `src/ui` folder.
29
+ - Web apps with a backend should include a `src/api` folder or microsite.
30
+ - Library projects may use a root source folder without `src/ui` or `src/api`.
31
+ - Shared utility/helper functions should live in a shared file when used in more than one place.
32
+ - Keep one component per file.
33
+ - Each component should have a sibling test file named `MyComponent.test.ts` or `MyComponent.test.tsx`.
34
+ - Group components by component folder, for example `src/ui/components/MyComponent/MyComponent.tsx`.
35
+
36
+ ## Package Scripts
37
+ - All projects should have at least `start`, `test`, and `update` scripts.
38
+ - If a project includes both `api` and `ui` workspaces, use `concurrently` in `start`:
39
+ `"start": "concurrently \"npm run start -w api\" \"npm run start -w ui\""`
40
+ - Use this workspace test script:
41
+ `"test": "npm run test --workspaces --ignore-scripts"`
42
+ - Use this workspace update script:
43
+ `"update": "npm run update --workspaces"`
44
+
45
+ ## Testing
46
+ - Maintain at least 90% unit test coverage.
47
+ - Components that require other components should have integration tests.
48
+ - Provide at least one Playwright e2e test for each happy path.
49
+
50
+ ## Code Style
51
+ - Use arrow functions for functions.
52
+ - Correctly type all variables, props, and arguments unless TypeScript inference already provides the correct type.
53
+ - Sort props and object keys alphabetically.
54
+ - Check and fix all ESLint errors and warnings.
55
+ - Use `eslint-config-styleguide` for linting rules.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # MetropolisJS: Seamless Frontend-Backend Integration Framework
2
2
 
3
+ <p align="center">
4
+ <img src="docs/assets/metropolisjs-logo.png" alt="MetropolisJS logo" width="432">
5
+ </p>
6
+
3
7
  > **The Ultimate Frontend Integration Library for Modern Web Applications**
4
8
 
5
9
  [![npm version](https://img.shields.io/npm/v/@nlabs/metropolisjs.svg?style=flat-square)](https://www.npmjs.com/package/@nlabs/metropolisjs)
@@ -53,7 +57,7 @@ npm install @nlabs/metropolisjs @nlabs/arkhamjs @nlabs/arkhamjs-utils-react
53
57
  ### Basic Setup
54
58
 
55
59
  ```tsx
56
- import {Metropolis, useUserActions, useMessageActions, useWebsocketActions} from '@nlabs/metropolisjs';
60
+ import {Metropolis, useUserActions, useMessageActions, useRestActions, useWebsocketActions} from '@nlabs/metropolisjs';
57
61
 
58
62
  const App = () => {
59
63
  return (
@@ -92,14 +96,59 @@ MetropolisJS provides multiple ways to access actions:
92
96
  // Option 1: Specialized hooks (recommended - best performance)
93
97
  const userActions = useUserActions();
94
98
  const postActions = usePostActions();
99
+ const restActions = useRestActions();
95
100
 
96
101
  // Option 2: Selective creation
97
- const {userActions, postActions} = useMetropolis(['user', 'post']);
102
+ const {userActions, postActions, restActions} = useMetropolis(['user', 'post', 'rest']);
98
103
 
99
104
  // Option 3: All actions (default behavior)
100
105
  const {userActions, postActions, messageActions} = useMetropolis();
101
106
  ```
102
107
 
108
+ ### External REST Endpoints
109
+
110
+ MetropolisJS includes a `rest` action group for APIs that are not part of Reaktor. It delegates to `@nlabs/rip-hunter`, so app code can keep using Metropolis actions instead of importing rip-hunter directly.
111
+
112
+ ```tsx
113
+ <Metropolis
114
+ config={{
115
+ development: {
116
+ app: {
117
+ api: {
118
+ endpoints: {
119
+ weather: 'https://api.example.com/weather'
120
+ },
121
+ public: 'http://localhost:3000/public',
122
+ url: 'http://localhost:3000/app'
123
+ }
124
+ }
125
+ }
126
+ }}
127
+ >
128
+ <YourApp />
129
+ </Metropolis>
130
+
131
+ const WeatherPanel = () => {
132
+ const restActions = useRestActions();
133
+
134
+ const loadWeather = async () => {
135
+ const weather = await restActions.get('weather', {zip: '60601'}, {cache: true});
136
+ return weather;
137
+ };
138
+ };
139
+ ```
140
+
141
+ Use `authenticate: true` when an external endpoint should receive the current Metropolis session token:
142
+
143
+ ```tsx
144
+ const profile = await restActions.request(
145
+ 'https://api.example.com/profile',
146
+ 'PATCH',
147
+ {displayName: 'Ada'},
148
+ {authenticate: true}
149
+ );
150
+ ```
151
+
103
152
  ## Configuration
104
153
 
105
154
  The `Metropolis` component accepts three main props: `config`, `adapters`, and `translations`. Here's how to configure each:
@@ -165,6 +214,7 @@ The `config` prop accepts a `MetropolisConfiguration` object that supports envir
165
214
  - `environment`: `'development' | 'production' | 'test' | 'local'` - Current environment
166
215
  - `app`: Application-specific configuration
167
216
  - `api`: API endpoint configuration
217
+ - `endpoints`: Named external REST endpoints for `restActions`
168
218
  - `url`: Main API endpoint
169
219
  - `public`: Public API endpoint
170
220
  - `uploadImage`: Image upload endpoint
package/docs/ACTIONS.md CHANGED
@@ -7,20 +7,22 @@ This reference documents every action family exposed by MetropolisJS, how to acc
7
7
  Use specialized hooks in React components:
8
8
 
9
9
  ```tsx
10
- import {useUserActions, usePostActions, useMessageActions} from '@nlabs/metropolisjs';
10
+ import {useUserActions, usePostActions, useMessageActions, useRestActions} from '@nlabs/metropolisjs';
11
11
 
12
12
  const userActions = useUserActions();
13
13
  const postActions = usePostActions();
14
14
  const messageActions = useMessageActions();
15
+ const restActions = useRestActions();
15
16
  ```
16
17
 
17
18
  Or create actions directly with a Flux instance:
18
19
 
19
20
  ```ts
20
- import {createUserActions, createPostActions} from '@nlabs/metropolisjs';
21
+ import {createUserActions, createPostActions, createRestActions} from '@nlabs/metropolisjs';
21
22
 
22
23
  const userActions = createUserActions(flux);
23
24
  const postActions = createPostActions(flux);
25
+ const restActions = createRestActions(flux);
24
26
  ```
25
27
 
26
28
  ## Action Families
@@ -30,27 +32,87 @@ Each row links to:
30
32
  - hook exposure (`src/utils/useMetropolis.ts`)
31
33
  - common method names (full signatures live in the interface in each source file)
32
34
 
33
- | Family | Hook | Creator | Typical Methods | Source |
35
+ ### Hook and Factory Actions
36
+
37
+ These action families are available through specialized hooks when present, `useMetropolis([...])`, `createAction(...)`, `createActions(...)`, and direct creators.
38
+
39
+ | Family | Hook | Factory Key | Creator | Typical Methods | Source |
34
40
  | --- | --- | --- | --- | --- |
35
- | App | `useMetropolis(['app'])` | `createAppActions` | `add`, `itemById`, `list`, `update`, `delete` | [appActions.ts](../src/actions/appActions/appActions.ts) |
36
- | Connection | `useMetropolis(['connection'])` | `createConnectionActions` | `addConnection`, `getConnections`, `removeConnection` | [connectionActions.ts](../src/actions/connectionActions/connectionActions.ts) |
37
- | Content | `useContentActions` | `createContentActions` | `add`, `itemById`, `itemByKey`, `listByCategory`, `list`, `update`, `delete` | [contentActions.ts](../src/actions/contentActions/contentActions.ts) |
38
- | Conversation | `useMetropolis(['conversation'])` | `createConversationActions` | `add`, `itemById`, `list`, `update`, `delete` | [conversationActions.ts](../src/actions/conversationActions/conversationActions.ts) |
39
- | Event | `useEventActions` | `createEventActions` | `addEvent`, `getEvent`, `getEventsByTags`, `getEventsByReactions`, `updateEvent`, `deleteEvent` | [eventActions.ts](../src/actions/eventActions/eventActions.ts) |
40
- | Group | `useGroupActions` | `createGroupActions` | `add`, `itemById`, `listByLatest`, `update`, `delete` | [groupActions.ts](../src/actions/groupActions/groupActions.ts) |
41
- | Image | `useImageActions` | `createImageActions` | `add`, `update`, `delete`, `upload`, `countByItem`, `listByItem`, `listByReactions` | [imageActions.ts](../src/actions/imageActions/imageActions.ts) |
42
- | Location | `useLocationActions` | `createLocationActions` | `autocompleteLocation`, `add`, `getLocation`, `getCurrentLocation`, `listByItem`, `update`, `delete` | [locationActions.ts](../src/actions/locationActions/locationActions.ts) |
43
- | Message | `useMessageActions` | `createMessageActions` | `sendMessage`, `getMessages`, `getConversations`, `getDirectConversation` | [messageActions.ts](../src/actions/messageActions/messageActions.ts) |
44
- | Permission | `usePermissionActions` | `createPermissionActions` | `add`, `check`, `itemById`, `list`, `listByUser`, `update`, `remove` | [permissionActions.ts](../src/actions/permissionActions/permissionActions.ts) |
45
- | Post | `usePostActions` | `createPostActions` | `add`, `itemById`, `listByLatest`, `listByLocation`, `listByReactions`, `listByTags`, `update`, `delete` | [postActions.ts](../src/actions/postActions/postActions.ts) |
46
- | Persona | `usePersonaActions` | `createPersonaActions` | `addPersona`, `getPersona`, `getPersonas`, `updatePersona`, `deletePersona` | [personaActions.ts](../src/actions/personaActions/personaActions.ts) |
47
- | Reaction | `useReactionActions` | `createReactionActions` | `addReaction`, `deleteReaction`, `getReactionCount`, `hasReaction`, `abbreviateCount` | [reactionActions.ts](../src/actions/reactionActions/reactionActions.ts) |
48
- | SSE | `useMetropolis(['sse'])` | `createSSEActions` | `connect`, `disconnect`, `reconnect`, `isConnected`, `sendMessage`, `addEventListener`, `removeEventListener` | [sseActions.ts](../src/actions/sseActions/sseActions.ts) |
49
- | Tag | `useTagActions` | `createTagActions` | `addTag`, `addTagToItem`, `getTags`, `updateTag`, `deleteTag`, `deleteTagFromItem` | [tagActions.ts](../src/actions/tagActions/tagActions.ts) |
50
- | Translation | `useTranslationActions` | `createTranslationActions` | `addTranslations`, `getTranslation`, `getTranslations`, `hasTranslation`, `queueTranslationKey`, `processPendingTranslations` | [translationActions.ts](../src/actions/translationActions/translationActions.ts) |
51
- | User | `useUserActions` | `createUserActions` | `signIn`, `signUp`, `session`, `refreshSession`, `itemById`, `listByLatest`, `updateUser` | [userActions.ts](../src/actions/userActions/userActions.ts) |
52
- | Video | `useMetropolis(['video'])` | `createVideoActions` | `add`, `itemById`, `list`, `update`, `delete` | [videoActions.ts](../src/actions/videoActions/videoActions.ts) |
53
- | Websocket | `useWebsocketActions` | `createWebsocketActions` | `wsInit`, `wsSend`, `onOpen`, `onReceive`, `onClose`, `onError` | [websocketActions.ts](../src/actions/websocketActions/websocketActions.ts) |
41
+ | Content | `useContentActions` | `content` | `createContentActions` | `add`, `itemById`, `itemByKey`, `listByCategory`, `list`, `update`, `delete` | [contentActions.ts](../src/actions/contentActions/contentActions.ts) |
42
+ | CRM | `useCrmActions` | `crm` | `createCrmActions` | `mailingLists`, `createMailingList`, `createSupportTicket`, `supportTickets`, `customerOrdersByUser` | [crmActions.ts](../src/actions/crmActions/crmActions.ts) |
43
+ | Event | `useEventActions` | `event` | `createEventActions` | `addEvent`, `getEvent`, `getEventsByTags`, `getEventsByReactions`, `updateEvent`, `deleteEvent` | [eventActions.ts](../src/actions/eventActions/eventActions.ts) |
44
+ | Group | `useGroupActions` | `group` | `createGroupActions` | `add`, `itemById`, `listByLatest`, `update`, `delete` | [groupActions.ts](../src/actions/groupActions/groupActions.ts) |
45
+ | Image | `useImageActions` | `image` | `createImageActions` | `add`, `update`, `delete`, `upload`, `countByItem`, `listByItem`, `listByReactions` | [imageActions.ts](../src/actions/imageActions/imageActions.ts) |
46
+ | Location | `useLocationActions` | `location` | `createLocationActions` | `autocompleteLocation`, `add`, `getLocation`, `getCurrentLocation`, `listByItem`, `update`, `delete` | [locationActions.ts](../src/actions/locationActions/locationActions.ts) |
47
+ | Message | `useMessageActions` | `message` | `createMessageActions` | `sendMessage`, `getMessages`, `getConversations`, `getDirectConversation` | [messageActions.ts](../src/actions/messageActions/messageActions.ts) |
48
+ | Permission | `usePermissionActions` | `permission` | `createPermissionActions` | `add`, `check`, `itemById`, `list`, `listByUser`, `update`, `remove` | [permissionActions.ts](../src/actions/permissionActions/permissionActions.ts) |
49
+ | Post | `usePostActions` | `post` | `createPostActions` | `add`, `itemById`, `listByLatest`, `listByLocation`, `listByReactions`, `listByTags`, `update`, `delete` | [postActions.ts](../src/actions/postActions/postActions.ts) |
50
+ | Persona | `usePersonaActions` | `persona` | `createPersonaActions` | `addPersona`, `getPersona`, `getPersonas`, `updatePersona`, `deletePersona` | [personaActions.ts](../src/actions/personaActions/personaActions.ts) |
51
+ | Reaction | `useReactionActions` | `reaction` | `createReactionActions` | `addReaction`, `deleteReaction`, `getReactionCount`, `hasReaction`, `abbreviateCount` | [reactionActions.ts](../src/actions/reactionActions/reactionActions.ts) |
52
+ | REST | `useRestActions` | `rest` | `createRestActions` | `get`, `post`, `put`, `delete`, `request` | [restActions.ts](../src/actions/restActions/restActions.ts) |
53
+ | SSE | `useMetropolis(['sse'])` | `sse` | `createSSEActions` | `connect`, `disconnect`, `reconnect`, `isConnected`, `sendMessage`, `addEventListener`, `removeEventListener` | [sseActions.ts](../src/actions/sseActions/sseActions.ts) |
54
+ | Subscription | `useSubscriptionActions` | `subscription` | `createSubscriptionActions` | `addPlan`, `getPlanByItem`, `addSubscription`, `getSubscriptionByItem`, `getSubscriptionListByUser`, `deleteSubscription` | [subscriptionActions.ts](../src/actions/subscriptionActions/subscriptionActions.ts) |
55
+ | Tag | `useTagActions` | `tag` | `createTagActions` | `addTag`, `addTagToItem`, `getTags`, `updateTag`, `deleteTag`, `deleteTagFromItem` | [tagActions.ts](../src/actions/tagActions/tagActions.ts) |
56
+ | Translation | `useTranslationActions` | `translation` | `createTranslationActions` | `addTranslations`, `getTranslation`, `getTranslations`, `hasTranslation`, `queueTranslationKey`, `processPendingTranslations` | [translationActions.ts](../src/actions/translationActions/translationActions.ts) |
57
+ | User | `useUserActions` | `user` | `createUserActions` | `signIn`, `signUp`, `session`, `refreshSession`, `itemById`, `listByLatest`, `updateUser` | [userActions.ts](../src/actions/userActions/userActions.ts) |
58
+ | Video | `useVideoActions` | `video` | `createVideoActions` | `add`, `itemById`, `list`, `update`, `delete` | [videoActions.ts](../src/actions/videoActions/videoActions.ts) |
59
+ | Websocket | `useWebsocketActions` | `websocket` | `createWebsocketActions` | `wsInit`, `wsSend`, `onOpen`, `onReceive`, `onClose`, `onError` | [websocketActions.ts](../src/actions/websocketActions/websocketActions.ts) |
60
+
61
+ ### Direct Creator Actions
62
+
63
+ These creators are exported directly from MetropolisJS, but are not currently part of the `createAction(...)`/`useMetropolis([...])` factory key union.
64
+
65
+ | Family | Creator | Typical Methods | Source |
66
+ | --- | --- | --- | --- |
67
+ | App | `createAppActions` | `add`, `itemById`, `list`, `update`, `delete` | [appActions.ts](../src/actions/appActions/appActions.ts) |
68
+ | Connection | `createConnectionActions` | `addConnection`, `getConnections`, `removeConnection` | [connectionActions.ts](../src/actions/connectionActions/connectionActions.ts) |
69
+ | Conversation | `createConversationActions` | `add`, `itemById`, `list`, `update`, `delete` | [conversationActions.ts](../src/actions/conversationActions/conversationActions.ts) |
70
+
71
+ ## REST Actions
72
+
73
+ Use REST actions for external APIs that are not represented in Reaktor. REST actions delegate to `@nlabs/rip-hunter`, share Metropolis network/session handling, and can target either a configured endpoint key or an absolute URL.
74
+
75
+ Configure named endpoints under `app.api.endpoints`:
76
+
77
+ ```tsx
78
+ <Metropolis
79
+ config={{
80
+ development: {
81
+ app: {
82
+ api: {
83
+ endpoints: {
84
+ weather: 'https://api.example.com/weather'
85
+ },
86
+ public: 'http://localhost:3000/public',
87
+ url: 'http://localhost:3000/app'
88
+ }
89
+ }
90
+ }
91
+ }}
92
+ >
93
+ <YourApp />
94
+ </Metropolis>
95
+ ```
96
+
97
+ Call the endpoint through `useRestActions()`:
98
+
99
+ ```ts
100
+ const restActions = useRestActions();
101
+
102
+ const weather = await restActions.get('weather', {zip: '60601'}, {cache: true});
103
+ const created = await restActions.post('https://api.example.com/items', {name: 'Item'});
104
+ ```
105
+
106
+ Pass `authenticate: true` only when the endpoint should receive the current Metropolis session token:
107
+
108
+ ```ts
109
+ const profile = await restActions.request(
110
+ 'https://api.example.com/profile',
111
+ 'PATCH',
112
+ {displayName: 'Ada'},
113
+ {authenticate: true}
114
+ );
115
+ ```
54
116
 
55
117
  ## Full Exports
56
118
 
@@ -101,12 +101,17 @@ export const runtimeUpdates = (flux: FluxFramework) => {
101
101
  // Example 4: Testing with Mock Adapters
102
102
  export const testingExample = (flux: FluxFramework) => {
103
103
  // Mock adapter for testing
104
- const mockUserAdapter = jest.fn((input: unknown) => ({
105
- ...(input as Record<string, unknown>),
106
- id: 'mock-user-id',
107
- validated: true,
108
- timestamp: new Date().toISOString()
109
- }));
104
+ const mockUserAdapter = Object.assign((input: unknown) => {
105
+ mockUserAdapter.calls.push(input);
106
+ return {
107
+ ...(input as Record<string, unknown>),
108
+ id: 'mock-user-id',
109
+ validated: true,
110
+ timestamp: new Date().toISOString()
111
+ };
112
+ }, {
113
+ calls: [] as unknown[]
114
+ });
110
115
 
111
116
  const userActions = createAction('user', flux, {
112
117
  userAdapter: mockUserAdapter
@@ -119,9 +124,9 @@ export const testingExample = (flux: FluxFramework) => {
119
124
  email: 'test@example.com'
120
125
  });
121
126
 
122
- expect(mockUserAdapter).toHaveBeenCalled();
123
- expect(user.id).toBe('mock-user-id');
124
- expect(user.validated).toBe(true);
127
+ if(mockUserAdapter.calls.length === 0 || user.id !== 'mock-user-id' || user.validated !== true) {
128
+ throw new Error('Mock adapter validation failed');
129
+ }
125
130
  };
126
131
 
127
132
  return {userActions, testUserCreation};
@@ -280,4 +285,4 @@ export const migrationExample = (flux: FluxFramework) => {
280
285
  };
281
286
 
282
287
  return { createUser, createPost };
283
- };
288
+ };
package/lex.config.mjs CHANGED
@@ -12,13 +12,6 @@ export default Config.create({
12
12
  minify: process.env.NODE_ENV === 'production'
13
13
  },
14
14
  gitUrl: 'https://github.com/nitrogenlabs/metropolisjs',
15
- jest: {
16
- extensionsToTreatAsEsm: ['.ts', '.tsx'],
17
- moduleNameMapper: {
18
- '^@nlabs/(.*)$': '<rootDir>/node_modules/@nlabs/$1'
19
- },
20
- testEnvironment: 'jsdom'
21
- },
22
15
  outputPath: 'lib',
23
16
  targetEnvironment: 'web',
24
17
  useESM: true,
@@ -53,6 +46,10 @@ export default Config.create({
53
46
  lines: 90,
54
47
  statements: 90
55
48
  }
56
- }
49
+ },
50
+ globals: true,
51
+ hookTimeout: 120000,
52
+ testEnvironment: 'jsdom',
53
+ testTimeout: 120000
57
54
  }
58
55
  });
@@ -1 +1 @@
1
- {"version":3,"file":"appActions.d.ts","sourceRoot":"","sources":["../../../src/actions/appActions/appActions.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AAItE,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,GAAG,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE;QACJ,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxG,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1G,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrH,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChH,gBAAgB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,GAAG,KAAK,IAAI,CAAC;IAC1F,uBAAuB,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/D;AAKD,eAAO,MAAM,gBAAgB,GAC3B,MAAM,aAAa,EACnB,UAAU,iBAAiB,KAC1B,UAyLF,CAAC"}
1
+ {"version":3,"file":"appActions.d.ts","sourceRoot":"","sources":["../../../src/actions/appActions/appActions.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AAItE,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,GAAG,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE;QACJ,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxG,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1G,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrH,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChH,gBAAgB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,KAAK,GAAG,KAAK,IAAI,CAAC;IAC1F,uBAAuB,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/D;AAKD,eAAO,MAAM,gBAAgB,GAC3B,MAAM,aAAa,EACnB,UAAU,iBAAiB,KAC1B,UAuNF,CAAC"}