@nurama/sdk 0.0.0-stage → 1.4.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 (220) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1080 -2
  4. package/dist/BotClient.d.ts +66 -0
  5. package/dist/BotClient.d.ts.map +1 -0
  6. package/dist/BotClient.js +68 -0
  7. package/dist/BotClient.js.map +1 -0
  8. package/dist/NuramaClient.d.ts +448 -0
  9. package/dist/NuramaClient.d.ts.map +1 -0
  10. package/dist/NuramaClient.js +864 -0
  11. package/dist/NuramaClient.js.map +1 -0
  12. package/dist/browser/nurama-bot-sdk.js +11780 -0
  13. package/dist/browser/nurama-bot-sdk.min.js +1 -0
  14. package/dist/browser/nurama-sdk.js +11732 -0
  15. package/dist/browser/nurama-sdk.min.js +1 -0
  16. package/dist/routes/ai.d.ts +280 -0
  17. package/dist/routes/ai.d.ts.map +1 -0
  18. package/dist/routes/ai.js +173 -0
  19. package/dist/routes/ai.js.map +1 -0
  20. package/dist/routes/asset.d.ts +493 -0
  21. package/dist/routes/asset.d.ts.map +1 -0
  22. package/dist/routes/asset.js +848 -0
  23. package/dist/routes/asset.js.map +1 -0
  24. package/dist/routes/auth.d.ts +218 -0
  25. package/dist/routes/auth.d.ts.map +1 -0
  26. package/dist/routes/auth.js +454 -0
  27. package/dist/routes/auth.js.map +1 -0
  28. package/dist/routes/blogPosts.d.ts +17 -0
  29. package/dist/routes/blogPosts.d.ts.map +1 -0
  30. package/dist/routes/blogPosts.js +29 -0
  31. package/dist/routes/blogPosts.js.map +1 -0
  32. package/dist/routes/board.d.ts +187 -0
  33. package/dist/routes/board.d.ts.map +1 -0
  34. package/dist/routes/board.js +270 -0
  35. package/dist/routes/board.js.map +1 -0
  36. package/dist/routes/bot.d.ts +147 -0
  37. package/dist/routes/bot.d.ts.map +1 -0
  38. package/dist/routes/bot.js +157 -0
  39. package/dist/routes/bot.js.map +1 -0
  40. package/dist/routes/chat.d.ts +842 -0
  41. package/dist/routes/chat.d.ts.map +1 -0
  42. package/dist/routes/chat.js +863 -0
  43. package/dist/routes/chat.js.map +1 -0
  44. package/dist/routes/chatAi.d.ts +51 -0
  45. package/dist/routes/chatAi.d.ts.map +1 -0
  46. package/dist/routes/chatAi.js +109 -0
  47. package/dist/routes/chatAi.js.map +1 -0
  48. package/dist/routes/config.d.ts +11 -0
  49. package/dist/routes/config.d.ts.map +1 -0
  50. package/dist/routes/config.js +24 -0
  51. package/dist/routes/config.js.map +1 -0
  52. package/dist/routes/convo.d.ts +169 -0
  53. package/dist/routes/convo.d.ts.map +1 -0
  54. package/dist/routes/convo.js +284 -0
  55. package/dist/routes/convo.js.map +1 -0
  56. package/dist/routes/credits.d.ts +82 -0
  57. package/dist/routes/credits.d.ts.map +1 -0
  58. package/dist/routes/credits.js +49 -0
  59. package/dist/routes/credits.js.map +1 -0
  60. package/dist/routes/device.d.ts +74 -0
  61. package/dist/routes/device.d.ts.map +1 -0
  62. package/dist/routes/device.js +122 -0
  63. package/dist/routes/device.js.map +1 -0
  64. package/dist/routes/folder.d.ts +75 -0
  65. package/dist/routes/folder.d.ts.map +1 -0
  66. package/dist/routes/folder.js +99 -0
  67. package/dist/routes/folder.js.map +1 -0
  68. package/dist/routes/invite.d.ts +61 -0
  69. package/dist/routes/invite.d.ts.map +1 -0
  70. package/dist/routes/invite.js +86 -0
  71. package/dist/routes/invite.js.map +1 -0
  72. package/dist/routes/joinLink.d.ts +38 -0
  73. package/dist/routes/joinLink.d.ts.map +1 -0
  74. package/dist/routes/joinLink.js +81 -0
  75. package/dist/routes/joinLink.js.map +1 -0
  76. package/dist/routes/membership.d.ts +116 -0
  77. package/dist/routes/membership.d.ts.map +1 -0
  78. package/dist/routes/membership.js +183 -0
  79. package/dist/routes/membership.js.map +1 -0
  80. package/dist/routes/notification.d.ts +103 -0
  81. package/dist/routes/notification.d.ts.map +1 -0
  82. package/dist/routes/notification.js +89 -0
  83. package/dist/routes/notification.js.map +1 -0
  84. package/dist/routes/payment.d.ts +56 -0
  85. package/dist/routes/payment.d.ts.map +1 -0
  86. package/dist/routes/payment.js +78 -0
  87. package/dist/routes/payment.js.map +1 -0
  88. package/dist/routes/product.d.ts +43 -0
  89. package/dist/routes/product.d.ts.map +1 -0
  90. package/dist/routes/product.js +53 -0
  91. package/dist/routes/product.js.map +1 -0
  92. package/dist/routes/project.d.ts +821 -0
  93. package/dist/routes/project.d.ts.map +1 -0
  94. package/dist/routes/project.js +1153 -0
  95. package/dist/routes/project.js.map +1 -0
  96. package/dist/routes/public.d.ts +269 -0
  97. package/dist/routes/public.d.ts.map +1 -0
  98. package/dist/routes/public.js +412 -0
  99. package/dist/routes/public.js.map +1 -0
  100. package/dist/routes/scratch.d.ts +70 -0
  101. package/dist/routes/scratch.d.ts.map +1 -0
  102. package/dist/routes/scratch.js +67 -0
  103. package/dist/routes/scratch.js.map +1 -0
  104. package/dist/routes/settings.d.ts +102 -0
  105. package/dist/routes/settings.d.ts.map +1 -0
  106. package/dist/routes/settings.js +94 -0
  107. package/dist/routes/settings.js.map +1 -0
  108. package/dist/routes/shortlink.d.ts +79 -0
  109. package/dist/routes/shortlink.d.ts.map +1 -0
  110. package/dist/routes/shortlink.js +25 -0
  111. package/dist/routes/shortlink.js.map +1 -0
  112. package/dist/routes/socket.d.ts +108 -0
  113. package/dist/routes/socket.d.ts.map +1 -0
  114. package/dist/routes/socket.js +555 -0
  115. package/dist/routes/socket.js.map +1 -0
  116. package/dist/routes/storage.d.ts +44 -0
  117. package/dist/routes/storage.d.ts.map +1 -0
  118. package/dist/routes/storage.js +49 -0
  119. package/dist/routes/storage.js.map +1 -0
  120. package/dist/routes/subscription.d.ts +184 -0
  121. package/dist/routes/subscription.d.ts.map +1 -0
  122. package/dist/routes/subscription.js +219 -0
  123. package/dist/routes/subscription.js.map +1 -0
  124. package/dist/routes/supportChat.d.ts +40 -0
  125. package/dist/routes/supportChat.d.ts.map +1 -0
  126. package/dist/routes/supportChat.js +53 -0
  127. package/dist/routes/supportChat.js.map +1 -0
  128. package/dist/routes/supportTicket.d.ts +89 -0
  129. package/dist/routes/supportTicket.d.ts.map +1 -0
  130. package/dist/routes/supportTicket.js +54 -0
  131. package/dist/routes/supportTicket.js.map +1 -0
  132. package/dist/routes/tag.d.ts +72 -0
  133. package/dist/routes/tag.d.ts.map +1 -0
  134. package/dist/routes/tag.js +81 -0
  135. package/dist/routes/tag.js.map +1 -0
  136. package/dist/routes/task.d.ts +252 -0
  137. package/dist/routes/task.d.ts.map +1 -0
  138. package/dist/routes/task.js +284 -0
  139. package/dist/routes/task.js.map +1 -0
  140. package/dist/routes/taskRelation.d.ts +80 -0
  141. package/dist/routes/taskRelation.d.ts.map +1 -0
  142. package/dist/routes/taskRelation.js +71 -0
  143. package/dist/routes/taskRelation.js.map +1 -0
  144. package/dist/routes/token.d.ts +75 -0
  145. package/dist/routes/token.d.ts.map +1 -0
  146. package/dist/routes/token.js +51 -0
  147. package/dist/routes/token.js.map +1 -0
  148. package/dist/routes/user.d.ts +112 -0
  149. package/dist/routes/user.d.ts.map +1 -0
  150. package/dist/routes/user.js +151 -0
  151. package/dist/routes/user.js.map +1 -0
  152. package/dist/routes/version.d.ts +42 -0
  153. package/dist/routes/version.d.ts.map +1 -0
  154. package/dist/routes/version.js +38 -0
  155. package/dist/routes/version.js.map +1 -0
  156. package/dist/routes/webhook.d.ts +170 -0
  157. package/dist/routes/webhook.d.ts.map +1 -0
  158. package/dist/routes/webhook.js +173 -0
  159. package/dist/routes/webhook.js.map +1 -0
  160. package/dist/routes/workspace.d.ts +120 -0
  161. package/dist/routes/workspace.d.ts.map +1 -0
  162. package/dist/routes/workspace.js +199 -0
  163. package/dist/routes/workspace.js.map +1 -0
  164. package/dist/utils/uploadSessionManager.d.ts +133 -0
  165. package/dist/utils/uploadSessionManager.d.ts.map +1 -0
  166. package/dist/utils/uploadSessionManager.js +321 -0
  167. package/dist/utils/uploadSessionManager.js.map +1 -0
  168. package/dist/utils/urlParams.d.ts +35 -0
  169. package/dist/utils/urlParams.d.ts.map +1 -0
  170. package/dist/utils/urlParams.js +146 -0
  171. package/dist/utils/urlParams.js.map +1 -0
  172. package/dist/version.d.ts +15 -0
  173. package/dist/version.d.ts.map +1 -0
  174. package/dist/version.js +12 -0
  175. package/dist/version.js.map +1 -0
  176. package/package.json +87 -3
  177. package/src/BotClient.ts +113 -0
  178. package/src/NuramaClient.ts +1193 -0
  179. package/src/bot-browser-entry.js +15 -0
  180. package/src/browser-entry.js +20 -0
  181. package/src/routes/ai.ts +378 -0
  182. package/src/routes/asset.ts +1104 -0
  183. package/src/routes/auth.ts +587 -0
  184. package/src/routes/blogPosts.ts +29 -0
  185. package/src/routes/board.ts +403 -0
  186. package/src/routes/bot.ts +257 -0
  187. package/src/routes/chat.ts +1292 -0
  188. package/src/routes/chatAi.ts +125 -0
  189. package/src/routes/config.ts +31 -0
  190. package/src/routes/convo.ts +321 -0
  191. package/src/routes/credits.ts +112 -0
  192. package/src/routes/device.ts +133 -0
  193. package/src/routes/folder.ts +154 -0
  194. package/src/routes/invite.ts +133 -0
  195. package/src/routes/joinLink.ts +100 -0
  196. package/src/routes/membership.ts +237 -0
  197. package/src/routes/notification.ts +166 -0
  198. package/src/routes/payment.ts +104 -0
  199. package/src/routes/product.ts +67 -0
  200. package/src/routes/project.ts +1528 -0
  201. package/src/routes/public.ts +496 -0
  202. package/src/routes/scratch.ts +94 -0
  203. package/src/routes/settings.ts +152 -0
  204. package/src/routes/shortlink.ts +90 -0
  205. package/src/routes/socket.ts +739 -0
  206. package/src/routes/storage.ts +83 -0
  207. package/src/routes/subscription.ts +307 -0
  208. package/src/routes/supportChat.ts +62 -0
  209. package/src/routes/supportTicket.ts +114 -0
  210. package/src/routes/tag.ts +131 -0
  211. package/src/routes/task.ts +431 -0
  212. package/src/routes/taskRelation.ts +125 -0
  213. package/src/routes/token.ts +113 -0
  214. package/src/routes/user.ts +214 -0
  215. package/src/routes/version.ts +62 -0
  216. package/src/routes/webhook.ts +295 -0
  217. package/src/routes/workspace.ts +223 -0
  218. package/src/utils/uploadSessionManager.ts +407 -0
  219. package/src/utils/urlParams.ts +181 -0
  220. package/src/version.ts +22 -0
