@nlabs/metropolisjs 0.1.2 → 0.3.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.
Files changed (329) hide show
  1. package/.github/copilot-instructions.md +167 -0
  2. package/AGENTS.md +13 -0
  3. package/CHANGELOG.md +112 -0
  4. package/GRAPHQL_VALIDATION_RESULTS.md +158 -0
  5. package/PERMISSION_GRAPHQL_INTEGRATION.md +293 -0
  6. package/PERMISSION_IMPLEMENTATION_SUMMARY.md +243 -0
  7. package/README.md +1123 -112
  8. package/SIGNIN_GRAPHQL_VALIDATION.md +125 -0
  9. package/VAULT_GRAPHQL_API_FINDINGS.md +312 -0
  10. package/docs/ACTIONS.md +63 -0
  11. package/docs/COLLECTIONS.md +1038 -0
  12. package/docs/CONNECTIONS.md +1367 -0
  13. package/docs/CRUD_INTEGRATION.md +702 -0
  14. package/examples/connections-usage.tsx +909 -0
  15. package/examples/content-actions-usage.ts +144 -0
  16. package/examples/crud-usage.tsx +847 -0
  17. package/examples/extensibility-usage.tsx +889 -0
  18. package/examples/i18n-import-examples.ts +86 -0
  19. package/examples/i18n-namespace-usage.ts +118 -0
  20. package/examples/i18next-singleton-demo.ts +187 -0
  21. package/examples/permission-system-usage.tsx +415 -0
  22. package/examples/project-details.tsx +397 -0
  23. package/examples/shared-i18n-usage.tsx +203 -0
  24. package/examples/signup-error-handling.ts +100 -0
  25. package/examples/translation-types.ts +86 -0
  26. package/examples/translation-usage.tsx +336 -0
  27. package/index.js +1 -0
  28. package/jest.config.mjs +28 -0
  29. package/lex.config.mjs +11 -2
  30. package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts +22 -0
  31. package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts.map +1 -0
  32. package/lib/__tests__/e2e/helpers/testGraphQLServer.js +197 -0
  33. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts +3 -0
  34. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts.map +1 -0
  35. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.js +52 -0
  36. package/lib/actions/appActions/appActions.d.ts +29 -0
  37. package/lib/actions/appActions/appActions.d.ts.map +1 -0
  38. package/lib/actions/appActions/appActions.js +159 -0
  39. package/lib/actions/connectionActions/connectionActions.d.ts +19 -0
  40. package/lib/actions/connectionActions/connectionActions.d.ts.map +1 -0
  41. package/lib/actions/connectionActions/connectionActions.js +131 -0
  42. package/lib/actions/contentActions/contentActions.d.ts +34 -0
  43. package/lib/actions/contentActions/contentActions.d.ts.map +1 -0
  44. package/lib/actions/contentActions/contentActions.js +314 -0
  45. package/lib/actions/conversationActions/conversationActions.d.ts +30 -0
  46. package/lib/actions/conversationActions/conversationActions.d.ts.map +1 -0
  47. package/lib/actions/conversationActions/conversationActions.js +225 -0
  48. package/lib/actions/eventActions/eventActions.d.ts +27 -7
  49. package/lib/actions/eventActions/eventActions.d.ts.map +1 -0
  50. package/lib/actions/eventActions/eventActions.js +436 -2
  51. package/lib/actions/groupActions/groupActions.d.ts +30 -0
  52. package/lib/actions/groupActions/groupActions.d.ts.map +1 -0
  53. package/lib/actions/groupActions/groupActions.js +229 -0
  54. package/lib/actions/imageActions/imageActions.d.ts +16 -10
  55. package/lib/actions/imageActions/imageActions.d.ts.map +1 -0
  56. package/lib/actions/imageActions/imageActions.js +593 -2
  57. package/lib/actions/index.d.ts +21 -9
  58. package/lib/actions/index.d.ts.map +1 -0
  59. package/lib/actions/index.js +22 -2
  60. package/lib/actions/locationActions/locationActions.d.ts +11 -9
  61. package/lib/actions/locationActions/locationActions.d.ts.map +1 -0
  62. package/lib/actions/locationActions/locationActions.js +388 -2
  63. package/lib/actions/messageActions/messageActions.d.ts +16 -32
  64. package/lib/actions/messageActions/messageActions.d.ts.map +1 -0
  65. package/lib/actions/messageActions/messageActions.js +235 -2
  66. package/lib/actions/permissionActions/permissionActions.d.ts +34 -0
  67. package/lib/actions/permissionActions/permissionActions.d.ts.map +1 -0
  68. package/lib/actions/permissionActions/permissionActions.js +330 -0
  69. package/lib/actions/personaActions/personaActions.d.ts +65 -0
  70. package/lib/actions/personaActions/personaActions.d.ts.map +1 -0
  71. package/lib/actions/personaActions/personaActions.js +572 -0
  72. package/lib/actions/postActions/postActions.d.ts +12 -32
  73. package/lib/actions/postActions/postActions.d.ts.map +1 -0
  74. package/lib/actions/postActions/postActions.js +447 -2
  75. package/lib/actions/reactionActions/reactionActions.d.ts +13 -27
  76. package/lib/actions/reactionActions/reactionActions.d.ts.map +1 -0
  77. package/lib/actions/reactionActions/reactionActions.js +413 -2
  78. package/lib/actions/sseActions/sseActions.d.ts +1 -0
  79. package/lib/actions/sseActions/sseActions.d.ts.map +1 -0
  80. package/lib/actions/sseActions/sseActions.js +148 -2
  81. package/lib/actions/subscriptionActions/subscriptionActions.d.ts +24 -0
  82. package/lib/actions/subscriptionActions/subscriptionActions.d.ts.map +1 -0
  83. package/lib/actions/subscriptionActions/subscriptionActions.js +329 -0
  84. package/lib/actions/tagActions/tagActions.d.ts +13 -31
  85. package/lib/actions/tagActions/tagActions.d.ts.map +1 -0
  86. package/lib/actions/tagActions/tagActions.js +347 -2
  87. package/lib/actions/translationActions/translationActions.d.ts +38 -0
  88. package/lib/actions/translationActions/translationActions.d.ts.map +1 -0
  89. package/lib/actions/translationActions/translationActions.js +189 -0
  90. package/lib/actions/userActions/userActions.d.ts +44 -95
  91. package/lib/actions/userActions/userActions.d.ts.map +1 -0
  92. package/lib/actions/userActions/userActions.js +870 -2
  93. package/lib/actions/videoActions/videoActions.d.ts +41 -0
  94. package/lib/actions/videoActions/videoActions.d.ts.map +1 -0
  95. package/lib/actions/videoActions/videoActions.js +532 -0
  96. package/lib/actions/websocketActions/websocketActions.d.ts +20 -21
  97. package/lib/actions/websocketActions/websocketActions.d.ts.map +1 -0
  98. package/lib/actions/websocketActions/websocketActions.js +377 -2
  99. package/lib/adapters/appAdapter/appAdapter.d.ts +8 -0
  100. package/lib/adapters/appAdapter/appAdapter.d.ts.map +1 -0
  101. package/lib/adapters/appAdapter/appAdapter.js +90 -0
  102. package/lib/adapters/arangoAdapter/arangoAdapter.d.ts +3 -2
  103. package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -0
  104. package/lib/adapters/arangoAdapter/arangoAdapter.js +75 -2
  105. package/lib/adapters/contentAdapter/contentAdapter.d.ts +3 -2
  106. package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -0
  107. package/lib/adapters/contentAdapter/contentAdapter.js +107 -2
  108. package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +3 -2
  109. package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -0
  110. package/lib/adapters/conversationAdapter/conversationAdapter.js +75 -2
  111. package/lib/adapters/eventAdapter/eventAdapter.d.ts +5 -2
  112. package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -0
  113. package/lib/adapters/eventAdapter/eventAdapter.js +162 -2
  114. package/lib/adapters/fileAdapter/fileAdapter.d.ts +3 -2
  115. package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -0
  116. package/lib/adapters/fileAdapter/fileAdapter.js +118 -2
  117. package/lib/adapters/groupAdapter/groupAdapter.d.ts +28 -0
  118. package/lib/adapters/groupAdapter/groupAdapter.d.ts.map +1 -0
  119. package/lib/adapters/groupAdapter/groupAdapter.js +66 -0
  120. package/lib/adapters/imageAdapter/imageAdapter.d.ts +9 -2
  121. package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -0
  122. package/lib/adapters/imageAdapter/imageAdapter.js +134 -2
  123. package/lib/adapters/index.d.ts +34 -16
  124. package/lib/adapters/index.d.ts.map +1 -0
  125. package/lib/adapters/index.js +19 -2
  126. package/lib/adapters/locationAdapter/locationAdapter.d.ts +3 -2
  127. package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -0
  128. package/lib/adapters/locationAdapter/locationAdapter.js +95 -2
  129. package/lib/adapters/messageAdapter/messageAdapter.d.ts +23 -19
  130. package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -0
  131. package/lib/adapters/messageAdapter/messageAdapter.js +82 -2
  132. package/lib/adapters/permissionAdapter/permissionAdapter.d.ts +41 -0
  133. package/lib/adapters/permissionAdapter/permissionAdapter.d.ts.map +1 -0
  134. package/lib/adapters/permissionAdapter/permissionAdapter.js +120 -0
  135. package/lib/adapters/personaAdapter/personaAdapter.d.ts +28 -2
  136. package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -0
  137. package/lib/adapters/personaAdapter/personaAdapter.js +161 -2
  138. package/lib/adapters/postAdapter/postAdapter.d.ts +3 -2
  139. package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -0
  140. package/lib/adapters/postAdapter/postAdapter.js +115 -2
  141. package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +5 -2
  142. package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -0
  143. package/lib/adapters/reactionAdapter/reactionAdapter.js +81 -2
  144. package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +3 -2
  145. package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -0
  146. package/lib/adapters/reaktorAdapter/reaktorAdapter.js +58 -2
  147. package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +3 -2
  148. package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -0
  149. package/lib/adapters/sessionAdapter/sessionAdapter.js +80 -2
  150. package/lib/adapters/tagAdapter/tagAdapter.d.ts +3 -2
  151. package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -0
  152. package/lib/adapters/tagAdapter/tagAdapter.js +98 -2
  153. package/lib/adapters/translationAdapter/translationAdapter.d.ts +21 -0
  154. package/lib/adapters/translationAdapter/translationAdapter.d.ts.map +1 -0
  155. package/lib/adapters/translationAdapter/translationAdapter.js +55 -0
  156. package/lib/adapters/userAdapter/userAdapter.d.ts +18 -6
  157. package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -0
  158. package/lib/adapters/userAdapter/userAdapter.js +302 -2
  159. package/lib/adapters/videoAdapter/videoAdapter.d.ts +8 -0
  160. package/lib/adapters/videoAdapter/videoAdapter.d.ts.map +1 -0
  161. package/lib/adapters/videoAdapter/videoAdapter.js +194 -0
  162. package/lib/config/index.d.ts +19 -6
  163. package/lib/config/index.d.ts.map +1 -0
  164. package/lib/config/index.js +97 -2
  165. package/lib/constants/Collections.d.ts +44 -0
  166. package/lib/constants/Collections.d.ts.map +1 -0
  167. package/lib/constants/Collections.js +40 -0
  168. package/lib/constants/MetropolisConstants.d.ts +1 -0
  169. package/lib/constants/MetropolisConstants.d.ts.map +1 -0
  170. package/lib/constants/MetropolisConstants.js +15 -2
  171. package/lib/graphql/message.d.ts +1 -0
  172. package/lib/graphql/message.d.ts.map +1 -0
  173. package/lib/graphql/message.js +6 -4
  174. package/lib/graphql/notification.d.ts +1 -0
  175. package/lib/graphql/notification.d.ts.map +1 -0
  176. package/lib/graphql/notification.js +6 -4
  177. package/lib/graphql/session.d.ts +1 -0
  178. package/lib/graphql/session.d.ts.map +1 -0
  179. package/lib/graphql/session.js +4 -3
  180. package/lib/index.d.ts +45 -22
  181. package/lib/index.d.ts.map +1 -0
  182. package/lib/index.js +248 -2
  183. package/lib/stores/appStore.d.ts +1 -0
  184. package/lib/stores/appStore.d.ts.map +1 -0
  185. package/lib/stores/appStore.js +23 -2
  186. package/lib/stores/contentStore.d.ts +36 -0
  187. package/lib/stores/contentStore.d.ts.map +1 -0
  188. package/lib/stores/contentStore.js +139 -0
  189. package/lib/stores/conversationStore.d.ts +38 -0
  190. package/lib/stores/conversationStore.d.ts.map +1 -0
  191. package/lib/stores/conversationStore.js +117 -0
  192. package/lib/stores/eventStore.d.ts +4 -1
  193. package/lib/stores/eventStore.d.ts.map +1 -0
  194. package/lib/stores/eventStore.js +47 -2
  195. package/lib/stores/groupStore.d.ts +37 -0
  196. package/lib/stores/groupStore.d.ts.map +1 -0
  197. package/lib/stores/groupStore.js +68 -0
  198. package/lib/stores/imageStore.d.ts +6 -1
  199. package/lib/stores/imageStore.d.ts.map +1 -0
  200. package/lib/stores/imageStore.js +53 -2
  201. package/lib/stores/index.d.ts +20 -11
  202. package/lib/stores/index.d.ts.map +1 -0
  203. package/lib/stores/index.js +24 -2
  204. package/lib/stores/locationStore.d.ts +2 -1
  205. package/lib/stores/locationStore.d.ts.map +1 -0
  206. package/lib/stores/locationStore.js +42 -2
  207. package/lib/stores/messageStore.d.ts +19 -4
  208. package/lib/stores/messageStore.d.ts.map +1 -0
  209. package/lib/stores/messageStore.js +137 -2
  210. package/lib/stores/notificationStore.d.ts +33 -0
  211. package/lib/stores/notificationStore.d.ts.map +1 -0
  212. package/lib/stores/notificationStore.js +55 -0
  213. package/lib/stores/permissionStore.d.ts +37 -0
  214. package/lib/stores/permissionStore.d.ts.map +1 -0
  215. package/lib/stores/permissionStore.js +117 -0
  216. package/lib/stores/personaStore.d.ts +26 -0
  217. package/lib/stores/personaStore.d.ts.map +1 -0
  218. package/lib/stores/personaStore.js +118 -0
  219. package/lib/stores/postStore.d.ts +2 -1
  220. package/lib/stores/postStore.d.ts.map +1 -0
  221. package/lib/stores/postStore.js +53 -2
  222. package/lib/stores/reactionStore.d.ts +1 -0
  223. package/lib/stores/reactionStore.d.ts.map +1 -0
  224. package/lib/stores/reactionStore.js +28 -2
  225. package/lib/stores/subscriptionStore.d.ts +41 -0
  226. package/lib/stores/subscriptionStore.d.ts.map +1 -0
  227. package/lib/stores/subscriptionStore.js +85 -0
  228. package/lib/stores/tagStore.d.ts +8 -5
  229. package/lib/stores/tagStore.d.ts.map +1 -0
  230. package/lib/stores/tagStore.js +87 -2
  231. package/lib/stores/translationStore.d.ts +42 -0
  232. package/lib/stores/translationStore.d.ts.map +1 -0
  233. package/lib/stores/translationStore.js +122 -0
  234. package/lib/stores/userStore.d.ts +5 -4
  235. package/lib/stores/userStore.d.ts.map +1 -0
  236. package/lib/stores/userStore.js +319 -2
  237. package/lib/stores/videoStore.d.ts +39 -0
  238. package/lib/stores/videoStore.d.ts.map +1 -0
  239. package/lib/stores/videoStore.js +132 -0
  240. package/lib/stores/websocketStore.d.ts +1 -0
  241. package/lib/stores/websocketStore.d.ts.map +1 -0
  242. package/lib/stores/websocketStore.js +51 -2
  243. package/lib/types/apps.types.d.ts +17 -0
  244. package/lib/types/apps.types.d.ts.map +1 -0
  245. package/lib/types/apps.types.js +6 -0
  246. package/lib/types/common.types.d.ts +31 -0
  247. package/lib/types/common.types.d.ts.map +1 -0
  248. package/lib/types/common.types.js +6 -0
  249. package/lib/types/conversations.types.d.ts +17 -0
  250. package/lib/types/conversations.types.d.ts.map +1 -0
  251. package/lib/types/conversations.types.js +6 -0
  252. package/lib/types/edges.types.d.ts +38 -0
  253. package/lib/types/edges.types.d.ts.map +1 -0
  254. package/lib/types/edges.types.js +6 -0
  255. package/lib/types/groups.types.d.ts +19 -0
  256. package/lib/types/groups.types.d.ts.map +1 -0
  257. package/lib/types/groups.types.js +6 -0
  258. package/lib/types/index.d.ts +12 -0
  259. package/lib/types/index.d.ts.map +1 -0
  260. package/lib/types/index.js +12 -0
  261. package/lib/types/subscriptions.types.d.ts +32 -0
  262. package/lib/types/subscriptions.types.d.ts.map +1 -0
  263. package/lib/types/subscriptions.types.js +6 -0
  264. package/lib/types/videos.types.d.ts +61 -0
  265. package/lib/types/videos.types.d.ts.map +1 -0
  266. package/lib/types/videos.types.js +6 -0
  267. package/lib/utils/MetropolisProvider.d.ts +39 -15
  268. package/lib/utils/MetropolisProvider.d.ts.map +1 -0
  269. package/lib/utils/MetropolisProvider.js +31 -2
  270. package/lib/utils/actionFactory.d.ts +21 -19
  271. package/lib/utils/actionFactory.d.ts.map +1 -0
  272. package/lib/utils/actionFactory.js +95 -2
  273. package/lib/utils/api.d.ts +5 -4
  274. package/lib/utils/api.d.ts.map +1 -0
  275. package/lib/utils/api.js +252 -6
  276. package/lib/utils/app.d.ts +1 -0
  277. package/lib/utils/app.d.ts.map +1 -0
  278. package/lib/utils/app.js +15 -2
  279. package/lib/utils/baseActionFactory.d.ts +5 -23
  280. package/lib/utils/baseActionFactory.d.ts.map +1 -0
  281. package/lib/utils/baseActionFactory.js +72 -2
  282. package/lib/utils/configUtils.d.ts +11 -0
  283. package/lib/utils/configUtils.d.ts.map +1 -0
  284. package/lib/utils/configUtils.js +20 -0
  285. package/lib/utils/dateUtils.d.ts +1 -0
  286. package/lib/utils/dateUtils.d.ts.map +1 -0
  287. package/lib/utils/dateUtils.js +11 -2
  288. package/lib/utils/file.d.ts +3 -1
  289. package/lib/utils/file.d.ts.map +1 -0
  290. package/lib/utils/file.js +156 -2
  291. package/lib/utils/i18n.d.ts +7 -0
  292. package/lib/utils/i18n.d.ts.map +1 -0
  293. package/lib/utils/i18n.js +96 -0
  294. package/lib/utils/index.d.ts +20 -0
  295. package/lib/utils/index.d.ts.map +1 -0
  296. package/lib/utils/index.js +26 -0
  297. package/lib/utils/location.d.ts +4 -3
  298. package/lib/utils/location.d.ts.map +1 -0
  299. package/lib/utils/location.js +82 -2
  300. package/lib/utils/permissionUtils.d.ts +22 -0
  301. package/lib/utils/permissionUtils.d.ts.map +1 -0
  302. package/lib/utils/permissionUtils.js +72 -0
  303. package/lib/utils/requestCache.d.ts +8 -0
  304. package/lib/utils/requestCache.d.ts.map +1 -0
  305. package/lib/utils/requestCache.js +53 -0
  306. package/lib/utils/session.d.ts +14 -0
  307. package/lib/utils/session.d.ts.map +1 -0
  308. package/lib/utils/session.js +107 -0
  309. package/lib/utils/useConversationTyping.d.ts +26 -0
  310. package/lib/utils/useConversationTyping.d.ts.map +1 -0
  311. package/lib/utils/useConversationTyping.js +117 -0
  312. package/lib/utils/useMetropolis.d.ts +67 -12
  313. package/lib/utils/useMetropolis.d.ts.map +1 -0
  314. package/lib/utils/useMetropolis.js +311 -2
  315. package/lib/utils/useTranslations.d.ts +16 -0
  316. package/lib/utils/useTranslations.d.ts.map +1 -0
  317. package/lib/utils/useTranslations.js +105 -0
  318. package/lib/utils/validatorFactory.d.ts +3 -2
  319. package/lib/utils/validatorFactory.d.ts.map +1 -0
  320. package/lib/utils/validatorFactory.js +95 -2
  321. package/package.json +58 -17
  322. package/temp_signin.ts +73 -0
  323. package/lib/actions/profileActions/profileActions.d.ts +0 -50
  324. package/lib/actions/profileActions/profileActions.js +0 -2
  325. package/lib/adapters/profileAdapter/profileAdapter.d.ts +0 -32
  326. package/lib/adapters/profileAdapter/profileAdapter.js +0 -2
  327. package/lib/stores/profileStore.d.ts +0 -24
  328. package/lib/stores/profileStore.js +0 -2
  329. /package/{FACTORY_PATTERN_GUIDE.md → factoryPatternGuide.md} +0 -0
