Files
NATSBridge/docs/specification.md
2026-03-23 14:50:00 +07:00

1329 lines
45 KiB
Markdown

# Specification: NATSBridge
**Version**: 1.1.0
**Date**: 2026-03-23
**Status**: Active
**Ground Truth**: [`src/NATSBridge.jl`](../src/NATSBridge.jl)
**Specification Format**: JSON Schema + AsyncAPI
---
## 1. Technical Contract Overview
This document defines the **technical contract** for NATSBridge - the cross-platform bi-directional data bridge that enables seamless communication between **Julia**, **JavaScript**, **Python**, **Dart**, and **MicroPython** applications using NATS as the message bus.
This specification serves as the single source of truth for:
- **Inputs**: What data structures are accepted by `smartsend()`
- **Outputs**: What data structures are returned by `smartreceive()`
- **Data Shapes**: Exact field names, types, and constraints
- **Error Codes**: Standardized error responses for failure scenarios
### 1.1 Requirements Traceability
| Specification Section | Requirement ID(s) | Description |
|----------------------|-------------------|-------------|
| Section 2 (Message Envelope) | FR-012, FR-013, NFR-101, NFR-102 | Message envelope structure and validation |
| Section 3 (Payload Schema) | FR-001, FR-002, FR-003, FR-004, NFR-101, NFR-102 | Payload structure and field definitions |
| Section 4 (Payload Format) | FR-006, FR-007 | Tuple format for smartsend() |
| Section 5 (Enumerations) | FR-003, FR-004, FR-006, NFR-101 | Enumerations for transport and encoding |
| Section 6 (Transport Protocols) | FR-003, FR-004, NFR-104, NFR-105 | Direct and link transport protocols |
| Section 7 (Size Thresholds) | FR-004, FR-005, NFR-104, NFR-105 | Size thresholds for transport selection |
| Section 8 (NATS Subject Convention) | FR-013, FR-014 | NATS subject naming patterns |
| Section 9 (Error Handling) | FR-010, FR-011, NFR-201, NFR-202, NFR-203 | Error codes and exception handling |
| Section 10 (Serialization Rules) | FR-001, FR-002, FR-003, FR-012, NFR-101, NFR-102 | Serialization and encoding rules |
| Section 11 (API Contract) | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 | Function signatures for all platforms |
| Section 12 (File Server Interface) | FR-008, FR-009, FR-010 | Upload and download handler contracts |
| Section 13 (Platform-Specific Constraints) | FR-005, FR-006, NFR-106, NFR-107 | Platform-specific feature support |
| Section 14 (Implementation Files) | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007 | Implementation file mapping |
| Section 15 (Message Flow) | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 | Mermaid diagrams for send/receive flows |
| Section 16 (Validation Rules) | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 | Envelope and payload validation rules |
| Section 17 (Test Contracts) | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 | Unit and integration test scenarios |
| Section 18 (Dependencies) | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 | Platform-specific dependencies |
| Section 19 (Change Log) | N/A | Version history and changes |
---
## 2. Message Envelope Schema
## Specification Versioning
| Component | Version | Notes |
|-----------|---------|-------|
| Specification | 1.0.0 | Initial release |
| Protocol | v1 | Message envelope protocol version |
---
## Message Envelope Schema
### Envelope Structure (JSON)
```json
{
"correlation_id": "string (UUID)",
"msg_id": "string (UUID)",
"timestamp": "string (ISO 8601 UTC)",
"send_to": "string",
"msg_purpose": "string",
"sender_name": "string",
"sender_id": "string (UUID)",
"receiver_name": "string",
"receiver_id": "string (UUID)",
"reply_to": "string",
"reply_to_msg_id": "string",
"broker_url": "string",
"metadata": "object",
"payloads": [
{
"id": "string (UUID)",
"dataname": "string",
"payload_type": "string",
"transport": "string",
"encoding": "string",
"size": "integer",
"data": "string or URL",
"metadata": "object"
}
]
}
```
### Field Definitions
| Field | Type | Required | Validation | Description | Requirement ID |
|-------|------|----------|------------|-------------|----------------|
| `correlation_id` | `string` | Yes | UUID v4 format | Track message flow across distributed systems | FR-011, NFR-401 |
| `msg_id` | `string` | Yes | UUID v4 format | Unique identifier for this specific message | FR-012, NFR-401 |
| `timestamp` | `string` | Yes | ISO 8601 UTC | Message publication timestamp | FR-012, NFR-401 |
| `send_to` | `string` | Yes | Non-empty string | NATS subject/topic to publish the message to | FR-013 |
| `msg_purpose` | `string` | Yes | Enum | Purpose of the message | FR-013 |
| `sender_name` | `string` | Yes | Non-empty string | Name of the sender application | FR-013 |
| `sender_id` | `string` | Yes | UUID v4 format | Unique identifier for the sender | FR-013 |
| `receiver_name` | `string` | Yes | Any string | Name of the receiver (empty = broadcast) | FR-013 |
| `receiver_id` | `string` | Yes | Any string | UUID of the receiver (empty = broadcast) | FR-013 |
| `reply_to` | `string` | Yes | Any string | Topic where receiver should reply | FR-013 |
| `reply_to_msg_id` | `string` | Yes | Any string | Message ID this message is replying to | FR-013 |
| `broker_url` | `string` | Yes | Valid URL | NATS broker URL | FR-013 |
| `metadata` | `object` | No | Any JSON object | Message-level metadata | NFR-401 |
| `payloads` | `array` | Yes | Non-empty array | List of payload objects | FR-012, FR-013 |
---
## Payload Schema
### Payload Structure (JSON)
```json
{
"id": "string (UUID)",
"dataname": "string",
"payload_type": "string",
"transport": "string",
"encoding": "string",
"size": "integer",
"data": "string or URL",
"metadata": "object"
}
```
### Payload Field Definitions
| Field | Type | Required | Validation | Description | Requirement ID |
|-------|------|----------|------------|-------------|----------------|
| `id` | `string` | Yes | UUID v4 format | Unique identifier for this payload | FR-012 |
| `dataname` | `string` | Yes | Non-empty string | Name of the payload (e.g., `login_image`, `user_data`) | FR-001, FR-002, FR-003 |
| `payload_type` | `string` | Yes | Enum | Type of payload (see `payload_type` enum) | FR-001, FR-002, FR-003, FR-006 |
| `transport` | `string` | Yes | Enum | Transport method: `direct` or `link` | FR-003, FR-004, NFR-104, NFR-105 |
| `encoding` | `string` | Yes | Enum | Encoding method (see `encoding` enum) | FR-001, FR-002, FR-003, FR-012 |
| `size` | `integer` | Yes | Positive integer | Size of the payload in bytes | FR-003, FR-004, NFR-104, NFR-105 |
| `data` | `string` or `URL` | Yes | Base64 string or URL | Payload data (base64 for direct, URL for link) | FR-003, FR-004, FR-008, FR-009, FR-010 |
| `metadata` | `object` | No | Any JSON object | Payload-level metadata | NFR-401 |
---
## Payload Format
### Tuple Format for `smartsend()`
The `smartsend()` function accepts data as an array of tuples with the format:
```
("data_name", data, "data_type")
```
| Position | Type | Description | Example |
|----------|------|-------------|---------|
| 1 | `string` | Data name - identifier for the payload | `"msg"`, `"login_image"`, `"user_data"` |
| 2 | `any` | Actual data - content to be serialized | `"Hello"`, `{"key": "value"}`, `DataFrame(...)` |
| 3 | `string` | Data type - must be in `payload_type` enum | `"text"`, `"dictionary"`, `"arrowtable"` |
### Single Payload Example
```julia
# Julia
smartsend("/chat/user/v1/message", [("msg", "Hello World", "text")])
```
```python
# Python
await smartsend("/chat/user/v1/message", [("msg", "Hello World", "text")])
```
```typescript
// JavaScript
await smartsend("/chat/user/v1/message", [["msg", "Hello World", "text"]]);
```
### Multiple Payloads Example
```julia
# Julia - Mixed text and binary data
data = [
("msg", "Hello", "text"),
("img", binary_data, "image")
]
smartsend("/agent/v1/process", data)
```
```python
# Python - Mixed types
data = [
("msg", "Hello", "text"),
("img", binary_data, "image")
]
await smartsend("/agent/v1/process", data)
```
### Data Type Mapping
| Platform | Input Type | Data Type String |
|----------|------------|------------------|
| All | `String` | `"text"` |
| All | `Dict`/`Object` | `"dictionary"` |
| Desktop | `DataFrame` | `"arrowtable"` or `"jsontable"` |
| Browser | `Array` of objects | `"jsontable"` (only table type) |
| All | `Array` of objects | `"jsontable"` |
| All | `Uint8Array`/`Buffer`/`bytes` | `"binary"` |
| Desktop | `Arrow.Table` | `"arrowtable"` |
| All | Image/Audio/Video binary | `"image"`, `"audio"`, `"video"` |
---
## Enumerations
### `msg_purpose` Enum
| Value | Description |
|-------|-------------|
| `ACK` | Acknowledgment of successful message processing |
| `NACK` | Negative acknowledgment of message processing failure |
| `updateStatus` | Status update message |
| `shutdown` | Graceful shutdown request |
| `chat` | Chat/message payload |
| `command` | Command payload |
| `event` | Event payload |
### `payload_type` Enum
| Value | Description | Supported Platforms | Encoding Options |
|-------|-------------|---------------------|------------------|
| `text` | Plain text string | All | `base64` |
| `dictionary` | JSON object/dictionary | All | `base64`, `json` |
| `arrowtable` | Apache Arrow IPC table | Desktop (Julia/Python/Node.js/Dart) | `base64`, `arrow-ipc` |
| `jsontable` | JSON array of objects | All (including Browser/Dart Web) | `base64`, `json` |
| `image` | Binary image data | All | `base64` |
| `audio` | Binary audio data | All | `base64` |
| `video` | Binary video data | All | `base64` |
| `binary` | Generic binary data | All | `base64` |
### `transport` Enum
| Value | Description | Data Format | Use Case |
|-------|-------------|-------------|----------|
| `direct` | Payload sent directly via NATS | Base64-encoded string | Payloads < size_threshold |
| `link` | Payload uploaded to file server | HTTP URL | Payloads ≥ size_threshold |
### `encoding` Enum
| Value | Description | Payload Types |
|-------|-------------|---------------|
| `none` | No additional encoding | Link transport URLs |
| `base64` | Base64 encoding | Text, binary, image, audio, video |
| `json` | JSON encoding | Dictionary, jsontable |
| `arrow-ipc` | Apache Arrow IPC format | Arrowtable |
---
## Transport Protocols
### Direct Transport Protocol
When `transport = "direct"`, the `data` field contains a Base64-encoded string of the serialized payload.
**Flow**:
1. Serialize payload according to `payload_type`
2. Encode serialized bytes as Base64
3. Include Base64 string in `data` field
**Example**:
```json
{
"transport": "direct",
"encoding": "base64",
"size": 11,
"data": "SGVsbG8gV29ybGQ="
}
```
### Link Transport Protocol
When `transport = "link"`, the `data` field contains a URL pointing to the uploaded payload.
**Flow**:
1. Serialize payload according to `payload_type`
2. Upload to HTTP file server (e.g., Plik)
3. Include returned URL in `data` field
**Example**:
```json
{
"transport": "link",
"encoding": "none",
"size": 1000000,
"data": "http://localhost:8080/file/3F62E/4AgGT/data.zip"
}
```
---
## Size Thresholds
### Desktop Platforms (Julia/JS/Python)
| Platform | Size Threshold | Notes |
|----------|----------------|-------|
| Desktop | 500,000 bytes (0.5MB) | Default threshold |
### MicroPython Platform
| Platform | Size Threshold | Maximum Payload | Notes |
|----------|----------------|-----------------|-------|
| MicroPython | 100,000 bytes (100KB) | 50,000 bytes | Hard limit due to memory constraints |
---
## NATS Subject Convention
### Subject Naming Pattern
```
<scope>/<service>/<version>/<operation>
```
**Examples**:
- `/agent/wine/api/v1/prompt` - AI agent prompt endpoint
- `/chat/user/v1/message` - User chat message
- `/system/worker/v1/status` - Worker status update
### Subject Wildcards
| Wildcard | Description | Example |
|----------|-------------|---------|
| `*` | Single-level wildcard | `/chat/user/v1/*` matches `/chat/user/v1/message` |
| `>` | Multi-level wildcard | `/chat/user/v1/>` matches all `/chat/user/v1/*` subjects |
---
## Error Handling
### Error Response Format
```json
{
"error": {
"code": "string",
"message": "string",
"details": "object"
}
}
```
### Error Codes
| Code | HTTP Status | Description | Recovery | Requirement ID |
|------|-------------|-------------|----------|----------------|
| `INVALID_ENVELOPE` | 400 | Message envelope validation failed | Fix envelope structure | FR-012, FR-013, FR-014 |
| `INVALID_PAYLOAD_TYPE` | 400 | Unsupported payload type | Use supported payload_type | FR-001, FR-002, FR-003, FR-006 |
| `INVALID_TRANSPORT` | 400 | Unsupported transport type | Use `direct` or `link` | FR-003, FR-004, FR-006 |
| `UPLOAD_FAILED` | 500 | File server upload failed | Retry or use direct transport | FR-008, FR-009 |
| `DOWNLOAD_FAILED` | 503 | File server download failed | Retry with exponential backoff | FR-010, FR-011, NFR-201, NFR-202 |
| `NATS_CONNECTION_FAILED` | 503 | NATS connection failed | Check NATS server availability | FR-013, FR-014, NFR-201, NFR-203 |
| `DESERIALIZATION_ERROR` | 500 | Payload deserialization failed | Check payload_type matches data | FR-001, FR-002, FR-003, FR-012 |
| `SIZE_EXCEEDED` | 413 | Payload exceeds maximum size | Split payload or use link transport | FR-003, FR-004, FR-005, NFR-104, NFR-105 |
### Exception Handling
| Scenario | Handler | Retry Policy | Requirement ID |
|----------|---------|--------------|----------------|
| File server unavailable | Retry up to 5 times | Exponential backoff (100ms → 5000ms) | FR-010, NFR-202 |
| NATS publish failure | Connection auto-reconnect | TCP-level reconnection | FR-013, FR-014, NFR-201, NFR-203 |
| Deserialization error | Log correlation ID and throw | No retry (data corruption) | FR-001, FR-002, FR-003, FR-012, NFR-401 |
| Memory overflow (MicroPython) | Reject payloads >50KB | No retry (client-side check) | FR-005, NFR-106 |
---
## Serialization Rules
### Text Serialization
| Platform | Input Type | Serialization | Encoding |
|----------|------------|---------------|----------|
| All | `String` | UTF-8 bytes | Base64 |
### Dictionary Serialization
| Platform | Input Type | Serialization | Encoding |
|----------|------------|---------------|----------|
| All | `Object`/`Dict` | JSON string | Base64 or direct JSON |
### Arrow Table Serialization
| Platform | Input Type | Serialization | Encoding |
|----------|------------|---------------|----------|
| Desktop | `DataFrame` | Arrow IPC stream | Base64 or arrow-ipc |
| Desktop | `Arrow.Table` | Arrow IPC stream | Base64 or arrow-ipc |
| MicroPython | ❌ | Not supported | N/A |
### JSON Table Serialization
| Platform | Input Type | Serialization | Encoding |
|----------|------------|---------------|----------|
| All | `Vector{Dict}`/`Array<Object>` | JSON array | Base64 or direct JSON |
| Desktop | `pandas.DataFrame` | JSON array | Base64 or direct JSON |
### Binary Serialization
| Platform | Input Type | Serialization | Encoding |
|----------|------------|---------------|----------|
| All | `Uint8Array`/`Buffer`/`bytes` | Raw bytes | Base64 |
---
## API Contract
### `smartsend` Function Signature
#### Julia
```julia
function smartsend(
subject::String,
data::AbstractArray{Tuple{String, Any, String}};
broker_url::String = "nats://localhost:4222",
fileserver_url::String = "http://localhost:8080",
fileserver_upload_handler::Function = plik_oneshot_upload,
size_threshold::Int = 500_000,
correlation_id::String = string(uuid4()),
msg_purpose::String = "chat",
sender_name::String = "NATSBridge",
receiver_name::String = "",
receiver_id::String = "",
reply_to::String = "",
reply_to_msg_id::String = "",
is_publish::Bool = true,
NATS_connection::Union{NATS.Connection, Nothing} = nothing,
msg_id::String = string(uuid4()),
sender_id::String = string(uuid4())
)::Tuple{msg_envelope_v1, String}
```
#### Python
```python
async def smartsend(
subject: str,
data: List[Tuple[str, Any, str]],
broker_url: str = "nats://localhost:4222",
fileserver_url: str = "http://localhost:8080",
fileserver_upload_handler: Callable = plik_oneshot_upload,
size_threshold: int = 500_000,
correlation_id: str = None,
msg_purpose: str = "chat",
sender_name: str = "NATSBridge",
receiver_name: str = "",
receiver_id: str = "",
reply_to: str = "",
reply_to_msg_id: str = "",
is_publish: bool = True,
nats_connection: Any = None,
msg_id: str = None,
sender_id: str = None
) -> Tuple[Dict, str]:
```
#### JavaScript (Node.js)
```typescript
async function smartsend(
subject: string,
data: Array<[string, any, string]>,
options?: {
broker_url?: string;
fileserver_url?: string;
fileserver_upload_handler?: Function;
size_threshold?: number;
correlation_id?: string;
msg_purpose?: string;
sender_name?: string;
receiver_name?: string;
receiver_id?: string;
reply_to?: string;
reply_to_msg_id?: string;
is_publish?: boolean;
nats_connection?: NATS.Connection;
msg_id?: string;
sender_id?: string;
}
): Promise<[Object, string]>;
```
#### JavaScript (Browser)
```typescript
async function smartsend(
subject: string,
data: Array<[string, any, string]>,
options?: {
broker_url?: string;
fileserver_url?: string;
fileserver_upload_handler?: Function;
size_threshold?: number;
correlation_id?: string;
msg_purpose?: string;
sender_name?: string;
receiver_name?: string;
receiver_id?: string;
reply_to?: string;
reply_to_msg_id?: string;
is_publish?: boolean;
nats_connection?: NATSClient | NATS.Connection;
msg_id?: string;
sender_id?: string;
}
): Promise<[Object, string]>;
// NATSClient class for connection management
class NATSClient {
constructor(url: string, keepAlive?: boolean);
connect(): Promise<NATS.Connection>;
publish(subject: string, message: string, correlationId: string): Promise<void>;
close(): Promise<void>;
getConnection(): NATS.Connection | null;
isConnected(): boolean;
}
// NATSConnectionPool for managing multiple connections
class NATSConnectionPool {
constructor(url: string, maxSize?: number);
acquire(): Promise<NATSClient>;
release(client: NATSClient): void;
closeAll(): Promise<void>;
}
// publishMessage function for manual publishing
async function publishMessage(
brokerUrlOrClient: string | NATSClient | NATS.Connection,
subject: string,
message: string,
correlationId: string,
closeConnection?: boolean
): Promise<void>;
```
#### MicroPython
```python
def smartsend(
subject: str,
data: List[Tuple[str, Any, str]],
**kwargs
) -> Tuple[Dict, str]:
```
#### Dart (Desktop/Flutter)
```dart
Future<[Map<String, dynamic>, String]> smartsend(
String subject,
List<List<dynamic>> data, {
String brokerUrl = 'nats://localhost:4222',
String fileserverUrl = 'http://localhost:8080',
Function? fileserverUploadHandler,
int sizeThreshold = 500000,
String? correlationId,
String msgPurpose = 'chat',
String senderName = 'NATSBridge',
String receiverName = '',
String receiverId = '',
String replyTo = '',
String replyToMsgId = '',
bool isPublish = true,
dynamic natsConnection,
String? msgId,
String? senderId,
}) async {
// Returns [envelope, jsonString]
}
```
#### Dart Web
```dart
Future<[Map<String, dynamic>, String]> smartsend(
String subject,
List<List<dynamic>> data, {
String brokerUrl = 'nats://localhost:4222',
String fileserverUrl = 'http://localhost:8080',
Function? fileserverUploadHandler,
int sizeThreshold = 500000,
String? correlationId,
String msgPurpose = 'chat',
String senderName = 'NATSBridge',
String receiverName = '',
String receiverId = '',
String replyTo = '',
String replyToMsgId = '',
bool isPublish = true,
dynamic natsConnection,
String? msgId,
String? senderId,
}) async {
// Returns [envelope, jsonString]
}
```
### `smartreceive` Function Signature
#### Julia
```julia
function smartreceive(
msg::NATS.Msg;
fileserver_download_handler::Function = _fetch_with_backoff,
max_retries::Int = 5,
base_delay::Int = 100,
max_delay::Int = 5000
)::JSON.Object{String, Any}
```
#### Python
```python
async def smartreceive(
msg: Any,
fileserver_download_handler: Callable = fetch_with_backoff,
max_retries: int = 5,
base_delay: int = 100,
max_delay: int = 5000
) -> Dict[str, Any]:
```
#### JavaScript (Node.js)
```typescript
async function smartreceive(
msg: Object,
options?: {
fileserver_download_handler?: Function;
max_retries?: number;
base_delay?: number;
max_delay?: number;
}
): Promise<Object>;
```
#### JavaScript (Browser)
```typescript
async function smartreceive(
msg: Object,
options?: {
fileserver_download_handler?: Function;
max_retries?: number;
base_delay?: number;
max_delay?: number;
}
): Promise<Object>;
```
#### MicroPython
```python
def smartreceive(msg: Any, **kwargs) -> Dict[str, Any]:
```
#### Dart (Desktop/Flutter)
```dart
Future<Map<String, dynamic>> smartreceive(
Map<String, dynamic> msg, {
Function? fileserverDownloadHandler,
int maxRetries = 5,
int baseDelay = 100,
int maxDelay = 5000,
}) async {
// Returns envelope with processed payloads
}
```
#### Dart Web
```dart
Future<Map<String, dynamic>> smartreceive(
Map<String, dynamic> msg, {
Function? fileserverDownloadHandler,
int maxRetries = 5,
int baseDelay = 100,
int maxDelay = 5000,
}) async {
// Returns envelope with processed payloads
}
```
---
## File Server Interface
### Upload Handler Contract
**Function Signature**:
```julia
function fileserver_upload_handler(
file_server_url::String,
dataname::String,
data::Vector{UInt8}
)::Dict{String, Any}
```
**Return Format**:
```json
{
"status": 200,
"uploadid": "string",
"fileid": "string",
"url": "string"
}
```
**Required Keys**:
| Key | Type | Description |
|-----|------|-------------|
| `status` | `integer` | HTTP response status code |
| `uploadid` | `string` | Upload session identifier |
| `fileid` | `string` | File identifier within session |
| `url` | `string` | Full download URL |
### Download Handler Contract
**Function Signature**:
```julia
function fileserver_download_handler(
url::String,
max_retries::Int,
base_delay::Int,
max_delay::Int,
correlation_id::String
)::Vector{UInt8}
```
**Retry Policy**:
- Initial delay: `base_delay` milliseconds
- Maximum delay: `max_delay` milliseconds
- Multiplier: 2x per retry
- Maximum retries: `max_retries`
---
## Platform-Specific Constraints
### Desktop (Julia/Python/Node.js/Dart)
| Feature | Status | Notes |
|---------|--------|-------|
| Arrow IPC | ✅ Supported | Requires Arrow.jl/pyarrow/dart-arrow |
| JSON table | ✅ Supported | Human-readable format |
| File server upload | ✅ Supported | HTTP/HTTPS |
| File server download | ✅ Supported | HTTP/HTTPS |
| Size threshold | 500KB | Configurable |
### Browser (JavaScript)
| Feature | Status | Notes |
|---------|--------|-------|
| Arrow IPC | ❌ Not supported | Apache Arrow not browser-compatible |
| JSON table | ✅ Supported | Only table type available in browser |
| File server upload | ✅ Supported | HTTP/HTTPS |
| File server download | ✅ Supported | HTTP/HTTPS |
| Size threshold | 500KB | Configurable |
### Dart Desktop (Dart SDK)
| Feature | Status | Notes |
|---------|--------|-------|
| Arrow IPC | ✅ Supported | Requires dart-arrow package |
| JSON table | ✅ Supported | Human-readable format |
| File server upload | ✅ Supported | HTTP/HTTPS |
| File server download | ✅ Supported | HTTP/HTTPS |
| Size threshold | 500KB | Configurable |
### Dart Flutter (Dart SDK)
| Feature | Status | Notes |
|---------|--------|-------|
| Arrow IPC | ✅ Supported | Requires dart-arrow package |
| JSON table | ✅ Supported | Human-readable format |
| File server upload | ✅ Supported | HTTP/HTTPS |
| File server download | ✅ Supported | HTTP/HTTPS |
| Size threshold | 500KB | Configurable |
### Dart Web (Dart SDK)
| Feature | Status | Notes |
|---------|--------|-------|
| Arrow IPC | ❌ Not supported | Apache Arrow not browser-compatible |
| JSON table | ✅ Supported | Only table type available in browser |
| File server upload | ✅ Supported | HTTP/HTTPS |
| File server download | ✅ Supported | HTTP/HTTPS |
| Size threshold | 500KB | Configurable |
### MicroPython
| Feature | Status | Notes |
|---------|--------|-------|
| Arrow IPC | ❌ Not supported | Memory constraints |
| JSON table | ⚠️ Limited | Only direct transport |
| File server upload | ❌ Not implemented | Placeholder only |
| File server download | ❌ Not implemented | Placeholder only |
| Size threshold | 100KB | Hard limit enforced |
| Max payload | 50KB | Hard limit enforced |
---
## Implementation Files
| File | Platform | Features | Notes |
|------|----------|----------|-------|
| [`src/NATSBridge.jl`](../src/NATSBridge.jl) | Julia | Full feature set, Arrow IPC, multiple dispatch | Ground truth implementation |
| [`src/natsbridge_ssr.js`](../src/natsbridge_ssr.js) | Node.js | Arrow IPC, async/await | Server-side JavaScript |
| [`src/natsbridge_csr.js`](../src/natsbridge_csr.js) | Browser | JSON table only, WebSocket NATS | Client-side rendering |
| [`src/natsbridge.py`](../src/natsbridge.py) | Python | Arrow IPC, async/await | Desktop Python |
| [`src/natsbridge.dart`](../src/natsbridge.dart) | Dart | Full feature set, Arrow IPC, async/await | Desktop/Flutter/Web |
| [`src/natsbridge_mpy.py`](../src/natsbridge_mpy.py) | MicroPython | Limited to direct transport | Memory-constrained |
### Browser Implementation Notes
The browser implementation ([`src/natsbridge_csr.js`](../src/natsbridge_csr.js)) has the following constraints:
| Constraint | Reason | Workaround |
|------------|--------|------------|
| No Apache Arrow IPC | Browser-incompatible dependency | Use `jsontable` for tabular data |
| WebSocket NATS only | Browser cannot use TCP directly | Use `ws://` or `wss://` broker URLs |
| Fetch API for HTTP | Browser fetch() API only | Compatible with Plik and other HTTP servers |
### Payload Type Availability by Platform
| Payload Type | Julia | Node.js | Browser | Python | Dart | MicroPython |
|--------------|-------|---------|---------|--------|------|-------------|
| `text` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `dictionary` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `arrowtable` | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
| `jsontable` | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| `image` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `audio` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `video` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `binary` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
---
## Message Flow
### Sending Flow
```mermaid
flowchart TD
A[User calls smartsend subject data] --> B[Serialize payload according to payload_type]
B --> C{Calculate serialized size}
C -->|Size < Threshold| D[Direct Transport: Encode as Base64]
C -->|Size >= Threshold| E[Link Transport: Upload to file server]
D --> F[Build envelope with metadata]
E --> F
F --> G[Convert envelope to JSON string]
G --> H[Publish to NATS subject]
H --> I[Return envelope and JSON string to caller]
style A fill:#f9f9f9,stroke:#333
style I fill:#e0e7ff,stroke:#3b82f6
style D fill:#d1fae5,stroke:#10b981
style E fill:#fef3c7,stroke:#f59e0b
```
### Receiving Flow
```mermaid
flowchart TD
A[NATS message arrives] --> B[Parse JSON envelope]
B --> C[For each payload: Check transport type]
C -->|transport == direct| D[Direct Transport: Extract Base64]
C -->|transport == link| E[Link Transport: Fetch from URL]
D --> F[Decode Base64]
E --> G[Fetch with exponential backoff]
F --> H[Deserialize based on payload_type]
G --> H
H --> I[Build payloads array]
I --> J[Replace payloads array with deserialized tuples]
J --> K[Return envelope with processed payloads]
style A fill:#f9f9f9,stroke:#333
style K fill:#e0e7ff,stroke:#3b82f6
style D fill:#d1fae5,stroke:#10b981
style E fill:#fef3c7,stroke:#f59e0b
```
---
## Validation Rules
### Envelope Validation
| Rule | Condition | Error Code | Requirement ID |
|------|-----------|------------|----------------|
| Required fields present | `correlation_id`, `msg_id`, `timestamp`, `send_to`, `payloads` | `INVALID_ENVELOPE` | FR-012, FR-013 |
| Valid UUID format | `correlation_id`, `msg_id`, `sender_id`, `receiver_id` | `INVALID_ENVELOPE` | FR-011, FR-012, NFR-401 |
| Valid timestamp format | ISO 8601 UTC | `INVALID_ENVELOPE` | FR-012, NFR-401 |
| Non-empty payloads array | `length(payloads) > 0` | `INVALID_ENVELOPE` | FR-012, FR-013 |
### Payload Validation
| Rule | Condition | Error Code | Requirement ID |
|------|-----------|------------|----------------|
| Valid payload_type | Must be in `payload_type` enum | `INVALID_PAYLOAD_TYPE` | FR-001, FR-002, FR-003, FR-006 |
| Valid transport | Must be `direct` or `link` | `INVALID_TRANSPORT` | FR-003, FR-004, FR-006 |
| Valid encoding | Must match payload_type and transport | `INVALID_TRANSPORT` | FR-001, FR-002, FR-003, FR-012 |
| Positive size | `size > 0` | `INVALID_PAYLOAD` | FR-003, FR-004, NFR-104, NFR-105 |
| Valid Base64 for direct | `data` matches Base64 pattern | `DESERIALIZATION_ERROR` | FR-001, FR-002, FR-003, FR-012 |
| Valid URL for link | `data` matches HTTP(S) URL pattern | `DOWNLOAD_FAILED` | FR-008, FR-009, FR-010 |
---
## Test Contracts
### Unit Test Validation
| Test | Input | Expected Output | Notes | Requirement ID |
|------|-------|-----------------|-------|----------------|
| Text round-trip | `("msg", "Hello", "text")` | `("msg", "Hello", "text")` | String serialization | FR-001, FR-012, NFR-101, NFR-102 |
| Dictionary round-trip | `("data", {"key": "value"}, "dictionary")` | `("data", {"key": "value"}, "dictionary")` | JSON object round-trip | FR-002, FR-012, NFR-101, NFR-102 |
| Arrow table round-trip | `("table", arrow_table_data, "arrowtable")` | `("table", arrow_table_data, "arrowtable")` | Arrow IPC round-trip | FR-002, FR-012, NFR-101, NFR-102 |
| JSON table round-trip | `("table", [{"a":1},{"b":2}], "jsontable")` | `("table", [{"a":1},{"b":2}], "jsontable")` | JSON array of objects | FR-001, FR-002, FR-006, FR-012 |
| Mixed payloads | `[("msg", "Hello", "text"), ("imgname", bytes, "binary")]` | `[("msg", "Hello", "text"), ("imgname", bytes, "binary")]` | Multiple payload types | FR-006, FR-007 |
| Large payload | `("data", rand(10_000_000), "arrowtable")` | `("data", URL, "arrowtable")` with link transport | File server upload | FR-003, FR-004, FR-008, FR-009, NFR-104, NFR-105 |
**Platform-Specific Notes:**
- **Julia**: Use `Dict`, `Vector{Dict}`, or convert `DataFrame` to dictionary for testing
- **Python**: Use `dict`, `list[dict]`, or convert `pandas.DataFrame` to dictionary for testing
- **JavaScript**: Use plain objects `{}` and arrays `[]`
- **MicroPython**: Use plain `dict` and `list` (limited to JSON table and text types)
### Integration Test Scenarios
| Scenario | Platforms | Payloads | Size Mix | Transport | Expected Result | Requirement ID |
|----------|-----------|----------|----------|-----------|-----------------|----------------|
| Single text (small) | All | `text` | Small | direct | Round-trip successful | FR-001, FR-012, NFR-101, NFR-102 |
| Single dictionary (small) | All | `dictionary` | Small | direct | Round-trip successful | FR-002, FR-012, NFR-101, NFR-102 |
| Single arrow table (small) | Julia/JS/Python | `arrowtable` | Small | direct | Arrow IPC round-trip | FR-002, FR-012, NFR-101, NFR-102 |
| Single JSON table (small) | All | `jsontable` | Small | direct | Dictionary array round-trip | FR-001, FR-002, FR-006, FR-012 |
| Single image (small) | All | `image` | Small | direct | Binary round-trip | FR-001, FR-006, FR-012 |
| Single audio (small) | All | `audio` | Small | direct | Binary round-trip | FR-001, FR-006, FR-012 |
| Single video (small) | All | `video` | Small | direct | Binary round-trip | FR-001, FR-006, FR-012 |
| Single binary (small) | All | `binary` | Small | direct | Binary round-trip | FR-001, FR-006, FR-012 |
| Single text (large) | All | `text` | Large | link | File server upload/download | FR-003, FR-004, FR-008, FR-009, NFR-104, NFR-105 |
| Single JSON table (large) | All | `jsontable` | Large | link | File server upload/download | FR-003, FR-004, FR-008, FR-009, NFR-104, NFR-105 |
| Single image (large) | All | `image` | Large | link | File server upload/download | FR-003, FR-004, FR-008, FR-009, NFR-104, NFR-105 |
| **Ultimate Test** | Julia/JS/Python | `text` (small) + `dictionary` (small) + `arrowtable` (small) + `jsontable` (small) + `image` (small) + `audio` (small) + `video` (small) + `binary` (small) + `text` (large) + `dictionary` (large) + `arrowtable` (large) + `jsontable` (large) + `image` (large) | Mixed | direct/link | All payloads preserved with correct transport | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 |
| **Ultimate Test** | MicroPython | `text` (small) + `dictionary` (small) + `text` (large) + `dictionary` (large) | Mixed | direct | Limited to text/dictionary with direct transport only | FR-005, FR-006, FR-012 |
| Cross-platform JSON table | All | `jsontable` | Small | direct | Dictionary array round-trip | FR-001, FR-002, FR-006, FR-012 |
| MicroPython ↔ Desktop | MicroPython ↔ Desktop | `text`/`dictionary` | Small | direct | Limited payload types | FR-005, FR-006, FR-012 |
| Desktop ↔ Desktop (all combos) | Julia↔JS↔Python | All types | Small/Large | direct/link | Full compatibility | FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007, FR-012, FR-013, FR-014 |
---
## Dependencies
### Required Dependencies by Platform
| Platform | Package | Version | Purpose |
|----------|---------|---------|---------|
| Julia | NATS.jl | Latest | NATS client |
| Julia | JSON.jl | Latest | JSON serialization |
| Julia | Arrow.jl | Latest | Arrow IPC support |
| Julia | HTTP.jl | Latest | HTTP file server |
| Julia | UUIDs.jl | Latest | UUID generation |
| Node.js | nats | Latest | NATS client (TCP) |
| Node.js | node-fetch | Latest | HTTP file server |
| Browser | nats.ws | Latest | NATS client (WebSocket) |
| Browser | nats | Latest | NATS client (for bundling) |
| Python | nats-py | Latest | NATS client |
| Python | aiohttp | Latest | HTTP file server |
| Python | pyarrow | Latest | Arrow IPC support |
| Dart | nats | Latest | NATS client |
| Dart | http | Latest | HTTP file server |
| Dart | uuid | Latest | UUID generation |
| Dart | dart-arrow | Latest | Arrow IPC support (Desktop/Flutter) |
| MicroPython | builtin | N/A | Limited implementation |
### Optional Dependencies
| Platform | Package | Purpose |
|----------|---------|---------|
| Julia | DataFrames.jl | DataFrame support |
| Python | pandas | DataFrame support |
---
## Change Log
| Date | Version | Changes |
|------|---------|---------|
| 2026-03-15 | 1.1.0 | Browser connection management |
| - | - | Added NATSClient class with keepAlive support |
| - | - | Added NATSConnectionPool for connection reuse |
| - | - | Added publishMessage function with closeConnection option |
| - | - | Added nats.ws to browser dependencies |
| 2026-03-13 | 1.0.0 | Initial specification |
| - | - | Message envelope schema defined |
| - | - | Payload schema with transport modes |
| - | - | Enumerations for payload_type, transport, encoding |
| - | - | Size thresholds for desktop/MicroPython |
| - | - | Error codes and validation rules |
| - | - | API contracts for all platforms |
---
## References
### 20.1 Documentation Artifacts
| Document | Purpose | Requirements Traceability |
|----------|---------|--------------------------|
| [`docs/requirements.md`](./requirements.md) | Business requirements and user stories | FR-001 through FR-014, NFR-101 through NFR-405 |
| [`docs/specification.md`](./specification.md) | Technical contract for NATSBridge | This document |
| [`docs/ui-specification.md`](./ui-specification.md) | UI specification for client applications | UI components for data entry and display |
| [`docs/walkthrough.md`](./walkthrough.md) | End-to-end system flow | Traceability from user journey to technical implementation |
| [`docs/architecture.md`](./architecture.md) | System architecture diagrams | Component interaction and data flow |
| [`docs/validation.md`](./validation.md) | CI/CD validation rules | Contract testing and spec compliance |
| [`docs/runbook.md`](./runbook.md) | Operational runbook | Deployment, scaling, and troubleshooting |
### 20.2 Implementation Files
| File | Platform | Features | Requirements Traceability |
|------|----------|----------|--------------------------|
| [`src/NATSBridge.jl`](../src/NATSBridge.jl) | Julia | Full feature set, Arrow IPC, multiple dispatch | FR-001 through FR-014, NFR-101 through NFR-405 |
| [`src/natsbridge_ssr.js`](../src/natsbridge_ssr.js) | Node.js | Arrow IPC, async/await | FR-001 through FR-014, NFR-101 through NFR-405 |
| [`src/natsbridge_csr.js`](../src/natsbridge_csr.js) | Browser | JSON table only, WebSocket NATS | FR-001 through FR-014, NFR-101 through NFR-405 |
| [`src/natsbridge.py`](../src/natsbridge.py) | Python | Arrow IPC, async/await | FR-001 through FR-014, NFR-101 through NFR-405 |
| [`src/natsbridge_mpy.py`](../src/natsbridge_mpy.py) | MicroPython | Limited to direct transport | FR-005, FR-006, FR-012 |
### 20.3 External Dependencies
| Platform | Package | Version | Purpose | Requirements Traceability |
|----------|---------|---------|---------|--------------------------|
| Julia | NATS.jl | Latest | NATS client | FR-013, FR-014, NFR-201 |
| Julia | JSON.jl | Latest | JSON serialization | FR-012, NFR-101, NFR-102 |
| Julia | Arrow.jl | Latest | Arrow IPC support | FR-002, FR-012 |
| Julia | HTTP.jl | Latest | HTTP file server | FR-008, FR-009 |
| Julia | UUIDs.jl | Latest | UUID generation | FR-011, NFR-401 |
| Node.js | nats | Latest | NATS client (TCP) | FR-013, FR-014 |
| Node.js | node-fetch | Latest | HTTP file server | FR-008, FR-009 |
| Browser | nats.ws | Latest | NATS client (WebSocket) | FR-013, FR-014 |
| Browser | nats | Latest | NATS client (for bundling) | FR-013, FR-014 |
| Python | nats-py | Latest | NATS client | FR-013, FR-014 |
| Python | aiohttp | Latest | HTTP file server | FR-008, FR-009 |
| Python | pyarrow | Latest | Arrow IPC support | FR-002, FR-012 |
| MicroPython | builtin | N/A | Limited implementation | FR-005, FR-006 |
---
## 21. Change Log
| Date | Version | Changes | Requirement ID(s) |
|------|---------|---------|-------------------|
| 2026-03-23 | 1.1.0 | Updated to ASG Framework specification guidelines | All |
| 2026-03-15 | 1.1.0 | Browser connection management | FR-001 through FR-014 |
| 2026-03-13 | 1.0.0 | Initial specification | FR-001 through FR-014, NFR-101 through NFR-405 |
---
## Appendix
### A. Complete JSON Schema
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "NATSBridge Envelope",
"type": "object",
"properties": {
"correlation_id": {
"type": "string",
"pattern": "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$",
"description": "UUID v4 format for tracking message flow"
},
"msg_id": {
"type": "string",
"pattern": "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$",
"description": "Unique message identifier"
},
"timestamp": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$",
"description": "ISO 8601 UTC timestamp"
},
"send_to": {
"type": "string",
"minLength": 1,
"description": "NATS subject to publish to"
},
"msg_purpose": {
"type": "string",
"enum": ["ACK", "NACK", "updateStatus", "shutdown", "chat", "command", "event"],
"description": "Purpose of the message"
},
"sender_name": {
"type": "string",
"minLength": 1,
"description": "Sender application name"
},
"sender_id": {
"type": "string",
"pattern": "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$",
"description": "Sender UUID"
},
"receiver_name": {
"type": "string",
"description": "Receiver name (empty = broadcast)"
},
"receiver_id": {
"type": "string",
"pattern": "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$|^$",
"description": "Receiver UUID (empty = broadcast)"
},
"reply_to": {
"type": "string",
"description": "Topic for reply messages"
},
"reply_to_msg_id": {
"type": "string",
"description": "Message ID being replied to"
},
"broker_url": {
"type": "string",
"pattern": "^nats://[^\\s]+$",
"description": "NATS broker URL"
},
"metadata": {
"type": "object",
"description": "Message-level metadata"
},
"payloads": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/Payload"
}
}
},
"required": ["correlation_id", "msg_id", "timestamp", "send_to", "msg_purpose", "sender_name", "sender_id", "receiver_name", "receiver_id", "reply_to", "reply_to_msg_id", "broker_url", "payloads"],
"definitions": {
"Payload": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$"
},
"dataname": {
"type": "string",
"minLength": 1
},
"payload_type": {
"type": "string",
"enum": ["text", "dictionary", "arrowtable", "jsontable", "image", "audio", "video", "binary"]
},
"transport": {
"type": "string",
"enum": ["direct", "link"]
},
"encoding": {
"type": "string",
"enum": ["none", "base64", "json", "arrow-ipc"]
},
"size": {
"type": "integer",
"minimum": 1
},
"data": {
"anyOf": [
{
"type": "string",
"pattern": "^(https?://[^\\s]+)$"
},
{
"type": "string",
"pattern": "^[A-Za-z0-9+/]+=*$"
}
]
},
"metadata": {
"type": "object"
}
},
"required": ["id", "dataname", "payload_type", "transport", "encoding", "size", "data"]
}
}
}
```
### B. AsyncAPI Specification (NATS)
```yaml
asyncapi: '2.6.0'
info:
title: NATSBridge API
version: '1.0.0'
description: Cross-platform bi-directional data bridge using NATS
contact:
name: NATSBridge Team
url: https://github.com/your-org/NATSBridge
license:
name: MIT
url: https://opensource.org/licenses/MIT
channels:
/agent/{service}/api/v{version}/{operation}:
address: /agent/{service}/api/v{version}/{operation}
parameters:
service:
schema:
type: string
version:
schema:
type: string
enum: ['v1']
operation:
schema:
type: string
publish:
summary: Publish message to NATS
operationId: publishMessage
message:
$ref: '#/components/message'
subscribe:
summary: Subscribe to NATS messages
operationId: subscribeMessage
message:
$ref: '#/components/message'
components:
message:
payload:
$ref: '#/components/schemas/Envelope'
schemas:
Envelope:
type: object
properties:
correlation_id:
type: string
format: uuid
msg_id:
type: string
format: uuid
timestamp:
type: string
format: date-time
send_to:
type: string
msg_purpose:
type: string
enum: [ACK, NACK, updateStatus, shutdown, chat, command, event]
sender_name:
type: string
sender_id:
type: string
format: uuid
receiver_name:
type: string
receiver_id:
type: string
format: uuid
reply_to:
type: string
reply_to_msg_id:
type: string
broker_url:
type: string
metadata:
type: object
payloads:
type: array
items:
$ref: '#/components/schemas/Payload'
required:
- correlation_id
- msg_id
- timestamp
- send_to
- msg_purpose
- sender_name
- sender_id
- receiver_name
- receiver_id
- reply_to
- reply_to_msg_id
- broker_url
- payloads
Payload:
type: object
properties:
id:
type: string
format: uuid
dataname:
type: string
payload_type:
type: string
enum: [text, dictionary, arrowtable, jsontable, image, audio, video, binary]
transport:
type: string
enum: [direct, link]
encoding:
type: string
enum: [none, base64, json, arrow-ipc]
size:
type: integer
minimum: 1
data:
type: string
metadata:
type: object
required:
- id
- dataname
- payload_type
- transport
- encoding
- size
- data
```
---
*This specification is versioned and maintained in git alongside the codebase. All implementations must adhere to this specification.*