package/README.md CHANGED
@@ -1,3 +1,1081 @@
1
- # Temporary Holding Version
1
+ # Nurama SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Provides API, web-socket, and additional utilities for interfacing with the Nurama platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install nurama-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic API Usage
14
+
15
+ ```javascript
16
+ import NuramaClient from 'nurama-sdk';
17
+
18
+ // Create client instance
19
+ const client = new NuramaClient('https://api.nurama.com', {
20
+ debug: true, // Optional: Enable debug logging
21
+ });
22
+
23
+ // Login to get token
24
+ await client.auth.login({
25
+ email: 'user@example.com',
26
+ password: 'yourpassword',
27
+ });
28
+
29
+ // Make API calls using the client
30
+ const workspaces = await client.workspace.listWorkspaces();
31
+ console.log(workspaces);
32
+ ```
33
+
34
+ ### WebSocket Support
35
+
36
+ The SDK provides WebSocket functionality for real-time notifications and events. Socket.IO is used under the hood, which handles protocol conversion automatically.
37
+
38
+ ```javascript
39
+ import NuramaClient from 'nurama-sdk';
40
+
41
+ // Create client instance with a separate WebSocket server URL if needed
42
+ const client = new NuramaClient('https://api.nurama.com', {
43
+ websocketURL: 'https://ws.nurama.com', // Optional: Can use HTTP/HTTPS URLs (Socket.IO handles protocol)
44
+ browserMode: false, // Set to true for browser environments that need localStorage
45
+ });
46
+
47
+ // Login first to authenticate
48
+ await client.auth.login({
49
+ email: 'user@example.com',
50
+ password: 'yourpassword',
51
+ });
52
+
53
+ // Connect to a user channel
54
+ const userId = '123456789';
55
+ await client.socket.connect(`/user/${userId}`);
56
+
57
+ // Connect to a channel with a different WebSocket URL just for this connection
58
+ await client.socket.connect('/workspace/456', {
59
+ websocketURL: 'https://alt-ws.nurama.com' // Can use HTTP/HTTPS URLs
60
+ });
61
+
62
+ // Subscribe to notifications
63
+ await client.socket.subscribe(`/user/${userId}`, 'notification', (event) => {
64
+ console.log('Received notification:', event);
65
+
66
+ // Handle different notification types
67
+ switch (event.type) {
68
+ case 'workspaceCreate':
69
+ console.log('New workspace created:', event.changes?.create?.[0]?.resource);
70
+ break;
71
+ case 'projectUpdate':
72
+ console.log('Project updated:', event.changes?.update?.[0]?.resource);
73
+ break;
74
+ // Handle other event types
75
+ }
76
+ });
77
+
78
+ // Disconnect when done
79
+ await client.socket.disconnect(`/user/${userId}`);
80
+ ```
81
+
82
+ #### WebSocket Methods
83
+
84
+ - `connect(channel, options)`: Connect to a WebSocket channel
85
+ - `disconnect(channel)`: Disconnect from a channel
86
+ - `disconnectAll()`: Disconnect from all channels
87
+ - `subscribe(channel, event, callback)`: Subscribe to an event on a channel
88
+ - `unsubscribe(channel, event, callback?)`: Unsubscribe from an event
89
+ - `isConnected(channel)`: Check if connected to a channel
90
+
91
+ #### Connection Options
92
+
93
+ ```javascript
94
+ const options = {
95
+ autoRefresh: true, // Auto refresh token when expired
96
+ autoReconnect: true, // Auto reconnect on disconnection
97
+ autoReconnectOnTokenExpiry: true, // Auto reconnect when token expires
98
+ debug: false, // Enable debug logging
99
+ websocketURL: 'https://custom-ws.nurama.com' // Override WebSocket URL for this connection
100
+ };
101
+
102
+ await client.socket.connect('/user/123', options);
103
+ ```
104
+
105
+ ## Documentation
106
+
107
+ For detailed API documentation, see the [API Reference](https://docs.nurama.com).
108
+
109
+ ## Bot SDK
110
+
111
+ For programmatic / integration use cases, import `BotClient` from the `/bot` subpath. It authenticates with a long-lived API key (`nrm_bot_...`) instead of a JWT, defaults `baseURL` to the bot subdomain, and skips token refresh logic.
112
+
113
+ ```javascript
114
+ import BotClient from '@nurama/sdk/bot';
115
+
116
+ const bot = new BotClient(process.env.NURAMA_BOT_API_KEY);
117
+ const workspaces = await bot.workspace.listWorkspaces();
118
+ ```
119
+
120
+ ### Constructor
121
+
122
+ ```ts
123
+ new BotClient(apiKey: string, options?: {
124
+ baseURL?: string; // Default: 'https://bot.nurama.com'
125
+ websocketURL?: string; // Default: 'https://bot-ws.nurama.com'
126
+ fetch?: typeof fetch; // Custom fetch implementation
127
+ debug?: boolean;
128
+ enableCache?: boolean; // Default: true
129
+ cacheDurationSeconds?: number; // Default: 5
130
+ });
131
+ ```
132
+
133
+ ### Exposed Namespaces
134
+
135
+ `workspace`, `project`, `asset`, `chat`, `folder`, `invite`, `membership`, `notification`, `storage`, `task`, `tag`, `settings`, `shortlink`, `convo`, `board`, `version`, `config`, `product`, `public`, `socket`.
136
+
137
+ ### WebSocket Subscriptions
138
+
139
+ Bots can subscribe to real-time events using the same `socket` API as the regular SDK. Connections go to the bot WS subdomain (`bot-ws.nurama.com` by default) and authenticate with the API key as the query param `token`.
140
+
141
+ ```ts
142
+ import BotClient from '@nurama/sdk/bot';
143
+
144
+ const bot = new BotClient(process.env.NURAMA_BOT_API_KEY!);
145
+
146
+ await bot.socket.subscribe(`/workspace/${workspaceId}`, 'notification', (event) => {
147
+ console.log('Bot received:', event.type, event);
148
+ });
149
+ ```
150
+
151
+ API keys don't expire client-side, so the SDK skips the JWT-refresh path. If the server emits `tokenEvent: TokenExpired` or `tokenBlacklisted` for a bot connection (e.g., the key was revoked), the SDK disconnects without retrying.
152
+
153
+ The following namespaces are intentionally omitted (the server rejects bot calls to them with `botAccountRestricted`):
154
+
155
+ - `auth` — login, MFA, password, OAuth
156
+ - `user` — profile editing
157
+ - `payment`, `subscription` — billing
158
+ - `device` — push notification routing
159
+
160
+ Bot management (creating / rotating / deleting other bots) is also restricted server-side and is not exposed here. Workspace admins manage bots via the regular `NuramaClient` with their JWT.
161
+
162
+ ### Browser Usage
163
+
164
+ ```html
165
+ <script src="https://cdn.nurama.com/nurama-bot-sdk.min.js"></script>
166
+ <script>
167
+ const bot = new NuramaBotClient('nrm_bot_...');
168
+ bot.workspace.listWorkspaces().then(console.log);
169
+ </script>
170
+ ```
171
+
172
+ ## Distribution Formats
173
+
174
+ The SDK is distributed in multiple formats:
175
+
176
+ - **ES Module**: `dist/NuramaClient.js` - For modern JavaScript environments with module support
177
+ - **Bot ES Module**: `dist/BotClient.js` — API-key client for bots, imported via `@nurama/sdk/bot`
178
+ - **Browser bundle**: `dist/browser/nurama-sdk.js` - IIFE bundle for direct browser usage
179
+ - **Bot Browser bundle**: `dist/browser/nurama-bot-sdk.js` — IIFE bot bundle (exposes `window.NuramaBotClient`)
180
+ - **TypeScript definitions**: `dist/NuramaClient.d.ts` - Type definitions for TypeScript projects
181
+
182
+ ## Initiation Options
183
+
184
+ The `NuramaClient` constructor accepts various options for customization:
185
+
186
+ ```javascript
187
+ // Import the client
188
+ import NuramaClient from 'nurama-sdk';
189
+
190
+ // Initialize with options
191
+ const nurama = new NuramaClient('https://api.nurama.com', {
192
+ // Custom fetch implementation (optional)
193
+ fetch: customFetchFunction,
194
+
195
+ // Storage key names (optional)
196
+ tokenStorageKey: 'custom_access_token',
197
+ refreshTokenStorageKey: 'custom_refresh_token',
198
+
199
+ // Whether to run in browser mode (uses localStorage for token storage)
200
+ browserMode: false, // Default: false - set to true for browser environments
201
+
202
+ // WebSocket server URL (if different from API URL)
203
+ websocketURL: 'https://ws.nurama.com', // Socket.IO handles protocol conversion
204
+
205
+ // Token refresh settings
206
+ tokenExpiryBufferSeconds: 60,
207
+ tokenRefreshRetryDelayMs: 100,
208
+ tokenRefreshMaxWaitMs: 1000,
209
+ refreshLockTimeoutMs: 1000,
210
+
211
+ // Response caching settings
212
+ enableCache: true, // Enable/disable caching (default: true)
213
+ cacheDurationSeconds: 5, // Cache duration in seconds (default: 5)
214
+
215
+ // Enable debug mode
216
+ debug: false
217
+ });
218
+ ```
219
+
220
+ ## Response Caching
221
+
222
+ The Nurama SDK includes a built-in response caching mechanism that can improve performance by avoiding redundant API calls.
223
+
224
+ ### Configuration
225
+
226
+ The caching system is configured through constructor options:
227
+
228
+ ```typescript
229
+ const client = new NuramaClient('https://api.nurama.com', {
230
+ enableCache: true, // Enable/disable caching (default: true)
231
+ cacheDurationSeconds: 5, // Cache duration in seconds (default: 5)
232
+ debug: true // Enable debug logging to see cache hits
233
+ });
234
+ ```
235
+
236
+ ### Default Behavior
237
+
238
+ - **Enabled by default**: Caching is enabled unless explicitly disabled
239
+ - **5-second cache**: Responses are cached for 5 seconds by default
240
+ - **GET/HEAD and select POST requests**: GET and HEAD requests are cached, plus specific read-only POST endpoints
241
+ - **Notification endpoints**: POST requests to notification endpoints (`/v1/notifications/count`, `/v1/notifications/count/bulk`, etc.) are cached since they are read-only query operations
242
+ - **Concurrent request handling**: Multiple identical requests will share the same response
243
+
244
+ ### Usage Examples
245
+
246
+ #### Basic Usage
247
+
248
+ ```typescript
249
+ // First call - makes API request and caches response
250
+ const users1 = await client.user.getUserSelf();
251
+
252
+ // Second call within 5 seconds - returns cached response
253
+ const users2 = await client.user.getUserSelf(); // Cache hit!
254
+ ```
255
+
256
+ #### Bypassing Cache
257
+
258
+ Some route methods may support a `bypassCache` option:
259
+
260
+ ```typescript
261
+ // This would bypass cache if the method supports it
262
+ const freshData = await client.user.getUserSelf({ bypassCache: true });
263
+ ```
264
+
265
+ #### Concurrent Requests
266
+
267
+ ```typescript
268
+ // Both requests will share the same API call and response
269
+ const [users1, users2] = await Promise.all([
270
+ client.user.getUserSelf(),
271
+ client.user.getUserSelf()
272
+ ]);
273
+ // Only one actual API request is made
274
+ ```
275
+
276
+ #### Cache Management
277
+
278
+ ```typescript
279
+ // Clear all cached responses
280
+ client.clearCache();
281
+
282
+ // Get cache statistics for debugging
283
+ const stats = client.getCacheStats();
284
+ console.log(`Cache size: ${stats.size}`);
285
+ console.log('Cache entries:', stats.entries);
286
+ ```
287
+
288
+ ### Chat Reactions
289
+
290
+ The SDK supports creating and removing reactions on chat messages:
291
+
292
+ ```javascript
293
+ // Add a reaction to a message
294
+ await client.chat.createReaction('messageId123', {
295
+ emoji: '👍'
296
+ });
297
+
298
+ // Update an existing reaction (replaces user's previous reaction)
299
+ await client.chat.createReaction('messageId123', {
300
+ emoji: '❤️'
301
+ });
302
+
303
+ // Remove your reaction from a message
304
+ await client.chat.removeReaction('messageId123');
305
+ ```
306
+
307
+ ### Upload Progress Persistence
308
+
309
+ The SDK now includes built-in upload progress persistence and cross-tab synchronization for multipart uploads. This feature allows uploads to continue tracking their progress even after page refreshes or when switching between browser tabs.
310
+
311
+ #### Features
312
+
313
+ - **Automatic Progress Persistence**: Upload progress is saved to localStorage during upload
314
+ - **Project Isolation**: Each project maintains separate upload sessions
315
+ - **Cross-Tab Synchronization**: Upload progress syncs across all browser tabs in real-time
316
+ - **Session Recovery**: Retrieve and resume tracking uploads after page refresh
317
+ - **Automatic Cleanup**: Old and completed sessions are cleaned up automatically
318
+
319
+ #### Usage
320
+
321
+ Enable progress persistence when uploading files:
322
+
323
+ ```javascript
324
+ // Upload with progress persistence enabled
325
+ const uploadResult = await client.asset.multipartUpload(
326
+ file,
327
+ signedUrls,
328
+ key,
329
+ uploadId,
330
+ {
331
+ // Enable automatic progress persistence
332
+ enableProgressPersistence: true,
333
+
334
+ // Required: Project ID for session isolation
335
+ projectId: 'project123',
336
+
337
+ // Optional: Custom session ID (auto-generated if not provided)
338
+ sessionId: 'custom-session-id',
339
+
340
+ // Optional: Additional metadata
341
+ fileName: 'video.mp4',
342
+ fileSize: 50 * 1024 * 1024, // 50MB
343
+ assetId: 'asset123', // Once available from backend
344
+
345
+ // Your existing progress callback still works
346
+ onProgress: (progress) => {
347
+ console.log(`Upload progress: ${progress.totalPercentComplete}%`);
348
+ }
349
+ }
350
+ );
351
+ ```
352
+
353
+ #### Managing Upload Sessions
354
+
355
+ Retrieve active upload sessions for a project:
356
+
357
+ ```javascript
358
+ // Get all upload sessions for a project
359
+ const sessions = client.asset.getUploadSessions('project123');
360
+ sessions.forEach(session => {
361
+ console.log(`Upload ${session.fileName}: ${session.progress}% complete`);
362
+ console.log(`Status: ${session.status}`); // 'uploading', 'completing', 'completed', 'failed', 'cancelled'
363
+ });
364
+
365
+ // Get specific upload session
366
+ const session = client.asset.getUploadSession('project123', 'session-id');
367
+
368
+ // Remove a session manually
369
+ client.asset.removeUploadSession('project123', 'session-id');
370
+
371
+ // Clean up old sessions (default: 24 hours)
372
+ client.asset.cleanupUploadSessions('project123');
373
+ // Or specify custom age threshold
374
+ client.asset.cleanupUploadSessions('project123', 7 * 24 * 60 * 60 * 1000); // 7 days
375
+ ```
376
+
377
+ #### Cross-Tab Synchronization
378
+
379
+ Listen for upload progress updates from other tabs:
380
+
381
+ ```javascript
382
+ // Register a listener for cross-tab messages
383
+ client.asset.onUploadSessionMessage('my-listener', (message) => {
384
+ console.log(`Upload event from another tab:`, message);
385
+
386
+ switch (message.type) {
387
+ case 'upload_progress_update':
388
+ console.log(`Progress update for ${message.sessionId}: ${message.data.progress}%`);
389
+ break;
390
+ case 'upload_status_change':
391
+ console.log(`Status change for ${message.sessionId}: ${message.data.status}`);
392
+ break;
393
+ case 'upload_cleanup':
394
+ console.log(`Upload ${message.sessionId} was removed`);
395
+ break;
396
+ }
397
+ });
398
+
399
+ // Unregister listener when done
400
+ client.asset.offUploadSessionMessage('my-listener');
401
+ ```
402
+
403
+ #### Upload Session Data Structure
404
+
405
+ Each upload session contains the following information:
406
+
407
+ ```typescript
408
+ interface UploadSessionData {
409
+ sessionId: string; // Unique session identifier
410
+ projectId: string; // Project ID for isolation
411
+ uploadId: string; // S3 multipart upload ID
412
+ key: string; // S3 object key
413
+ assetId?: string; // Backend asset ID (once available)
414
+
415
+ // Progress tracking
416
+ progress: number; // Overall progress (0-100)
417
+ partNumber: number; // Current part being uploaded
418
+ totalParts: number; // Total number of parts
419
+ completedParts: number[]; // Array of completed part numbers
420
+
421
+ // Metadata
422
+ fileName: string; // Original file name
423
+ fileSize: number; // File size in bytes
424
+ timestamp: number; // Last update timestamp
425
+ status: 'uploading' | 'completing' | 'completed' | 'failed' | 'cancelled';
426
+
427
+ // Optional error information
428
+ error?: string; // Error message if failed
429
+ }
430
+ ```
431
+
432
+ #### Complete Example
433
+
434
+ ```javascript
435
+ // Start an upload with persistence
436
+ const file = document.getElementById('fileInput').files[0];
437
+ const { signedUrlData } = await client.project.createAssets('project123', {
438
+ files: [{ name: file.name, sizeInMB: file.size / 1024 / 1024, checksum: 'abc123' }]
439
+ });
440
+
441
+ // Upload with progress persistence
442
+ await client.asset.multipartUpload(
443
+ file,
444
+ signedUrlData.urls,
445
+ signedUrlData.key,
446
+ signedUrlData.uploadId,
447
+ {
448
+ enableProgressPersistence: true,
449
+ projectId: 'project123',
450
+ fileName: file.name,
451
+ fileSize: file.size,
452
+ onProgress: updateProgressBar
453
+ }
454
+ );
455
+
456
+ // Complete the upload
457
+ await client.asset.completeUpload({
458
+ key: signedUrlData.key,
459
+ uploadId: signedUrlData.uploadId,
460
+ parts: uploadResult.parts
461
+ });
462
+
463
+ // Mark session as completed
464
+ client.asset.completeUploadSession('project123', sessionId);
465
+ ```
466
+
467
+ ### File System Operations
468
+
469
+ The SDK now supports advanced file system operations for organizing and managing project assets and folders:
470
+
471
+ ```javascript
472
+ // Create a folder with file system integration
473
+ await client.project.createFolder('projectId123', 'creator', {
474
+ name: 'New Folder',
475
+ basePath: '/project/projectId123/creator'
476
+ });
477
+
478
+ // Get items at a specific file system path
479
+ const items = await client.project.getItemsAtPath(
480
+ 'projectId123',
481
+ 'creator',
482
+ '/project/projectId123/creator/folder1',
483
+ {
484
+ limit: 20,
485
+ mediaTypes: ['image', 'video'],
486
+ nameSearch: 'test'
487
+ }
488
+ );
489
+
490
+ // Move items to a new location
491
+ await client.project.moveItemsToPath('projectId123', 'creator', {
492
+ itemPaths: ['/project/projectId123/creator/folder1/asset1'],
493
+ destinationPath: '/project/projectId123/creator/folder2'
494
+ });
495
+
496
+ // Publish items with file system support
497
+ await client.project.publishItems('projectId123', {
498
+ resourceIds: ['assetId1', 'folderId1'],
499
+ basePath: '/project/projectId123/creator',
500
+ sendEmailNotification: true
501
+ });
502
+
503
+ // Copy items to a new location
504
+ await client.project.copyItemsToPath('projectId123', 'creator', {
505
+ itemPaths: ['/project/projectId123/creator/folder1/asset1'],
506
+ destinationPath: '/project/projectId123/creator/folder2'
507
+ });
508
+ ```
509
+
510
+ ### Tag Management
511
+
512
+ The SDK supports tagging and untagging assets and folders:
513
+
514
+ ```javascript
515
+ // Tag an asset
516
+ await client.asset.tagAsset('assetId123', {
517
+ tagId: 'tagId123'
518
+ });
519
+
520
+ // Remove a tag from an asset
521
+ await client.asset.untagAsset('assetId123', {
522
+ tagId: 'tagId123'
523
+ });
524
+
525
+ // Tag a folder
526
+ await client.folder.tagFolder('folderId123', {
527
+ tagId: 'tagId123'
528
+ });
529
+
530
+ // Remove a tag from a folder
531
+ await client.folder.untagFolder('folderId123', {
532
+ tagId: 'tagId123'
533
+ });
534
+ ```
535
+
536
+ ### Debug Output
537
+
538
+ When `debug: true` is enabled, you'll see cache-related log messages:
539
+
540
+ ```
541
+ [DEBUG] Cache hit for key: {"endpoint":"/v1/users","method":"GET","params":{},"body":null}
542
+ [DEBUG] Cached response for key: {"endpoint":"/v1/users","method":"GET","params":{},"body":null}
543
+ [DEBUG] Returning pending promise for concurrent request: {"endpoint":"/v1/users","method":"GET","params":{},"body":null}
544
+ [DEBUG] Cache cleared
545
+ ```
546
+
547
+ ### Cache Key Generation
548
+
549
+ Cache keys are generated based on:
550
+ - Endpoint URL
551
+ - HTTP method
552
+ - Query parameters (sorted)
553
+ - Request body
554
+
555
+ This ensures that different requests are cached separately while identical requests share the same cache entry.
556
+
557
+ ### Limitations
558
+
559
+ - Only GET and HEAD requests are cached
560
+ - Cache is in-memory only (cleared when client is destroyed)
561
+ - No persistent storage across page reloads in browser mode
562
+ - Authentication/refresh token requests are never cached
563
+
564
+ ## URL Parameter Handling
565
+
566
+ The Nurama SDK includes a sophisticated URL parameter utility that automatically transforms complex JavaScript objects into URL-safe query parameters for all API requests.
567
+
568
+ ### Automatic Integration
569
+
570
+ The URL parameter handling is **automatically applied to ALL routes** through the `NuramaClient._request()` method. Every route that accepts parameters benefits from this functionality without requiring individual updates. You can pass complex parameter objects containing arrays, nested objects, and various data types, and they will be properly serialized:
571
+
572
+ ```javascript
573
+ // Complex parameters are automatically handled
574
+ const assets = await client.project.getAssets('project123', 'creator', {
575
+ page: 1,
576
+ limit: 20,
577
+ search: 'test query',
578
+ tags: ['important', 'urgent'], // Arrays become comma-separated
579
+ filters: { // Objects become JSON strings
580
+ status: ['active', 'pending'],
581
+ dateRange: {
582
+ start: '2024-01-01',
583
+ end: '2024-12-31'
584
+ }
585
+ },
586
+ sort: { createdAt: -1, priority: 1 },
587
+ active: true, // Booleans converted to strings
588
+ startDate: new Date('2024-01-01') // Dates properly formatted
589
+ });
590
+ ```
591
+
592
+ ### Default Configuration
593
+
594
+ The SDK uses optimized defaults for the Nurama API:
595
+
596
+ - **arrayFormat**: `'comma'` - Arrays are serialized as comma-separated values
597
+ - **objectFormat**: `'bracket'` - Objects are serialized using bracket notation
598
+ - **encode**: `true` - All values are URL encoded
599
+
600
+ ### Route Coverage
601
+
602
+ **All routes automatically inherit this functionality**, including:
603
+
604
+ - ✅ **Authentication routes** (`client.auth.*`)
605
+ - ✅ **Asset routes** (`client.asset.*`)
606
+ - ✅ **Chat routes** (`client.chat.*`)
607
+ - ✅ **Folder routes** (`client.folder.*`)
608
+ - ✅ **Invite routes** (`client.invite.*`)
609
+ - ✅ **Membership routes** (`client.membership.*`)
610
+ - ✅ **Notification routes** (`client.notification.*`)
611
+ - ✅ **Package routes** (`client.package.*`)
612
+ - ✅ **Project routes** (`client.project.*`)
613
+ - ✅ **Storage routes** (`client.storage.*`)
614
+ - ✅ **Subscription routes** (`client.subscription.*`)
615
+ - ✅ **Task routes** (`client.task.*`)
616
+ - ✅ **User routes** (`client.user.*`)
617
+ - ✅ **Version routes** (`client.version.*`)
618
+ - ✅ **Workspace routes** (`client.workspace.*`)
619
+
620
+ ### Supported Data Types
621
+
622
+ | Type | Behavior | Example Input | Example Output |
623
+ |------|----------|---------------|----------------|
624
+ | **String** | URL encoded | `"John Doe"` | `"John%20Doe"` |
625
+ | **Number** | Converted to string | `25` | `"25"` |
626
+ | **Boolean** | Converted to string | `true` | `"true"` |
627
+ | **Array** | Comma-separated, encoded | `["a", "b", "c"]` | `"a%2Cb%2Cc"` |
628
+ | **Object** | Dot notation, encoded | `{x: 1}` | `"obj.x=1"` |
629
+ | **Date** | toString(), encoded | `new Date()` | `"Mon%20Jan%2001..."` |
630
+ | **null/undefined** | Filtered out | `null` | (not included) |
631
+
632
+ ### Real-World Examples
633
+
634
+ #### Asset Search with Complex Parameters
635
+ ```javascript
636
+ await client.project.getAssets('project123', 'creator', {
637
+ mediaTypes: ['image', 'video'], // → "image%2Cvideo"
638
+ includeChats: true, // → "true"
639
+ folderId: 'folder123', // → "folder123"
640
+ sort: { name: 1, createdAt: -1 }, // → "sort[name]=1&sort[createdAt]=-1"
641
+ search: 'test query' // → "test%20query"
642
+ });
643
+ ```
644
+
645
+ #### Chat Parameters
646
+ ```javascript
647
+ await client.chat.getMessages('chat123', {
648
+ limit: 50, // → "50"
649
+ sort: { createdAt: -1 }, // → "sort[createdAt]=-1"
650
+ replies: 10, // → "10"
651
+ mentions: ['user1', 'user2'] // → "user1%2Cuser2"
652
+ });
653
+ ```
654
+
655
+ #### Notification Queries
656
+ ```javascript
657
+ await client.notification.getNotifications({
658
+ channels: ['project:123', 'workspace:456'], // → "project%3A123%2Cworkspace%3A456"
659
+ types: ['comment', 'mention'], // → "comment%2Cmention"
660
+ paginate: 'cursor', // → "cursor"
661
+ limit: 20, // → "20"
662
+ createdAfter: Date.now() - 86400000 // → "1704067200000"
663
+ });
664
+ ```
665
+
666
+ ### Manual Usage
667
+
668
+ The URL parameter utility can also be used independently:
669
+
670
+ ```javascript
671
+ import { parseUrlParams, paramsToUrlSearchParams } from 'nurama-sdk/utils/urlParams';
672
+
673
+ // Convert parameters to string record
674
+ const stringParams = parseUrlParams({
675
+ tags: ['a', 'b', 'c'],
676
+ filter: { status: 'active' }
677
+ });
678
+ // Result: { tags: 'a%2Cb%2Cc', filter: '%7B%22status%22%3A%22active%22%7D' }
679
+
680
+ // Create URLSearchParams object
681
+ const urlParams = paramsToUrlSearchParams({
682
+ page: 1,
683
+ limit: 10,
684
+ active: true
685
+ });
686
+ console.log(urlParams.toString()); // "page=1&limit=10&active=true"
687
+ ```
688
+
689
+ ### Configuration Options
690
+
691
+ The utility supports configuration options (though the defaults work well for the Nurama API):
692
+
693
+ ```javascript
694
+ import { parseUrlParams } from 'nurama-sdk/utils/urlParams';
695
+
696
+ const result = parseUrlParams(params, {
697
+ arrayFormat: 'comma', // 'comma' or 'brackets' (key[]=val1&key[]=val2)
698
+ objectFormat: 'bracket', // 'bracket', 'dot', or 'json' (JSON stringified)
699
+ encode: true // Whether to URL encode values (default: true)
700
+ });
701
+ ```
702
+
703
+ ### Technical Implementation
704
+
705
+ - **Minimal overhead**: Parameter processing only occurs when parameters are provided
706
+ - **Efficient encoding**: Uses native `encodeURIComponent()`
707
+ - **Caching friendly**: Encoded parameters work properly with response caching
708
+ - **Proper encoding**: Manual query string building to avoid double-encoding issues
709
+ - **Comprehensive test coverage**: 36 total tests (26 unit + 10 integration)
710
+
711
+ ### Migration Notes
712
+
713
+ The implementation provides backward compatibility with all existing route method signatures remaining unchanged. Only the URL encoding behavior has been updated to be more robust and consistent with `encode: true` by default.
714
+
715
+ ## API Client Methods
716
+
717
+ This section lists the methods available under `client.<module>`. Full API documentation can be found in the Nurama backend repo or its Swagger/OpenAPI definition.
718
+
719
+ ### Authentication (`client.auth`)
720
+
721
+ * `async register(userData)`: Registers a new user (`firstName`, `lastName`, `email`, `password`).
722
+ * `async login(credentials)`: Logs in a user (`login`, `password`). Handles MFA challenges.
723
+ * `async logout(refreshToken?)`: Clears stored tokens and invalidates the refresh token.
724
+ * `async refreshTokens(refreshToken?)`: Exchanges a refresh token for new access and refresh tokens.
725
+ * `async forgotPassword(email)`: Sends a password reset email.
726
+ * `async resetPassword({ token, newPassword })`: Resets the password using a token.
727
+ * `async sendVerificationEmail()`: Sends an email verification link to the logged-in user.
728
+ * `async verifyEmail(token)`: Verifies the user's email using a token.
729
+ * `async enableMfa()`: Enables multi-factor authentication, returns setup details.
730
+ * `async verifyMfa(mfaToken)`: Verifies an MFA token (during setup or login).
731
+ * `async disableMfa(mfaToken)`: Disables MFA using a current MFA token.
732
+ * `tokenValid()`: Checks if the current JWT token exists and is valid. Returns `true` if the token exists and is not expired, `false` otherwise.
733
+
734
+ ### Cache Management (`client`)
735
+
736
+ * `clearCache()`: Clears all cached responses.
737
+ * `getCacheStats()`: Returns cache statistics including size and entry details for debugging.
738
+
739
+ ### User (`client.user`)
740
+
741
+ * `async getUserSelf()`: Retrieves the full profile of logged in user.
742
+ * `async getUser(userId)`: Retrieves the public profile of a specific user.
743
+ * `async updateSelf(updateData)`: Updates the profile of the currently logged-in user (`email`, `userName`, `password`, `firstName`, `middleName`, `lastName`, `company`, `displayName`).
744
+ * `async deleteCurrentUser()`: Marks the logged-in user's account for deletion.
745
+ * `async createAvatar(fileData)`: Creates a new avatar asset for the user, returns signed URL data.
746
+ * `async updateAvatar(fileData)`: Updates the user's avatar asset, returns signed URL data.
747
+ * `async updatePreferences(preferenceData)`: Updates user preferences (e.g., `hide` array).
748
+
749
+ ### Workspace (`client.workspace`)
750
+
751
+ * `async createWorkspace(data)`: Creates a new workspace (`name`, `description?`).
752
+ * `async getWorkspace(workspaceId)`: Gets workspace details by ID.
753
+ * `async listWorkspaces(sortParams?)`: Gets all workspaces for the current user, with optional sorting.
754
+ * `async updateWorkspace(workspaceId, updateData)`: Updates workspace details (`name?`, `description?`, `updateSlug?`).
755
+ * `async deleteWorkspace(workspaceId)`: Marks a workspace for deletion.
756
+ * `async createLogo(workspaceId, fileData)`: Creates a logo asset, returns signed URL data.
757
+ * `async updateLogo(workspaceId, fileData)`: Updates the logo asset, returns signed URL data.
758
+ * `async createIcon(workspaceId, fileData)`: Creates an icon asset, returns signed URL data.
759
+ * `async updateIcon(workspaceId, fileData)`: Updates the icon asset, returns signed URL data.
760
+ * `async listProjects(workspaceId)`: Retrieves projects within a workspace.
761
+ * `async updateSetting(workspaceId, settingName, value)`: Updates a specific boolean workspace setting.
762
+
763
+ ### Project (`client.project`)
764
+
765
+ * `async createProject(projectData)`: Creates a new project (`name`, `workspaceId`).
766
+ * `async getProjects()`: Retrieves projects accessible by the user (no pagination).
767
+ * `async getProject(projectId)`: Retrieves a specific project by ID.
768
+ * `async updateProject(projectId, updateData)`: Updates project details (`name?`, `description?`, `updateSlug?`).
769
+ * `async deleteProject(projectId)`: Marks a project for deletion.
770
+ * `async createLogo(projectId, logoData)`: Creates a logo asset for the project, returns signed URL data.
771
+ * `async updateLogo(projectId, logoData)`: Updates the project logo asset, returns signed URL data.
772
+ * `async createAssets(projectId, fileUploadBody)`: Creates multiple assets from file data, returns signed URL data for each.
773
+ * `async getAssets(projectId, visibility, params?)`: Gets assets for one visibility tier (`'creator'` or `'reviewer'`) with pagination, sorting and filtering.
774
+ * `async getHomeFeed(projectId, visibility, params?)`: Gets the feed (assets with inline chat data) for one visibility tier.
775
+ * `async getHomeFeed(projectId, visibility, params?)`: Gets feed based on visibility.
776
+ * `async getFolders(projectId, visibility, params?)`: Gets folders for one visibility tier.
777
+ * `async getFolders(projectId, visibility, params?)`: Gets folders based on visibility.
778
+ * `async getProjectChat(projectId, visibility, params?)`: Gets the project chat for one visibility tier.
779
+ * `async getProjectChat(projectId, visibility, params?)`: Gets chat based on visibility.
780
+ * `async createSubmission(projectId, submissionData)`: Creates a new submission.
781
+ * `async getSubmissions(projectId, params?)`: Gets submissions for the project.
782
+ * `async getSubmission(projectId, submissionId)`: Gets a specific submission.
783
+ * `async getSubmissionAssets(projectId, submissionId, params?)`: Gets assets within a submission.
784
+ * `async publishAssets(projectId, assetData)`: Publishes a list of assets.
785
+ * `async unpublishAssets(projectId, assetData)`: Unpublishes a list of assets.
786
+ * `async createFolder(projectId, visibility, folderData)`: Creates a folder with file system integration (`name`, `basePath?`).
787
+ * `async publishItems(projectId, publishData)`: Publishes items (assets/folders) with file system support (`resourceIds`, `basePath?`, `sendEmailNotification?`).
788
+ * `async unpublishItems(projectId, unpublishData)`: Unpublishes items from file system (`resourceIds`).
789
+ * `async getItemsAtPath(projectId, visibility, path, params?)`: Gets items at a specific file system path with advanced filtering and pagination.
790
+ * `async moveItemsToPath(projectId, visibility, moveData)`: Moves items to a new file system path (`itemPaths`, `destinationPath`).
791
+ * `async copyItemsToPath(projectId, visibility, copyData)`: Copies items to a new file system path (`itemPaths`, `destinationPath`).
792
+ * `async deleteItemsAtPath(projectId, visibility, deleteData)`: Deletes items from file system path (`itemPaths`).
793
+
794
+ ### Folder (`client.folder`)
795
+
796
+ * `async getFolder(folderId)`: Retrieves folder details.
797
+ * `async updateFolder(folderId, data)`: Updates the folder's name and/or color (`name?`, `color?`).
798
+ * `async updateFolderIcon(folderId, data)`: Updates the folder's icon (`name`, `checksum`, `sizeInMB`).
799
+ * `async deleteFolder(folderId)`: Deletes a folder.
800
+ * `async getFoldersAssets(folderId, params?)`: Gets assets within a folder (pagination, sorting).
801
+ * `async tagFolder(folderId, tagData)`: Tags a folder with a specific tag (`tagId`).
802
+ * `async untagFolder(folderId, untagData)`: Removes a tag from a folder (`tagId`).
803
+ * `async updateFolderName(folderId, data)`: **@deprecated** Use `updateFolder` instead. Updates the folder's name.
804
+
805
+ ### Asset (`client.asset`)
806
+
807
+ * `async getAsset(assetId)`: Retrieves asset details.
808
+ * `async updateAsset(assetId, updateData)`: Updates asset details (`name?`, `meta?`, `tags?`, `folderId?`).
809
+ * `async deleteAsset(assetId)`: Marks an asset for deletion.
810
+ * `async getFile(assetId, fileId)`: Retrieves a specific file object within an asset.
811
+ * `async getFilesByFunctionType(assetId, functionType)`: Gets files by function type (e.g., 'thumbnail').
812
+ * `async completeUpload(uploadData)`: Completes a multipart upload initiated elsewhere.
813
+ * `async multipartUpload(file, signedUrls, key, uploadId, options?)`: Handles multipart file upload using pre-signed URLs with optional progress persistence.
814
+ * `async getAssetPage(assetId, params?)`: Gets the page number an asset appears on based on filter/sort.
815
+ * `async repairAssets(assetIds)`: Initiates repair process for assets.
816
+ * `async downloadAssets(assetIds)`: Initiates download process for assets.
817
+ * `async tagAsset(assetId, tagData)`: Tags an asset with a specific tag (`tagId`).
818
+ * `async untagAsset(assetId, untagData)`: Removes a tag from an asset (`tagId`).
819
+
820
+ **Upload Session Management Methods:**
821
+ * `getUploadSessions(projectId)`: Get all active upload sessions for a project.
822
+ * `getUploadSession(projectId, sessionId)`: Get a specific upload session.
823
+ * `removeUploadSession(projectId, sessionId)`: Remove an upload session.
824
+ * `cleanupUploadSessions(projectId, olderThanMs?)`: Clean up old upload sessions.
825
+ * `onUploadSessionMessage(listenerId, callback)`: Register a cross-tab message listener.
826
+ * `offUploadSessionMessage(listenerId)`: Unregister a cross-tab message listener.
827
+ * `completeUploadSession(projectId, sessionId)`: Mark an upload session as completed.
828
+
829
+ ### Chat (`client.chat`)
830
+
831
+ * `async createTopicChat(data)`: Creates a chat linked to a topic (project or asset).
832
+ * `async getChatByTopicId(topicId, params)`: Gets chat details by its topic ID.
833
+ * `async createMemberChat(data)`: Creates a direct/group chat between members.
834
+ * `async getUsersMemberChats(params?)`: Gets member chats the user is part of.
835
+ * `async getMemberChat(chatId)`: Gets details of a specific member chat.
836
+ * `async updateMemberChat(chatId, data)`: Updates a member chat (subject, color, etc.).
837
+ * `async deleteMemberChat(chatId)`: Deletes a member chat.
838
+ * `async getAddableMembers(chatId)`: Gets users that can be added to a member chat.
839
+ * `async addMembers(chatId, data)`: Adds members (`memberIds`) to a chat.
840
+ * `async removeMembers(chatId, data)`: Removes members (`memberIds`) from a chat.
841
+ * `async getChat(chatId)`: Retrieves details for any chat type by ID.
842
+ * `async updateChatSubject(chatId, data)`: Updates the subject of any chat type.
843
+ * `async deleteChat(chatId)`: Deletes any chat type.
844
+ * `async createMessage(chatId, data)`: Creates a new message in a chat.
845
+ * `async createAssetChatAndMessage(assetId, visibility, data, params?)`: Creates a message in an asset's chat (potentially creating the chat).
846
+ * `async getMessages(chatId, params?)`: Gets messages in a chat (pagination, sorting).
847
+ * `async getMessage(messageId, params?)`: Gets a specific message by ID.
848
+ * `async reviseMessage(messageId, data)`: Updates the content/mentions of a message.
849
+ * `async deleteMessage(messageId)`: Deletes a message.
850
+ * `async getMessagePage(messageId, params?)`: Gets the page number a message appears on.
851
+ * `async getReplies(messageId, params?)`: Gets replies to a specific message.
852
+ * `async addAttachments(messageId, attachments)`: Adds file attachments to a message.
853
+ * `async removeAttachment(messageId, assetId)`: Removes an attachment from a message.
854
+ * `async getMentions(params?)`: Gets messages where the current user is mentioned.
855
+ * `async generateSummary(chatId, data)`: Generates an AI summary for a chat.
856
+ * `async getSummaries(chatId, params?)`: Retrieves previously generated summaries for a chat.
857
+ * `async createReaction(messageId, data)`: Creates or updates a reaction on a chat message (`emoji`).
858
+ * `async removeReaction(messageId)`: Removes the authenticated user's reaction from a chat message.
859
+
860
+ ### Membership (`client.membership`)
861
+
862
+ * `async getMyMemberships()`: Retrieves the logged-in user's membership records.
863
+ * `async getWorkspaceMemberships(workspaceId, params?)`: Gets memberships for a workspace.
864
+ * `async getProjectMemberships(projectId, params?)`: Gets memberships for a project.
865
+ * `async deleteMembership(membershipId)`: Deletes a specific membership record.
866
+ * `async leaveResource(resourceId)`: Allows the user to leave a workspace or project.
867
+ * `async addRole(data)`: Adds a role to a membership (`membershipId`, `role`).
868
+ * `async removeRole(data)`: Removes a role from a membership (`membershipId`, `role`).
869
+ * `async getProjectMentionableUsers(projectId, visibility)`: Gets users mentionable in a project.
870
+
871
+ ### Invite (`client.invite`)
872
+
873
+ * `async inviteUser(data)`: Invites a user (`inviteeEmail`) to a resource (`resourceType`, `resourceId`) with a specific `role`.
874
+ * `async getInvites(params?)`: Retrieves invites involving the current user (as inviter or invitee).
875
+ * `async getInviteById(inviteId)`: Gets details of a specific invite.
876
+ * `async cancelInvite(inviteId)`: Cancels an active invite.
877
+ * `async getInvitesForResource(resourceId, params?)`: Gets invites associated with a specific resource.
878
+ * `async acceptInvite(inviteId)`: Accepts an invite.
879
+ * `async resendInvite(inviteId)`: Resends an invite email.
880
+
881
+ ### Notification (`client.notification`)
882
+
883
+ * `async getNotifications(data)`: Retrieves notifications for channels (pagination, filtering).
884
+ * `async getNewNotifications(data)`: Gets new notifications since last seen.
885
+ * `async getNewNotificationCount(data)`: Gets the count of new notifications for channels.
886
+ * `async getNewNotificationCountBulk(data)`: Gets counts for multiple channel queries.
887
+ * `async getUsersLastNotificationsSeen(data)`: Retrieves the last seen timestamp for channels.
888
+ * `async updateUsersLastSeen(data)`: Updates the last seen timestamp for channels.
889
+
890
+ ### Subscription (`client.subscription`)
891
+
892
+ * `async getWorkspaceSubscriptions(workspaceId, params?)`: Retrieves subscriptions for a workspace.
893
+ * `async getMySubscriptions(params?)`: Retrieves subscriptions owned by the user.
894
+ * `async getResourceLimits(resourceId)`: Gets storage/seat limits for a resource (workspace).
895
+ * `async getSeatUsage(resourceId)`: Gets seat usage for a resource (workspace).
896
+ * `async getStorageUsage(resourceId)`: Gets storage usage for a resource (workspace).
897
+
898
+ ### Package (`client.package`)
899
+
900
+ * `async getWorkspacePackages(workspaceId)`: Retrieves available subscription packages for a workspace.
901
+
902
+ ### Task (`client.task`)
903
+
904
+ * `async getMyTasks(params?)`: Retrieves tasks assigned to the user (pagination, sorting, filtering).
905
+ * `async updateTaskStatus(taskId, data)`: Updates the status of a task.
906
+ * `async acknowledgeTask(taskId)`: Toggles the acknowledgement status of a task.
907
+ * `async getUnacknowledgedTaskCount(projectId)`: Gets the count of unacknowledged tasks in a project.
908
+
909
+ ### Storage (`client.storage`)
910
+
911
+ * `async getStorageChart(resourceType, resourceId, params?)`: Retrieves storage usage chart data.
912
+ * `async getStorageRecord(resourceType, resourceId)`: Retrieves the latest storage record for a resource.
913
+
914
+ ### Tag (`client.tag`)
915
+
916
+ * `async createTag(data)`: Creates a new tag (`name`, `ownerResourceType`, `ownerResourceId`, `color`).
917
+ * `async getTags(resourceType, resourceId, params?)`: Retrieves tags for a resource (pagination, sorting).
918
+ * `async updateTag(tagId, updateData)`: Updates tag details (`name?`, `color?`).
919
+ * `async deleteTag(tagId)`: Deletes a tag.
920
+
921
+ ### Config (`client.config`)
922
+
923
+ * `async getConfig()`: Get platform configuration data (limits, colors, etc.).
924
+
925
+ ### Version (`client.version`)
926
+
927
+ * `async getCommitHash()`: Retrieves the latest git commit hash of the deployed backend application.
928
+ * `getVersion()`: Returns SDK version information including version number, build timestamp, build hash, and git commit. This is a synchronous method that returns the version info embedded during the build process.
929
+
930
+ ### WebSocket (`client.socket`)
931
+
932
+ * `connect(channel, options?)`: Connect to a WebSocket channel (e.g., `/user/123`, `/workspace/456`).
933
+ * `disconnect(channel)`: Disconnect from a specific channel.
934
+ * `disconnectAll()`: Disconnect from all connected channels.
935
+ * `subscribe(channel, event, callback)`: Subscribe to an event (e.g., 'notification') on a channel.
936
+ * `unsubscribe(channel, event, callback?)`: Unsubscribe from an event on a channel.
937
+ * `isConnected(channel)`: Check if currently connected to a specific channel.
938
+
939
+ ## SDK Version Tracking
940
+
941
+ Releases follow [semantic versioning](https://semver.org/) and are published
942
+ automatically: every production deploy that changes the SDK publishes the next
943
+ patch version. Building the SDK never changes `package.json`.
944
+
945
+ `getVersion()` reports what the running bundle was built from:
946
+
947
+ - **Published releases** report their version, e.g. `1.4.3`.
948
+ - **Any other build** reports `<base version>+<source hash>`, e.g.
949
+ `1.4.0+60ddbec0`, so it is never mistaken for a release. The same source gives
950
+ the same string on any machine.
951
+
952
+ Alongside the version it carries `buildTimestamp`, `buildHash` (MD5 of the SDK
953
+ source), `gitCommit`, and `dirty` (the build included uncommitted SDK source
954
+ changes).
955
+
956
+ **Generated files:** `src/version.ts` is written by
957
+ `scripts/generate-version-info.cjs` before each build (git-ignored).
958
+
959
+ **Watch mode:** `pnpm dev` rebuilds `dist/` on every source change and keeps the
960
+ dev version current. Inside the Nurama monorepo it starts automatically with the
961
+ web app's dev server.
962
+
963
+ ### Usage in Frontend Code
964
+
965
+ ```typescript
966
+ import nuramaClient from '@/lib/nuramaSdk';
967
+
968
+ // Get SDK version information
969
+ const versionInfo = nuramaClient.getVersion();
970
+
971
+ console.log('SDK Version:', versionInfo.version);
972
+ console.log('Built:', new Date(versionInfo.buildTimestamp).toLocaleString());
973
+ console.log('Build Hash:', versionInfo.buildHash);
974
+ console.log('Git Commit:', versionInfo.gitCommit);
975
+
976
+ // Example output:
977
+ // SDK Version: 1.4.0
978
+ // Built: 10/11/2025, 2:01:01 AM
979
+ // Build Hash: 7db059000a1af183dbfe27dd73017015
980
+ // Git Commit: d13a1e0
981
+ ```
982
+
983
+ **TypeScript Type:**
984
+
985
+ ```typescript
986
+ import { type SDKVersionInfo } from 'nurama-sdk';
987
+
988
+ interface SDKVersionInfo {
989
+ version: string; // Semantic version (e.g., "1.3.3")
990
+ buildTimestamp: string; // ISO timestamp of build
991
+ buildHash: string; // MD5 hash of source files
992
+ gitCommit: string; // Short git commit hash
993
+ }
994
+ ```
995
+
996
+ ### Build Examples
997
+
998
+ **First Build (or after code changes):**
999
+ ```bash
1000
+ $ npm run build
1001
+
1002
+ [Build Hash] Calculating source code hash...
1003
+ [Build Hash] Current hash: 7db059000a1af183dbfe27dd73017015
1004
+ [Build Hash] 🔄 Changes detected - version will be updated
1005
+ [Version Gen] Incrementing version: 1.3.2 → 1.3.3
1006
+ [Version Gen] ✅ Version info generated successfully
1007
+ ...
1008
+ [Build Metadata] ✅ Build metadata saved successfully
1009
+ [Build Metadata] Version: 1.3.3
1010
+ ```
1011
+
1012
+ **Rebuild Without Changes:**
1013
+ ```bash
1014
+ $ npm run build
1015
+
1016
+ [Build Hash] Calculating source code hash...
1017
+ [Build Hash] Current hash: 7db059000a1af183dbfe27dd73017015
1018
+ [Build Hash] ✅ No changes detected - using existing version
1019
+ [Build Hash] Version: 1.3.3
1020
+ [Build Hash] Build time: 2025-10-11T09:01:01.521Z
1021
+ [Version Gen] No code changes - reusing previous version info
1022
+ ...
1023
+ ```
1024
+
1025
+ ### Benefits
1026
+
1027
+ 1. **No Unnecessary Version Bumps**: Same code = same version
1028
+ 2. **Automatic Versioning**: Patch version auto-increments on code changes
1029
+ 3. **Build Traceability**: Hash + timestamp + git commit for debugging
1030
+ 4. **Frontend Visibility**: Easy version checking in production
1031
+ 5. **CI/CD Friendly**: Works in automated build pipelines
1032
+
1033
+ ### Debugging
1034
+
1035
+ If you encounter issues:
1036
+
1037
+ 1. **Check build metadata exists**:
1038
+ ```bash
1039
+ cat dist/.build-metadata.json
1040
+ ```
1041
+
1042
+ 2. **Manually run scripts**:
1043
+ ```bash
1044
+ node scripts/check-build-hash.cjs
1045
+ node scripts/generate-version-info.cjs
1046
+ ```
1047
+
1048
+ 3. **Force new version**: Delete metadata file
1049
+ ```bash
1050
+ rm dist/.build-metadata.json
1051
+ npm run build
1052
+ ```
1053
+
1054
+ 4. **Check version.ts was generated**:
1055
+ ```bash
1056
+ cat src/version.ts
1057
+ ```
1058
+
1059
+ ## File System
1060
+
1061
+ The file system is a separate collection of reference paths to assets and folders. It is designed this way so we can provide fast filterable/sortable virtual file systems across the platform that are organized and behave like a local file system. This allows us to replicate local file system functionality with complex file hierarchy.
1062
+
1063
+ ### Key Features
1064
+
1065
+ - **Path-based organization**: Each file-system entry contains a reference to either an asset or folder as well as shared, indexed keys between assets and folders (keys that exist in both collections) like name, tags, etc.
1066
+ - **Fast searching**: This allows us to rapidly search for folders or assets with the same tag or partial-name matches for both folders and assets without using a complex and slow aggregation query.
1067
+ - **Multiple references**: Assets may have multiple file system entries. For example an asset may exist at `/project/:projectId/creator`, `/project/:projectId/reviewer` and `/submission/:submissionId` - each reference will point to the same asset.
1068
+ - **Unique folders**: Folders may only have one reference. If you copy a folder to a new location you will create a new folder (duplicating the original) and new file-system path.
1069
+
1070
+ ### Path Structure
1071
+
1072
+ The path in the File-System object represents the path to but not including the file system item (Asset or Folder). So the path to asset-1 may be `/project/:projectId/creator` NOT `/project/:projectId/creator/asset-1`.
1073
+
1074
+ ### SDK Support
1075
+
1076
+ The SDK now provides comprehensive file system operations through the `client.project` namespace:
1077
+
1078
+ - **Navigation**: `getItemsAtPath()` for browsing file system paths
1079
+ - **Organization**: `createFolder()`, `moveItemsToPath()`, `copyItemsToPath()`, `deleteItemsAtPath()`
1080
+ - **Publishing**: `publishItems()`, `unpublishItems()` with file system integration
1081
+ - **Search**: Advanced filtering by path, media type, tags, and name search