@@ -0,0 +1,1367 @@
1
+ # MetropolisJS Connections Guide
2
+
3
+ Comprehensive guide for managing relationships between collections using connections, reactions, tags, conversations, and file associations.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Connection Types](#connection-types)
9
+ - [Edge Types](#edge-types)
10
+ - [Connection Actions](#connection-actions)
11
+ - [Reaction Management](#reaction-management)
12
+ - [Tag Management](#tag-management)
13
+ - [File Associations](#file-associations)
14
+ - [Conversation Links](#conversation-links)
15
+ - [Advanced Patterns](#advanced-patterns)
16
+ - [Best Practices](#best-practices)
17
+
18
+ ## Overview
19
+
20
+ MetropolisJS uses ArangoDB's edge collections to model relationships between documents. These relationships are managed through specialized actions that create, query, and remove edges between collections.
21
+
22
+ ### Core Concepts
23
+
24
+ **Collections**: Documents like users, posts, groups (vertices in graph database)
25
+ **Edges**: Relationships between collections (edges in graph database)
26
+ **Connection Types**: Semantic meaning of relationships (admin, member, follower, etc.)
27
+
28
+ ### Constants
29
+
30
+ ```typescript
31
+ import {COLLECTIONS, EDGES, CONNECTION_TYPES, REACTION_TYPES} from '@nlabs/metropolisjs';
32
+
33
+ // Collection types
34
+ const collections = {
35
+ APPS: 'apps',
36
+ CONVERSATIONS: 'conversations',
37
+ FILES: 'files',
38
+ GROUPS: 'groups',
39
+ IMAGES: 'images',
40
+ MESSAGES: 'messages',
41
+ POSTS: 'posts',
42
+ PERSONAS: 'personas',
43
+ TAGS: 'tags',
44
+ USERS: 'users',
45
+ VIDEOS: 'videos'
46
+ };
47
+
48
+ // Edge types
49
+ const edges = {
50
+ HAS_CONNECTION: 'hasConnection',
51
+ HAS_CONVERSATION: 'hasConversation',
52
+ HAS_FILE: 'hasFile',
53
+ HAS_REACTION: 'hasReaction',
54
+ IS_TAGGED: 'isTagged'
55
+ };
56
+
57
+ // Connection types
58
+ const connectionTypes = {
59
+ ADMIN: 'admin',
60
+ BLOCKED: 'blocked',
61
+ FOLLOWER: 'follower',
62
+ FOLLOWING: 'following',
63
+ MEMBER: 'member'
64
+ };
65
+
66
+ // Reaction types
67
+ const reactionTypes = {
68
+ DISLIKE: 'dislike',
69
+ LIKE: 'like',
70
+ PIN: 'pin',
71
+ RSVP: 'rsvp',
72
+ VIEW: 'view'
73
+ };
74
+ ```
75
+
76
+ ## Connection Types
77
+
78
+ Connection types define the semantic meaning of relationships between collections.
79
+
80
+ ### ADMIN
81
+
82
+ Administrative privileges and ownership.
83
+
84
+ ```typescript
85
+ import {CONNECTION_TYPES, COLLECTIONS} from '@nlabs/metropolisjs';
86
+
87
+ // User is admin of group
88
+ await connectionActions.addConnection(
89
+ COLLECTIONS.USERS,
90
+ 'user123',
91
+ COLLECTIONS.GROUPS,
92
+ 'group456',
93
+ CONNECTION_TYPES.ADMIN,
94
+ {
95
+ permissions: ['manage_members', 'edit_settings', 'delete_posts'],
96
+ grantedAt: Date.now()
97
+ }
98
+ );
99
+
100
+ // Check if user is admin
101
+ const connections = await connectionActions.getConnections(
102
+ COLLECTIONS.USERS,
103
+ 'user123',
104
+ COLLECTIONS.GROUPS,
105
+ {connectionType: CONNECTION_TYPES.ADMIN}
106
+ );
107
+
108
+ const isAdmin = connections.some(conn => conn.toId === 'group456');
109
+ ```
110
+
111
+ ### MEMBER
112
+
113
+ Standard membership in groups or organizations.
114
+
115
+ ```typescript
116
+ // Add user to group
117
+ await connectionActions.addConnection(
118
+ COLLECTIONS.USERS,
119
+ 'user123',
120
+ COLLECTIONS.GROUPS,
121
+ 'group456',
122
+ CONNECTION_TYPES.MEMBER,
123
+ {
124
+ joinedAt: Date.now(),
125
+ role: 'contributor'
126
+ }
127
+ );
128
+
129
+ // List group members
130
+ const members = await connectionActions.getConnections(
131
+ COLLECTIONS.GROUPS,
132
+ 'group456',
133
+ COLLECTIONS.USERS,
134
+ {connectionType: CONNECTION_TYPES.MEMBER}
135
+ );
136
+
137
+ console.log(`Group has ${members.length} members`);
138
+ ```
139
+
140
+ ### FOLLOWER / FOLLOWING
141
+
142
+ Social following relationships.
143
+
144
+ ```typescript
145
+ // User follows another user
146
+ await connectionActions.addConnection(
147
+ COLLECTIONS.USERS,
148
+ 'user123', // follower
149
+ COLLECTIONS.USERS,
150
+ 'user456', // following
151
+ CONNECTION_TYPES.FOLLOWING
152
+ );
153
+
154
+ // This creates complementary relationship
155
+ // user123 FOLLOWING user456
156
+ // user456 has FOLLOWER user123
157
+
158
+ // Get users I'm following
159
+ const following = await connectionActions.getConnections(
160
+ COLLECTIONS.USERS,
161
+ 'user123',
162
+ COLLECTIONS.USERS,
163
+ {connectionType: CONNECTION_TYPES.FOLLOWING}
164
+ );
165
+
166
+ // Get my followers
167
+ const followers = await connectionActions.getConnections(
168
+ COLLECTIONS.USERS,
169
+ 'user123',
170
+ COLLECTIONS.USERS,
171
+ {connectionType: CONNECTION_TYPES.FOLLOWER}
172
+ );
173
+
174
+ console.log(`Following ${following.length}, Followers ${followers.length}`);
175
+ ```
176
+
177
+ ### BLOCKED
178
+
179
+ Block relationships for moderation.
180
+
181
+ ```typescript
182
+ // Block a user
183
+ await connectionActions.addConnection(
184
+ COLLECTIONS.USERS,
185
+ 'user123',
186
+ COLLECTIONS.USERS,
187
+ 'user456',
188
+ CONNECTION_TYPES.BLOCKED,
189
+ {
190
+ reason: 'spam',
191
+ blockedAt: Date.now()
192
+ }
193
+ );
194
+
195
+ // Check if blocked
196
+ const blockedUsers = await connectionActions.getConnections(
197
+ COLLECTIONS.USERS,
198
+ 'user123',
199
+ COLLECTIONS.USERS,
200
+ {connectionType: CONNECTION_TYPES.BLOCKED}
201
+ );
202
+
203
+ const isBlocked = blockedUsers.some(conn => conn.toId === 'user456');
204
+ ```
205
+
206
+ ## Edge Types
207
+
208
+ Edge types define different categories of relationships in the system.
209
+
210
+ ### HAS_CONNECTION (hasConnection)
211
+
212
+ General connections between any collections.
213
+
214
+ ```typescript
215
+ import {EDGES, COLLECTIONS, CONNECTION_TYPES} from '@nlabs/metropolisjs';
216
+
217
+ const connectionActions = createConnectionActions(flux);
218
+
219
+ // User connects to group
220
+ await connectionActions.addConnection(
221
+ COLLECTIONS.USERS,
222
+ 'user123',
223
+ COLLECTIONS.GROUPS,
224
+ 'group456',
225
+ CONNECTION_TYPES.MEMBER
226
+ );
227
+
228
+ // Post connects to group
229
+ await connectionActions.addConnection(
230
+ COLLECTIONS.POSTS,
231
+ 'post789',
232
+ COLLECTIONS.GROUPS,
233
+ 'group456',
234
+ CONNECTION_TYPES.MEMBER,
235
+ {publishedAt: Date.now()}
236
+ );
237
+
238
+ // Get all group connections
239
+ const groupConnections = await connectionActions.getConnections(
240
+ COLLECTIONS.GROUPS,
241
+ 'group456'
242
+ );
243
+ ```
244
+
245
+ ### HAS_REACTION (hasReaction)
246
+
247
+ Reactions like likes, views, pins, etc.
248
+
249
+ ```typescript
250
+ import {REACTION_TYPES} from '@nlabs/metropolisjs';
251
+
252
+ const reactionActions = createReactionActions(flux);
253
+
254
+ // Add like to post
255
+ await reactionActions.addReaction(
256
+ 'post123',
257
+ COLLECTIONS.POSTS,
258
+ {
259
+ name: REACTION_TYPES.LIKE
260
+ }
261
+ );
262
+
263
+ // Add view count
264
+ await reactionActions.addReaction(
265
+ 'post123',
266
+ COLLECTIONS.POSTS,
267
+ {
268
+ name: REACTION_TYPES.VIEW,
269
+ value: 1
270
+ }
271
+ );
272
+
273
+ // Get reaction count
274
+ const likeCount = await reactionActions.getReactionCount(
275
+ 'post123',
276
+ COLLECTIONS.POSTS,
277
+ REACTION_TYPES.LIKE
278
+ );
279
+
280
+ // Check if user has reacted
281
+ const hasLiked = await reactionActions.hasReaction(
282
+ 'post123',
283
+ COLLECTIONS.POSTS,
284
+ REACTION_TYPES.LIKE,
285
+ 'outbound'
286
+ );
287
+ ```
288
+
289
+ ### IS_TAGGED (isTagged)
290
+
291
+ Tag associations for categorization.
292
+
293
+ ```typescript
294
+ const tagActions = createTagActions(flux);
295
+
296
+ // Tag a post
297
+ await tagActions.tagItem(
298
+ 'post123',
299
+ COLLECTIONS.POSTS,
300
+ ['javascript', 'tutorial', 'beginner']
301
+ );
302
+
303
+ // Get items by tag
304
+ const taggedPosts = await tagActions.itemsByTag(
305
+ 'javascript',
306
+ COLLECTIONS.POSTS
307
+ );
308
+
309
+ // Get tags for item
310
+ const postTags = await tagActions.tagsByItem(
311
+ 'post123',
312
+ COLLECTIONS.POSTS
313
+ );
314
+
315
+ // Remove tag
316
+ await tagActions.untagItem(
317
+ 'post123',
318
+ COLLECTIONS.POSTS,
319
+ 'beginner'
320
+ );
321
+ ```
322
+
323
+ ### HAS_FILE (hasFile)
324
+
325
+ File attachments to any collection.
326
+
327
+ ```typescript
328
+ // Attach files to post
329
+ await connectionActions.addConnection(
330
+ COLLECTIONS.POSTS,
331
+ 'post123',
332
+ COLLECTIONS.FILES,
333
+ 'file456',
334
+ CONNECTION_TYPES.MEMBER,
335
+ {
336
+ order: 1,
337
+ caption: 'Screenshot'
338
+ }
339
+ );
340
+
341
+ // Attach image
342
+ await connectionActions.addConnection(
343
+ COLLECTIONS.POSTS,
344
+ 'post123',
345
+ COLLECTIONS.IMAGES,
346
+ 'image789',
347
+ CONNECTION_TYPES.MEMBER,
348
+ {
349
+ order: 2,
350
+ caption: 'Header image'
351
+ }
352
+ );
353
+
354
+ // Get post files
355
+ const files = await connectionActions.getConnections(
356
+ COLLECTIONS.POSTS,
357
+ 'post123',
358
+ COLLECTIONS.FILES
359
+ );
360
+
361
+ // Get post images
362
+ const images = await connectionActions.getConnections(
363
+ COLLECTIONS.POSTS,
364
+ 'post123',
365
+ COLLECTIONS.IMAGES
366
+ );
367
+ ```
368
+
369
+ ### HAS_CONVERSATION (hasConversation)
370
+
371
+ Links items to conversations for commenting/discussion.
372
+
373
+ ```typescript
374
+ // Create conversation for post
375
+ const conversation = await conversationActions.add({
376
+ type: 'post_comments',
377
+ isGroup: true
378
+ });
379
+
380
+ // Link post to conversation
381
+ await connectionActions.addConnection(
382
+ COLLECTIONS.POSTS,
383
+ 'post123',
384
+ COLLECTIONS.CONVERSATIONS,
385
+ conversation.conversationId,
386
+ CONNECTION_TYPES.MEMBER,
387
+ {
388
+ purpose: 'comments',
389
+ createdAt: Date.now()
390
+ }
391
+ );
392
+
393
+ // Get post conversation
394
+ const postConversations = await connectionActions.getConnections(
395
+ COLLECTIONS.POSTS,
396
+ 'post123',
397
+ COLLECTIONS.CONVERSATIONS
398
+ );
399
+ ```
400
+
401
+ ## Connection Actions
402
+
403
+ ### Creating Connections
404
+
405
+ ```typescript
406
+ import {createConnectionActions} from '@nlabs/metropolisjs';
407
+
408
+ const connectionActions = createConnectionActions(flux);
409
+
410
+ interface ConnectionActions {
411
+ addConnection: (
412
+ fromType: CollectionType,
413
+ fromId: string,
414
+ toType: CollectionType,
415
+ toId: string,
416
+ connectionType?: ConnectionType,
417
+ metadata?: Record<string, any>
418
+ ) => Promise<ConnectionEdge>;
419
+
420
+ getConnections: (
421
+ fromType: CollectionType,
422
+ fromId: string,
423
+ toType?: CollectionType,
424
+ filters?: Record<string, any>
425
+ ) => Promise<ConnectionEdge[]>;
426
+
427
+ removeConnection: (
428
+ fromType: CollectionType,
429
+ fromId: string,
430
+ toType: CollectionType,
431
+ toId: string
432
+ ) => Promise<boolean>;
433
+ }
434
+ ```
435
+
436
+ ### Connection Edge Structure
437
+
438
+ ```typescript
439
+ interface ConnectionEdge {
440
+ _from?: string; // Source document ID (e.g., 'users/123')
441
+ _to?: string; // Target document ID (e.g., 'groups/456')
442
+ fromId?: string; // Source ID ('123')
443
+ fromType?: CollectionType; // Source collection ('users')
444
+ toId?: string; // Target ID ('456')
445
+ toType?: CollectionType; // Target collection ('groups')
446
+ connectionType?: ConnectionType; // Connection type ('member', 'admin', etc.)
447
+ status?: string; // Connection status
448
+ metadata?: Record<string, any>; // Additional data
449
+ }
450
+ ```
451
+
452
+ ### Examples
453
+
454
+ ```typescript
455
+ // Add connection with metadata
456
+ const connection = await connectionActions.addConnection(
457
+ COLLECTIONS.USERS,
458
+ 'user123',
459
+ COLLECTIONS.GROUPS,
460
+ 'group456',
461
+ CONNECTION_TYPES.MEMBER,
462
+ {
463
+ joinedAt: Date.now(),
464
+ invitedBy: 'user789',
465
+ role: 'contributor',
466
+ permissions: ['post', 'comment']
467
+ }
468
+ );
469
+
470
+ console.log('Connection created:', connection);
471
+
472
+ // Query connections with filters
473
+ const adminConnections = await connectionActions.getConnections(
474
+ COLLECTIONS.USERS,
475
+ 'user123',
476
+ COLLECTIONS.GROUPS,
477
+ {
478
+ connectionType: CONNECTION_TYPES.ADMIN,
479
+ status: 'active'
480
+ }
481
+ );
482
+
483
+ // Remove connection
484
+ await connectionActions.removeConnection(
485
+ COLLECTIONS.USERS,
486
+ 'user123',
487
+ COLLECTIONS.GROUPS,
488
+ 'group456'
489
+ );
490
+ ```
491
+
492
+ ## Reaction Management
493
+
494
+ Reactions provide engagement metrics for content.
495
+
496
+ ### Reaction Types
497
+
498
+ ```typescript
499
+ const reactionTypes = {
500
+ LIKE: 'like', // Positive feedback
501
+ DISLIKE: 'dislike', // Negative feedback
502
+ VIEW: 'view', // View count
503
+ PIN: 'pin', // Pin/bookmark
504
+ RSVP: 'rsvp' // Event RSVP
505
+ };
506
+ ```
507
+
508
+ ### Reaction Actions
509
+
510
+ ```typescript
511
+ import {createReactionActions} from '@nlabs/metropolisjs';
512
+
513
+ const reactionActions = createReactionActions(flux);
514
+
515
+ interface ReactionActions {
516
+ addReaction: (itemId, itemType, reaction, props?) => Promise<Reaction>;
517
+ deleteReaction: (itemId, itemType, reactionName, props?) => Promise<Reaction>;
518
+ getReactionCount: (itemId, itemType, reactionName) => Promise<number>;
519
+ hasReaction: (itemId, itemType, reactionName, direction) => Promise<boolean>;
520
+ abbreviateCount: (count) => string;
521
+ updateReactionAdapter: (adapter) => void;
522
+ updateReactionAdapterOptions: (options) => void;
523
+ }
524
+ ```
525
+
526
+ ### Reaction Examples
527
+
528
+ ```typescript
529
+ // Like a post
530
+ await reactionActions.addReaction(
531
+ 'post123',
532
+ COLLECTIONS.POSTS,
533
+ {name: REACTION_TYPES.LIKE}
534
+ );
535
+
536
+ // Unlike a post
537
+ await reactionActions.deleteReaction(
538
+ 'post123',
539
+ COLLECTIONS.POSTS,
540
+ REACTION_TYPES.LIKE
541
+ );
542
+
543
+ // Track view
544
+ await reactionActions.addReaction(
545
+ 'post123',
546
+ COLLECTIONS.POSTS,
547
+ {
548
+ name: REACTION_TYPES.VIEW,
549
+ value: 1
550
+ }
551
+ );
552
+
553
+ // Pin content
554
+ await reactionActions.addReaction(
555
+ 'post123',
556
+ COLLECTIONS.POSTS,
557
+ {name: REACTION_TYPES.PIN}
558
+ );
559
+
560
+ // RSVP to event
561
+ await reactionActions.addReaction(
562
+ 'event456',
563
+ COLLECTIONS.POSTS,
564
+ {
565
+ name: REACTION_TYPES.RSVP,
566
+ value: 'attending'
567
+ }
568
+ );
569
+
570
+ // Get reaction counts
571
+ const likes = await reactionActions.getReactionCount(
572
+ 'post123',
573
+ COLLECTIONS.POSTS,
574
+ REACTION_TYPES.LIKE
575
+ );
576
+
577
+ const views = await reactionActions.getReactionCount(
578
+ 'post123',
579
+ COLLECTIONS.POSTS,
580
+ REACTION_TYPES.VIEW
581
+ );
582
+
583
+ // Check if current user has liked
584
+ const hasLiked = await reactionActions.hasReaction(
585
+ 'post123',
586
+ COLLECTIONS.POSTS,
587
+ REACTION_TYPES.LIKE,
588
+ 'outbound'
589
+ );
590
+
591
+ // Display abbreviated counts
592
+ console.log(reactionActions.abbreviateCount(1234)); // "1.2k"
593
+ console.log(reactionActions.abbreviateCount(1500000)); // "1.5m"
594
+ ```
595
+
596
+ ### Reaction Component Example
597
+
598
+ ```typescript
599
+ const PostReactions: React.FC<{postId: string}> = ({postId}) => {
600
+ const [likes, setLikes] = React.useState(0);
601
+ const [hasLiked, setHasLiked] = React.useState(false);
602
+
603
+ React.useEffect(() => {
604
+ const loadReactions = async () => {
605
+ const count = await reactionActions.getReactionCount(
606
+ postId,
607
+ COLLECTIONS.POSTS,
608
+ REACTION_TYPES.LIKE
609
+ );
610
+ setLikes(count);
611
+
612
+ const liked = await reactionActions.hasReaction(
613
+ postId,
614
+ COLLECTIONS.POSTS,
615
+ REACTION_TYPES.LIKE,
616
+ 'outbound'
617
+ );
618
+ setHasLiked(liked);
619
+ };
620
+
621
+ loadReactions();
622
+ }, [postId]);
623
+
624
+ const handleLike = async () => {
625
+ if (hasLiked) {
626
+ await reactionActions.deleteReaction(
627
+ postId,
628
+ COLLECTIONS.POSTS,
629
+ REACTION_TYPES.LIKE
630
+ );
631
+ setLikes(likes - 1);
632
+ setHasLiked(false);
633
+ } else {
634
+ await reactionActions.addReaction(
635
+ postId,
636
+ COLLECTIONS.POSTS,
637
+ {name: REACTION_TYPES.LIKE}
638
+ );
639
+ setLikes(likes + 1);
640
+ setHasLiked(true);
641
+ }
642
+ };
643
+
644
+ return (
645
+ <button onClick={handleLike}>
646
+ {hasLiked ? '❤️' : '🤍'} {reactionActions.abbreviateCount(likes)}
647
+ </button>
648
+ );
649
+ };
650
+ ```
651
+
652
+ ## Tag Management
653
+
654
+ Tags provide flexible categorization across collections.
655
+
656
+ ### Tag Actions
657
+
658
+ ```typescript
659
+ import {createTagActions} from '@nlabs/metropolisjs';
660
+
661
+ const tagActions = createTagActions(flux);
662
+
663
+ // Tag operations
664
+ await tagActions.tagItem(itemId, collectionType, tags);
665
+ await tagActions.untagItem(itemId, collectionType, tagName);
666
+ const items = await tagActions.itemsByTag(tagName, collectionType);
667
+ const tags = await tagActions.tagsByItem(itemId, collectionType);
668
+ ```
669
+
670
+ ### Tag Examples
671
+
672
+ ```typescript
673
+ // Tag a post
674
+ await tagActions.tagItem(
675
+ 'post123',
676
+ COLLECTIONS.POSTS,
677
+ ['javascript', 'tutorial', 'react']
678
+ );
679
+
680
+ // Tag a group
681
+ await tagActions.tagItem(
682
+ 'group456',
683
+ COLLECTIONS.GROUPS,
684
+ ['technology', 'programming', 'web-dev']
685
+ );
686
+
687
+ // Get all posts with tag
688
+ const jsPosts = await tagActions.itemsByTag(
689
+ 'javascript',
690
+ COLLECTIONS.POSTS
691
+ );
692
+
693
+ // Get all tags for post
694
+ const postTags = await tagActions.tagsByItem(
695
+ 'post123',
696
+ COLLECTIONS.POSTS
697
+ );
698
+
699
+ console.log('Tags:', postTags.map(t => t.name).join(', '));
700
+
701
+ // Remove specific tag
702
+ await tagActions.untagItem(
703
+ 'post123',
704
+ COLLECTIONS.POSTS,
705
+ 'tutorial'
706
+ );
707
+
708
+ // Update tags (remove all and add new)
709
+ const currentTags = await tagActions.tagsByItem('post123', COLLECTIONS.POSTS);
710
+ await Promise.all(
711
+ currentTags.map(tag =>
712
+ tagActions.untagItem('post123', COLLECTIONS.POSTS, tag.name)
713
+ )
714
+ );
715
+ await tagActions.tagItem('post123', COLLECTIONS.POSTS, ['updated', 'tags']);
716
+ ```
717
+
718
+ ### Tag Search Component
719
+
720
+ ```typescript
721
+ const TagSearch: React.FC = () => {
722
+ const [selectedTag, setSelectedTag] = React.useState('');
723
+ const [posts, setPosts] = React.useState([]);
724
+
725
+ const searchByTag = async (tagName: string) => {
726
+ setSelectedTag(tagName);
727
+ const results = await tagActions.itemsByTag(
728
+ tagName,
729
+ COLLECTIONS.POSTS
730
+ );
731
+ setPosts(results);
732
+ };
733
+
734
+ return (
735
+ <div>
736
+ <input
737
+ type="text"
738
+ placeholder="Search by tag"
739
+ onChange={(e) => searchByTag(e.target.value)}
740
+ />
741
+ <div>
742
+ {posts.map(post => (
743
+ <div key={post.postId}>{post.name}</div>
744
+ ))}
745
+ </div>
746
+ </div>
747
+ );
748
+ };
749
+ ```
750
+
751
+ ## File Associations
752
+
753
+ Link files, images, and videos to any collection.
754
+
755
+ ### Attaching Files
756
+
757
+ ```typescript
758
+ // Attach file to post
759
+ await connectionActions.addConnection(
760
+ COLLECTIONS.POSTS,
761
+ 'post123',
762
+ COLLECTIONS.FILES,
763
+ 'file456',
764
+ CONNECTION_TYPES.MEMBER,
765
+ {
766
+ order: 1,
767
+ caption: 'Project document',
768
+ fileType: 'application/pdf'
769
+ }
770
+ );
771
+
772
+ // Attach multiple images
773
+ const imageIds = ['img1', 'img2', 'img3'];
774
+ await Promise.all(
775
+ imageIds.map((imageId, index) =>
776
+ connectionActions.addConnection(
777
+ COLLECTIONS.POSTS,
778
+ 'post123',
779
+ COLLECTIONS.IMAGES,
780
+ imageId,
781
+ CONNECTION_TYPES.MEMBER,
782
+ {
783
+ order: index,
784
+ caption: `Image ${index + 1}`
785
+ }
786
+ )
787
+ )
788
+ );
789
+
790
+ // Attach video
791
+ await connectionActions.addConnection(
792
+ COLLECTIONS.POSTS,
793
+ 'post123',
794
+ COLLECTIONS.VIDEOS,
795
+ 'video789',
796
+ CONNECTION_TYPES.MEMBER,
797
+ {
798
+ order: 0,
799
+ caption: 'Tutorial video',
800
+ isFeatured: true
801
+ }
802
+ );
803
+ ```
804
+
805
+ ### Retrieving Files
806
+
807
+ ```typescript
808
+ // Get all post files
809
+ const files = await connectionActions.getConnections(
810
+ COLLECTIONS.POSTS,
811
+ 'post123',
812
+ COLLECTIONS.FILES
813
+ );
814
+
815
+ // Get images ordered by metadata
816
+ const images = await connectionActions.getConnections(
817
+ COLLECTIONS.POSTS,
818
+ 'post123',
819
+ COLLECTIONS.IMAGES
820
+ );
821
+ const sortedImages = images.sort((a, b) =>
822
+ (a.metadata?.order || 0) - (b.metadata?.order || 0)
823
+ );
824
+
825
+ // Get all media (images + videos)
826
+ const allFiles = await connectionActions.getConnections(
827
+ COLLECTIONS.POSTS,
828
+ 'post123'
829
+ );
830
+ const media = allFiles.filter(conn =>
831
+ conn.toType === COLLECTIONS.IMAGES ||
832
+ conn.toType === COLLECTIONS.VIDEOS
833
+ );
834
+ ```
835
+
836
+ ### File Upload Example
837
+
838
+ ```typescript
839
+ const uploadAndAttach = async (
840
+ file: File,
841
+ postId: string
842
+ ): Promise<void> => {
843
+ // 1. Upload file (implement your upload logic)
844
+ const uploadedFile = await uploadFileToServer(file);
845
+
846
+ // 2. Create file metadata
847
+ const fileMetadata = await fileActions.add({
848
+ fileName: file.name,
849
+ fileType: file.type,
850
+ size: file.size,
851
+ url: uploadedFile.url
852
+ });
853
+
854
+ // 3. Attach to post
855
+ await connectionActions.addConnection(
856
+ COLLECTIONS.POSTS,
857
+ postId,
858
+ COLLECTIONS.FILES,
859
+ fileMetadata.fileId,
860
+ CONNECTION_TYPES.MEMBER,
861
+ {
862
+ uploadedAt: Date.now(),
863
+ caption: ''
864
+ }
865
+ );
866
+ };
867
+ ```
868
+
869
+ ## Conversation Links
870
+
871
+ Link conversations for comments, discussions, and messaging.
872
+
873
+ ### Creating Conversation Links
874
+
875
+ ```typescript
876
+ // Create conversation for post comments
877
+ const conversation = await conversationActions.add({
878
+ type: 'post_comments',
879
+ isGroup: true,
880
+ name: 'Post Discussion'
881
+ });
882
+
883
+ // Link to post
884
+ await connectionActions.addConnection(
885
+ COLLECTIONS.POSTS,
886
+ 'post123',
887
+ COLLECTIONS.CONVERSATIONS,
888
+ conversation.conversationId,
889
+ CONNECTION_TYPES.MEMBER,
890
+ {
891
+ purpose: 'comments',
892
+ createdAt: Date.now()
893
+ }
894
+ );
895
+
896
+ // Get post conversation
897
+ const postConversations = await connectionActions.getConnections(
898
+ COLLECTIONS.POSTS,
899
+ 'post123',
900
+ COLLECTIONS.CONVERSATIONS
901
+ );
902
+
903
+ const commentConversation = postConversations[0];
904
+ ```
905
+
906
+ ### Comment System Example
907
+
908
+ ```typescript
909
+ const CommentSection: React.FC<{postId: string}> = ({postId}) => {
910
+ const [conversationId, setConversationId] = React.useState('');
911
+ const [messages, setMessages] = React.useState([]);
912
+
913
+ React.useEffect(() => {
914
+ const loadComments = async () => {
915
+ // Get post conversation
916
+ const conversations = await connectionActions.getConnections(
917
+ COLLECTIONS.POSTS,
918
+ postId,
919
+ COLLECTIONS.CONVERSATIONS
920
+ );
921
+
922
+ if (conversations.length === 0) {
923
+ // Create conversation if doesn't exist
924
+ const conv = await conversationActions.add({
925
+ type: 'post_comments',
926
+ isGroup: true
927
+ });
928
+
929
+ await connectionActions.addConnection(
930
+ COLLECTIONS.POSTS,
931
+ postId,
932
+ COLLECTIONS.CONVERSATIONS,
933
+ conv.conversationId,
934
+ CONNECTION_TYPES.MEMBER
935
+ );
936
+
937
+ setConversationId(conv.conversationId);
938
+ } else {
939
+ setConversationId(conversations[0].toId);
940
+ }
941
+ };
942
+
943
+ loadComments();
944
+ }, [postId]);
945
+
946
+ React.useEffect(() => {
947
+ if (conversationId) {
948
+ const loadMessages = async () => {
949
+ const msgs = await messageActions.listByConversation(
950
+ conversationId,
951
+ 0,
952
+ 50
953
+ );
954
+ setMessages(msgs);
955
+ };
956
+ loadMessages();
957
+ }
958
+ }, [conversationId]);
959
+
960
+ const addComment = async (content: string) => {
961
+ await messageActions.add({
962
+ conversationId,
963
+ content,
964
+ type: 'text'
965
+ });
966
+ // Reload messages
967
+ };
968
+
969
+ return (
970
+ <div>
971
+ {messages.map(msg => (
972
+ <div key={msg.messageId}>{msg.content}</div>
973
+ ))}
974
+ <textarea onBlur={(e) => addComment(e.target.value)} />
975
+ </div>
976
+ );
977
+ };
978
+ ```
979
+
980
+ ## Advanced Patterns
981
+
982
+ ### Multi-hop Relationships
983
+
984
+ ```typescript
985
+ // Get all posts from groups user is member of
986
+ const getUserGroupPosts = async (userId: string) => {
987
+ // 1. Get user's groups
988
+ const userGroups = await connectionActions.getConnections(
989
+ COLLECTIONS.USERS,
990
+ userId,
991
+ COLLECTIONS.GROUPS,
992
+ {connectionType: CONNECTION_TYPES.MEMBER}
993
+ );
994
+
995
+ // 2. Get posts from each group
996
+ const groupPosts = await Promise.all(
997
+ userGroups.map(group =>
998
+ connectionActions.getConnections(
999
+ COLLECTIONS.GROUPS,
1000
+ group.toId,
1001
+ COLLECTIONS.POSTS
1002
+ )
1003
+ )
1004
+ );
1005
+
1006
+ // 3. Flatten results
1007
+ return groupPosts.flat();
1008
+ };
1009
+ ```
1010
+
1011
+ ### Permission Checking
1012
+
1013
+ ```typescript
1014
+ const hasPermission = async (
1015
+ userId: string,
1016
+ groupId: string,
1017
+ permission: 'admin' | 'member'
1018
+ ): Promise<boolean> => {
1019
+ const connections = await connectionActions.getConnections(
1020
+ COLLECTIONS.USERS,
1021
+ userId,
1022
+ COLLECTIONS.GROUPS
1023
+ );
1024
+
1025
+ const groupConnection = connections.find(
1026
+ conn => conn.toId === groupId
1027
+ );
1028
+
1029
+ if (!groupConnection) return false;
1030
+
1031
+ if (permission === 'admin') {
1032
+ return groupConnection.connectionType === CONNECTION_TYPES.ADMIN;
1033
+ }
1034
+
1035
+ return groupConnection.connectionType === CONNECTION_TYPES.ADMIN ||
1036
+ groupConnection.connectionType === CONNECTION_TYPES.MEMBER;
1037
+ };
1038
+
1039
+ // Usage
1040
+ const canManageGroup = await hasPermission('user123', 'group456', 'admin');
1041
+ const canViewGroup = await hasPermission('user123', 'group456', 'member');
1042
+ ```
1043
+
1044
+ ### Social Feed
1045
+
1046
+ ```typescript
1047
+ const getSocialFeed = async (userId: string) => {
1048
+ // Get users I'm following
1049
+ const following = await connectionActions.getConnections(
1050
+ COLLECTIONS.USERS,
1051
+ userId,
1052
+ COLLECTIONS.USERS,
1053
+ {connectionType: CONNECTION_TYPES.FOLLOWING}
1054
+ );
1055
+
1056
+ // Get posts from followed users
1057
+ const followedUserIds = following.map(conn => conn.toId);
1058
+ const feedPosts = await Promise.all(
1059
+ followedUserIds.map(async (followedUserId) => {
1060
+ // Query posts by user
1061
+ const userPosts = await postActions.listByLatest(0, 10);
1062
+ return userPosts.filter(post => post.userId === followedUserId);
1063
+ })
1064
+ );
1065
+
1066
+ // Combine and sort by date
1067
+ return feedPosts
1068
+ .flat()
1069
+ .sort((a, b) => (b.added || 0) - (a.added || 0));
1070
+ };
1071
+ ```
1072
+
1073
+ ### Recommendation Engine
1074
+
1075
+ ```typescript
1076
+ const getRecommendedGroups = async (userId: string) => {
1077
+ // Get user's current groups
1078
+ const userGroups = await connectionActions.getConnections(
1079
+ COLLECTIONS.USERS,
1080
+ userId,
1081
+ COLLECTIONS.GROUPS,
1082
+ {connectionType: CONNECTION_TYPES.MEMBER}
1083
+ );
1084
+
1085
+ const userGroupIds = new Set(userGroups.map(g => g.toId));
1086
+
1087
+ // Get tags from user's groups
1088
+ const groupTags = await Promise.all(
1089
+ Array.from(userGroupIds).map(groupId =>
1090
+ tagActions.tagsByItem(groupId, COLLECTIONS.GROUPS)
1091
+ )
1092
+ );
1093
+
1094
+ const allTags = groupTags.flat().map(t => t.name);
1095
+ const uniqueTags = [...new Set(allTags)];
1096
+
1097
+ // Find groups with similar tags
1098
+ const similarGroups = await Promise.all(
1099
+ uniqueTags.map(tag =>
1100
+ tagActions.itemsByTag(tag, COLLECTIONS.GROUPS)
1101
+ )
1102
+ );
1103
+
1104
+ // Filter out groups user is already in
1105
+ return similarGroups
1106
+ .flat()
1107
+ .filter(group => !userGroupIds.has(group.groupId))
1108
+ .slice(0, 10);
1109
+ };
1110
+ ```
1111
+
1112
+ ## Best Practices
1113
+
1114
+ ### 1. Use Appropriate Connection Types
1115
+
1116
+ ```typescript
1117
+ // ✅ Correct - semantic connection types
1118
+ await connectionActions.addConnection(
1119
+ COLLECTIONS.USERS,
1120
+ userId,
1121
+ COLLECTIONS.GROUPS,
1122
+ groupId,
1123
+ CONNECTION_TYPES.ADMIN // Clear meaning
1124
+ );
1125
+
1126
+ // ❌ Incorrect - generic connection type
1127
+ await connectionActions.addConnection(
1128
+ COLLECTIONS.USERS,
1129
+ userId,
1130
+ COLLECTIONS.GROUPS,
1131
+ groupId,
1132
+ CONNECTION_TYPES.MEMBER // Too generic for admin
1133
+ );
1134
+ ```
1135
+
1136
+ ### 2. Store Relevant Metadata
1137
+
1138
+ ```typescript
1139
+ // ✅ Good - meaningful metadata
1140
+ await connectionActions.addConnection(
1141
+ COLLECTIONS.USERS,
1142
+ userId,
1143
+ COLLECTIONS.GROUPS,
1144
+ groupId,
1145
+ CONNECTION_TYPES.MEMBER,
1146
+ {
1147
+ joinedAt: Date.now(),
1148
+ invitedBy: inviterUserId,
1149
+ role: 'contributor',
1150
+ permissions: ['post', 'comment']
1151
+ }
1152
+ );
1153
+
1154
+ // ❌ Bad - missing context
1155
+ await connectionActions.addConnection(
1156
+ COLLECTIONS.USERS,
1157
+ userId,
1158
+ COLLECTIONS.GROUPS,
1159
+ groupId,
1160
+ CONNECTION_TYPES.MEMBER
1161
+ );
1162
+ ```
1163
+
1164
+ ### 3. Clean Up Orphaned Connections
1165
+
1166
+ ```typescript
1167
+ // When deleting a post, remove all connections
1168
+ const deletePostWithConnections = async (postId: string) => {
1169
+ // Remove reactions
1170
+ // Remove tags
1171
+ // Remove file connections
1172
+ // Remove conversation links
1173
+
1174
+ // Finally delete the post
1175
+ await postActions.delete(postId);
1176
+ };
1177
+ ```
1178
+
1179
+ ### 4. Implement Bidirectional Checks
1180
+
1181
+ ```typescript
1182
+ // Check both directions for symmetric relationships
1183
+ const areUsersFriends = async (userId1: string, userId2: string) => {
1184
+ const user1Following = await connectionActions.getConnections(
1185
+ COLLECTIONS.USERS,
1186
+ userId1,
1187
+ COLLECTIONS.USERS,
1188
+ {connectionType: CONNECTION_TYPES.FOLLOWING}
1189
+ );
1190
+
1191
+ const user2Following = await connectionActions.getConnections(
1192
+ COLLECTIONS.USERS,
1193
+ userId2,
1194
+ COLLECTIONS.USERS,
1195
+ {connectionType: CONNECTION_TYPES.FOLLOWING}
1196
+ );
1197
+
1198
+ const user1FollowsUser2 = user1Following.some(c => c.toId === userId2);
1199
+ const user2FollowsUser1 = user2Following.some(c => c.toId === userId1);
1200
+
1201
+ return user1FollowsUser2 && user2FollowsUser1;
1202
+ };
1203
+ ```
1204
+
1205
+ ### 5. Batch Connection Operations
1206
+
1207
+ ```typescript
1208
+ // ✅ Efficient - batch operations
1209
+ const addUserToGroups = async (userId: string, groupIds: string[]) => {
1210
+ await Promise.all(
1211
+ groupIds.map(groupId =>
1212
+ connectionActions.addConnection(
1213
+ COLLECTIONS.USERS,
1214
+ userId,
1215
+ COLLECTIONS.GROUPS,
1216
+ groupId,
1217
+ CONNECTION_TYPES.MEMBER
1218
+ )
1219
+ )
1220
+ );
1221
+ };
1222
+
1223
+ // ❌ Inefficient - sequential operations
1224
+ const addUserToGroupsSequential = async (userId: string, groupIds: string[]) => {
1225
+ for (const groupId of groupIds) {
1226
+ await connectionActions.addConnection(
1227
+ COLLECTIONS.USERS,
1228
+ userId,
1229
+ COLLECTIONS.GROUPS,
1230
+ groupId,
1231
+ CONNECTION_TYPES.MEMBER
1232
+ );
1233
+ }
1234
+ };
1235
+ ```
1236
+
1237
+ ### 6. Cache Connection Queries
1238
+
1239
+ ```typescript
1240
+ // Cache frequently accessed connections
1241
+ const connectionCache = new Map<string, ConnectionEdge[]>();
1242
+
1243
+ const getCachedConnections = async (
1244
+ fromType: CollectionType,
1245
+ fromId: string,
1246
+ toType?: CollectionType
1247
+ ): Promise<ConnectionEdge[]> => {
1248
+ const cacheKey = `${fromType}:${fromId}:${toType || 'all'}`;
1249
+
1250
+ if (connectionCache.has(cacheKey)) {
1251
+ return connectionCache.get(cacheKey)!;
1252
+ }
1253
+
1254
+ const connections = await connectionActions.getConnections(
1255
+ fromType,
1256
+ fromId,
1257
+ toType
1258
+ );
1259
+
1260
+ connectionCache.set(cacheKey, connections);
1261
+
1262
+ // Clear cache after 5 minutes
1263
+ setTimeout(() => connectionCache.delete(cacheKey), 300000);
1264
+
1265
+ return connections;
1266
+ };
1267
+ ```
1268
+
1269
+ ### 7. Validate Before Creating Connections
1270
+
1271
+ ```typescript
1272
+ const addMemberToGroup = async (userId: string, groupId: string) => {
1273
+ // Check if already member
1274
+ const existing = await connectionActions.getConnections(
1275
+ COLLECTIONS.USERS,
1276
+ userId,
1277
+ COLLECTIONS.GROUPS,
1278
+ {connectionType: CONNECTION_TYPES.MEMBER}
1279
+ );
1280
+
1281
+ if (existing.some(conn => conn.toId === groupId)) {
1282
+ throw new Error('User is already a member');
1283
+ }
1284
+
1285
+ // Check if blocked
1286
+ const blocked = await connectionActions.getConnections(
1287
+ COLLECTIONS.USERS,
1288
+ userId,
1289
+ COLLECTIONS.GROUPS,
1290
+ {connectionType: CONNECTION_TYPES.BLOCKED}
1291
+ );
1292
+
1293
+ if (blocked.some(conn => conn.toId === groupId)) {
1294
+ throw new Error('User is blocked from this group');
1295
+ }
1296
+
1297
+ // Add connection
1298
+ return await connectionActions.addConnection(
1299
+ COLLECTIONS.USERS,
1300
+ userId,
1301
+ COLLECTIONS.GROUPS,
1302
+ groupId,
1303
+ CONNECTION_TYPES.MEMBER
1304
+ );
1305
+ };
1306
+ ```
1307
+
1308
+ ### 8. Use Transactions for Complex Operations
1309
+
1310
+ ```typescript
1311
+ // Atomic operation to transfer group ownership
1312
+ const transferGroupOwnership = async (
1313
+ groupId: string,
1314
+ currentOwnerId: string,
1315
+ newOwnerId: string
1316
+ ) => {
1317
+ try {
1318
+ // Remove current owner's admin status
1319
+ await connectionActions.removeConnection(
1320
+ COLLECTIONS.USERS,
1321
+ currentOwnerId,
1322
+ COLLECTIONS.GROUPS,
1323
+ groupId
1324
+ );
1325
+
1326
+ // Add current owner as regular member
1327
+ await connectionActions.addConnection(
1328
+ COLLECTIONS.USERS,
1329
+ currentOwnerId,
1330
+ COLLECTIONS.GROUPS,
1331
+ groupId,
1332
+ CONNECTION_TYPES.MEMBER
1333
+ );
1334
+
1335
+ // Remove new owner's member status
1336
+ await connectionActions.removeConnection(
1337
+ COLLECTIONS.USERS,
1338
+ newOwnerId,
1339
+ COLLECTIONS.GROUPS,
1340
+ groupId
1341
+ );
1342
+
1343
+ // Add new owner as admin
1344
+ await connectionActions.addConnection(
1345
+ COLLECTIONS.USERS,
1346
+ newOwnerId,
1347
+ COLLECTIONS.GROUPS,
1348
+ groupId,
1349
+ CONNECTION_TYPES.ADMIN,
1350
+ {
1351
+ grantedAt: Date.now(),
1352
+ grantedBy: currentOwnerId
1353
+ }
1354
+ );
1355
+ } catch (error) {
1356
+ // Rollback logic if needed
1357
+ console.error('Transfer failed:', error);
1358
+ throw error;
1359
+ }
1360
+ };
1361
+ ```
1362
+
1363
+ ## Next Steps
1364
+
1365
+ - [CRUD Integration Guide](./CRUD_INTEGRATION.md) - Learn CRUD patterns
1366
+ - [Collections Reference](./COLLECTIONS.md) - Collection field details
1367
+ - [MetropolisJS Documentation](../README.md) - Main library docs