diff --git a/README.md b/README.md new file mode 100644 index 0000000..ead8efb --- /dev/null +++ b/README.md @@ -0,0 +1,345 @@ +# NATS Client Module - Documentation + +## Overview + +The `natsClient.js` module provides a comprehensive NATS WebSocket client with automatic reconnection, request/reply messaging, and msghandler integration. + +## Features + +- **Automatic Connection**: Connects to NATS server on demand +- **Auto-Reconnection**: Exponential backoff reconnection on connection loss +- **Request/Reply**: NATS request/reply pattern for synchronous communication +- **Publish/Subscribe**: NATS pub/sub pattern for broadcast messaging +- **Msghandler Integration**: Automatic envelope wrapping/unwrapping for message serialization + +## API Reference + +### Connection Management + +#### `getNats(opts)` + +Establishes connection to NATS server. + +```javascript +import { getNats } from './natsClient.js'; + +// Connect to default server +await getNats(); + +// Connect to custom server +await getNats({ servers: 'wss://nats.yiem.cc' }); +``` + +**Parameters:** +- `opts.servers` (string): NATS server URL (default: `wss://nats.yiem.cc`) + +**Returns:** `Promise` - NATS connection object + +#### `connectNATS(brokerUrl, config)` + +Connects to NATS broker using the provided URL or config. + +```javascript +import { connectNATS } from './natsClient.js'; + +await connectNATS('wss://nats.yiem.cc'); +// or +await connectNATS(null, { nats_server_info: { url: 'wss://nats.yiem.cc' } }); +``` + +**Parameters:** +- `brokerUrl` (string): NATS broker URL +- `config` (object): Configuration object with `nats_server_info.url` + +#### `closeNats()` + +Closes the NATS connection and unsubscribes from all topics. + +```javascript +import { closeNats } from './natsClient.js'; + +await closeNats(); +``` + +### Subscription + +#### `subscribe(subject, onMessage)` + +Subscribes to a NATS subject and registers a message handler. + +```javascript +import { subscribe } from './natsClient.js'; + +const subscription = await subscribe('sommpanion.backend.dbapi.v1.inbox', (message) => { + console.log('Received message:', message); +}); + +// Later, to unsubscribe: +await subscription.unsubscribe(); +``` + +**Parameters:** +- `subject` (string): NATS subject to subscribe to +- `onMessage` (function): Callback function to handle incoming messages + +**Returns:** `Promise<{unsubscribe: Function}>` - Subscription object with unsubscribe method + +### Publishing + +#### `publish(subject, payload, opts)` + +Publishes a message to a NATS subject. + +```javascript +import { publish } from './natsClient.js'; + +await publish('sommpanion.backend.dbapi.v1.inbox', { cmd: 'search', data: { keyword: 'chardonnay' } }); +``` + +**Parameters:** +- `subject` (string): NATS subject to publish to +- `payload` (object|string): Message payload +- `opts` (object): Optional configuration + +### Request/Reply + +#### `request(subject, payload, opts)` + +Sends a request and waits for a reply using NATS request/reply pattern. + +```javascript +import { request } from './natsClient.js'; + +const response = await request( + 'sommpanion.backend.dbapi.v1.inbox', + { functioncall: 'search_wine_table', args: { searchkeyword: 'chardonnay' } } +); +``` + +**Parameters:** +- `subject` (string): NATS subject to send request to +- `payload` (string|object): Request payload +- `opts` (object): Optional configuration + +**Returns:** `Promise` - Response from the server + +#### `reply(subject, handler, opts)` + +Sets up a reply handler for request/reply pattern. + +```javascript +import { reply } from './natsClient.js'; + +const subscription = await reply('sommpanion.backend.dbapi.v1.inbox', (request, msg) => { + return { functioncall: request.functioncall, result: 'success', type: 'text' }; +}); + +// Later, to unsubscribe: +await subscription.unsubscribe(); +``` + +**Parameters:** +- `subject` (string): NATS subject to listen on +- `handler` (function): Handler function that returns response +- `opts` (object): Optional configuration + +**Returns:** `Promise<{unsubscribe: Function}>` - Subscription object with unsubscribe method + +### Msghandler Integration + +#### `sendMsghandlerRequest(subject, functioncall, args, config)` + +Sends a request using msghandler envelope format for proper message serialization. + +```javascript +import { sendMsghandlerRequest } from './natsClient.js'; +import msghandlerCSR from './msghandler-csr.js'; + +const response = await sendMsghandlerRequest( + 'sommpanion.backend.dbapi.v1.inbox', + 'search_wine_table', + { searchkeyword: 'chardonnay', searchcolumn: 'wine_name' }, + { nats_server_info: { url: 'wss://nats.yiem.cc' } } +); + +console.log(response.result); // Array of wine records +``` + +**Parameters:** +- `subject` (string): NATS subject to send request to +- `functioncall` (string): Backend function to invoke (e.g., `search_wine_table`) +- `args` (object): Function arguments +- `config` (object): Configuration object containing `nats_server_info.url` + +**Returns:** `Promise` - Response with `functioncall`, `result`, and `type` fields + +### Configuration + +#### Connection Status Store + +```javascript +import { connectionStatus } from './natsClient.js'; + +connectionStatus.subscribe(status => { + console.log('NATS status:', status); +}); +``` + +The `connectionStatus` store emits objects with: +- `state`: `"connecting"`, `"connected"`, `"error"`, `"disconnected"` +- `message`: Human-readable status message + +## End-to-End Examples + +### Example 1: Search with Msghandler + +```javascript +import { sendMsghandlerRequest, connectNATS } from './natsClient.js'; + +async function searchWines(keyword) { + await connectNATS(null, { nats_server_info: { url: 'wss://nats.yiem.cc' } }); + + const response = await sendMsghandlerRequest( + 'sommpanion.backend.dbapi.v1.inbox', + 'search_wine_table', + { + searchkeyword: keyword, + searchcolumn: 'seo_name' + }, + { nats_server_info: { url: 'wss://nats.yiem.cc' } } + ); + + if (response && response.result) { + console.log('Found wines:', response.result); + return response.result; + } + + return []; +} +``` + +### Example 2: Complete Search Component Integration + +```javascript +import { sendMsghandlerRequest, getNats } from './natsClient.js'; + +export async function searchWines(keyword, config) { + try { + await getNats(); + + const searchPayload = { + searchkeyword: keyword || '*', + searchcolumn: 'seo_name' + }; + + const response = await sendMsghandlerRequest( + config?.dbservice_subject || 'sommpanion.backend.dbapi.v1.inbox', + 'search_wine_table', + searchPayload, + config + ); + + return response?.result || []; + + } catch (error) { + console.error('Search failed:', error); + return []; + } +} +``` + +## Error Handling + +### Connection Errors + +```javascript +import { connectNATS } from './natsClient.js'; + +try { + await connectNATS('wss://nats.yiem.cc'); +} catch (error) { + console.error('NATS connection failed:', error); +} +``` + +### Request/Reply Errors + +```javascript +import { sendMsghandlerRequest, connectNATS } from './natsClient.js'; + +try { + const response = await sendMsghandlerRequest( + 'sommpanion.backend.dbapi.v1.inbox', + 'search_wine_table', + { searchkeyword: '*', searchcolumn: 'seo_name' }, + { nats_server_info: { url: 'wss://nats.yiem.cc' } } + ); + + console.log(response.result); +} catch (error) { + console.error('Request failed:', error.message); +} +``` + +## Best Practices + +1. **Reuse Connection**: Don't connect/disconnect for each request. Connect once on app startup. +2. **Handle Errors**: Always wrap NATS operations in try-catch blocks. +3. **Use Msghandler**: Always use `sendMsghandlerRequest()` for backend API calls. +4. **Clean Up**: Unsubscribe from topics when components are destroyed. +5. **Configuration**: Read NATS server URL from config for environment-specific configuration. + +## Msghandler Message Format + +### Request Envelope + +When using `sendMsghandlerRequest()`, the module automatically: +1. Wraps the payload in msghandler format +2. Uses `dictionary` payload type with `db_request` data name +3. Automatically selects transport based on payload size + +### Response Format + +The response from backend follows this structure: + +```json +{ + "correlation_id": "uuid-v4", + "msg_id": "uuid-v4", + "timestamp": "2026-05-26T...", + "send_to": "sommpanion.backend.dbapi.v1.inbox", + "msg_purpose": "chat", + "sender_name": "agent-backend", + "payloads": [ + ["db_request", { + "functioncall": "search_wine_table", + "result": [...], + "type": "jsontable" + }, "dictionary"] + ] +} +``` + +The `sendMsghandlerRequest()` function automatically: +- Unwraps the envelope using `smartunpack()` +- Extracts the result from `payloads[0][1]` +- Returns `{functioncall, result, type}` object + +## Troubleshooting + +### Connection Issues + +- Verify NATS server URL in config +- Check network connectivity +- Ensure NATS server is running + +### Request Timeouts + +- Check backend service is running +- Verify subject matches backend listener + +### Response Parsing Errors + +- Ensure backend returns msghandler format +- Check payload type matches expected format +- Verify `functioncall` and `result` fields exist in response