Files
msghandler/README.md
2026-05-24 08:57:14 +07:00

685 lines
19 KiB
Markdown

# msghandler - Cross-Platform Communication Layer
A high-performance, **transport-agnostic** communication layer for both **Julia** (backend) and **JavaScript** (CSR webapp) applications. Implements the Claim-Check pattern for efficient payload transport (direct for small payloads, URL-based for large payloads).
- 🟢 **Julia Backend**: Full-featured implementation with Arrow IPC support
- 🔵 **JavaScript Frontend**: Browser-compatible implementation for CSR webapps
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
---
## Table of Contents
- [Quick Start](#quick-start)
- [Julia Backend](#julia-backend)
- [JavaScript CSR Webapp](#javascript-csr-webapp)
- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [API Reference](#api-reference)
- [Examples](#examples)
- [Testing](#testing)
- [Documentation](#documentation)
- [License](#license)
---
## Quick Data Format Reference
### Input Format for `smartpack()`
**Format:** `[(dataname, data, type), ...]`
| Element | Type | Description |
|---------|------|-------------|
| `dataname` | `String` | Name for the payload (e.g., `"message"`, `"config.json"`) |
| `data` | `Any` | The actual data (type depends on the `type` parameter below) |
| `type` | `String` | Payload type identifier |
### Supported Payload Types
| Type | Julia Data | Description |
|------|------------|-------------|
| `"text"` | `String` | Plain text |
| `"dictionary"` | `Dict` | JSON object |
| `"arrowtable"` | `DataFrame`, `Arrow.Table` | Arrow IPC table |
| `"jsontable"` | `DataFrame`, `Vector{Dict}`, `Vector{NamedTuple}` | JSON table |
| `"image"` | `Vector{UInt8}` | Image data |
| `"audio"` | `Vector{UInt8}` | Audio data |
| `"video"` | `Vector{UInt8}` | Video data |
| `"binary"` | `Vector{UInt8}`, `IOBuffer` | Binary data |
### Examples
**Sending multiple payloads:**
```julia
data = [
("message", "Hello!", "text"),
("config", Dict("key" => "value"), "dictionary"),
("file", file_bytes, "binary")
]
env, json_str = msghandler.smartpack("subject", data, options...)
```
**Important Notes:**
- Always wrap payloads in a list `[]`, even for single payloads
- Type must be one of the supported types above
- Large payloads (≥500KB) automatically use link transport
---
## Overview
msghandler provides a **transport-agnostic** communication layer with intelligent payload transport selection (same API for Julia and JavaScript):
| Transport | Payload Size | Method |
|-----------|--------------|--------|
| **Direct** | < 500KB | Sent directly via chosen transport (Base64 encoded) |
| **Link** | ≥ 500KB | Uploaded to HTTP file server, URL sent via chosen transport |
### Use Cases
- **Chat Applications**: Text, images, audio, video in a single message
- **File Transfer**: Efficient transfer of large files using claim-check pattern
- **Cross-Platform Communication**: Interoperability between Julia backend and JavaScript frontend
- **Any Transport**: Works with NATS, HTTP, WebSockets, WebRTC, or any custom transport mechanism
### Platform Differences
| Feature | Julia Backend | JavaScript Frontend |
|---------|---------------|---------------------|
| **Arrow IPC** | ✅ Supported | ❌ Not supported (use "jsontable" instead) |
| **Full Type Support** | ✅ All 8 types | ✅ 7 of 8 types |
| **Transport Support** | ✅ NATS, HTTP, WebSockets, etc. | ✅ Fetch, WebSockets, HTTP |
| **File Server** | ✅ Plik, AWS S3, custom | ✅ Plik, custom HTTP server |
### Supported Payload Types
| Type | Julia | JavaScript | Description |
|------|-------|------------|-------------|
| `"text"` | ✅ | ✅ | Plain text |
| `"dictionary"` | ✅ | ✅ | JSON object |
| `"arrowtable"` | ✅ | ❌ | Arrow IPC table (Julia only) |
| `"jsontable"` | ✅ | ✅ | JSON table |
| `"image"` | ✅ | ✅ | Image data |
| `"audio"` | ✅ | ✅ | Audio data |
| `"video"` | ✅ | ✅ | Video data |
| `"binary"` | ✅ | ✅ | Binary data |
### Key Design Principles
- **Transport Agnostic**: Core API (`smartpack`/`smartunpack`) is decoupled from transport
- **Claim-Check Pattern**: Efficient handling of large payloads via URL references
- **Rich Type System**: Support for Arrow IPC, JSON tables, and binary data
- **Handler Abstraction**: Pluggable file server implementations (Plik, AWS S3, custom)
---
## Features
-**Cross-platform** - Same API for Julia backend and JavaScript frontend
-**Transport-agnostic** - Core API works with any communication channel (NATS, HTTP, WebSockets, etc.)
-**Bi-directional** - Request-reply patterns with `reply_to` support
-**Multi-payload** - Send multiple payloads of different types in one message
-**Automatic transport selection** - Direct vs link based on payload size (threshold: 500KB)
-**Claim-Check pattern** - Efficient large payload handling via URL references
-**Rich payload types** - Text, dictionary, Arrow IPC (Julia only), JSON table, image, audio, video, binary
-**Exponential backoff** - Reliable file server downloads with retry logic
-**Correlation ID** - End-to-end message tracing
-**Handler abstraction** - Pluggable file server implementations (Plik, AWS S3, custom)
---
## Installation
### Julia Backend
```julia
using Pkg
Pkg.add("msghandler")
```
Add to your `Project.toml`:
```toml
[deps]
msghandler = "path-to-msghandler"
```
### JavaScript Frontend
**Using NPM:**
```bash
npm install msghandler-csr
```
**Using local source (development):**
```bash
cp ./src/msghandler-csr.js ./public/js/msghandler.js
```
Then import in your browser:
```javascript
import msghandlerCSR from './js/msghandler.js';
```
---
## Quick Start
### Julia Backend
#### Installation
```julia
using Pkg
Pkg.add("msghandler")
```
Add to your `Project.toml`:
```toml
[deps]
msghandler = "path-to-msghandler"
```
#### Quick Start
```julia
using msghandler
# Data format: [(dataname, data, type), ...]
payload_1 = ("test_message", "Hello World", "text")
file_path_large_image = "./test/large_image.png"
file_data_large_image = read(file_path_large_image)
filename_large_image = basename(file_path_large_image)
payload_2 = (filename_large_image, file_data_large_image, "binary")
payloads = [payload_1, payload_2] # List of tuples
# Step 1: Create the message envelope (transport-agnostic)
envelope, envelope_json_str = msghandler.smartpack("test.topic",
payloads;
broker_url="nats.yiem.cc",
fileserver_url="http://192.168.88.104:8080")
# Step 2: Send via your chosen transport (NATS in this example)
using NATS
conn = NATS.connect("nats.yiem.cc")
NATS.publish(conn, "test.topic", envelope_json_str; reply_to="test.replytopic")
NATS.drain(conn)
# OR using request-reply pattern
reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10)
```
#### Receive Messages
```julia
using msghandler, NATS
conn = NATS.connect("nats.yiem.cc")
NATS.subscribe(conn, "test.topic") do msg
println("Received message on $(msg.subject)")
envelope_json_str = String(msg.payload)
# Step 2: Unpack the envelope (transport-agnostic)
envelope = msghandler.smartunpack(
envelope_json_str;
max_retries = 5,
base_delay = 100,
max_delay = 5000
)
println(envelope.payloads[1])
end
```
### JavaScript CSR Webapp
#### Installation
**Using NPM:**
```bash
npm install msghandler-csr
```
**Using ES Modules (browser):**
```javascript
import msghandlerCSR from './src/msghandler-csr.js';
```
#### Features
- ✅ No build tools required (works directly in browser)
- ✅ Uses native browser APIs (Web Crypto, Fetch, TextEncoder/Decoder)
- ✅ Browser-compatible payload types (no Arrow IPC dependency)
#### Quick Start
```javascript
import msghandlerCSR from './src/msghandler-csr.js';
const { smartpack, smartunpack, plikOneshotUpload, fetchWithBackoff } = msghandlerCSR;
// Data format: [[dataname, data, type], ...]
const payload1 = ["test_message", "Hello World", "text"];
const payload2 = ["config_data", { key: "value" }, "dictionary"];
const payloads = [payload1, payload2]; // Array of arrays
// Step 1: Create the message envelope (transport-agnostic)
const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("test.topic", payloads, {
broker_url: "nats.yiem.cc",
fileserver_url: "http://192.168.88.104:8080"
});
// Step 2: Send via your chosen transport (NATS, WebSocket, HTTP, etc.)
// await myTransport.publish("test.topic", envelopeJsonStr);
// OR using fetch API for HTTP transport
fetch("http://localhost:3000/api/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: envelopeJsonStr
});
```
#### Receive Messages
```javascript
import msghandlerCSR from './src/msghandler-csr.js';
const { smartunpack } = msghandlerCSR;
// Option 1: Direct JSON string
const envelope = await msghandlerCSR.smartunpack(jsonString, {
fileserver_download_handler: msghandlerCSR.fetchWithBackoff,
max_retries: 5,
base_delay: 100,
max_delay: 5000
});
// Option 2: From transport message object (e.g., NATS, WebSocket)
const envelope = await msghandlerCSR.smartunpack(natsMessage, {
fileserver_download_handler: msghandlerCSR.fetchWithBackoff
});
// Process payloads
for (const [dataname, data, type] of envelope.payloads) {
console.log(`${dataname}:`, data, `(type: ${type})`);
}
```
### Prerequisites
1. **Transport Channel** - Choose your communication channel:
- **NATS** (recommended): `docker run -p 4222:4222 nats:latest`
- **HTTP/WebSocket**: Any HTTP server or WebSocket endpoint
- **Custom**: Implement your own transport mechanism
2. **HTTP File Server** (optional, for payloads ≥ 500KB) - Install and run:
```bash
# Using Plik
docker run -p 8080:8080 -v /tmp/fileserver:/var/lib/plik -e PLIK_ADMIN_PASSWORD=admin plik/plik
# OR using Python HTTP server
mkdir -p /tmp/fileserver
python3 -m http.server 8080 --directory /tmp/fileserver
```
### Send Your First Message
```julia
using msghandler, NATS
# Data format: [(dataname, data, type), ...]
payload_1 = ("test_message", "Hello World", "text")
file_path_large_image = "./test/large_image.png"
file_data_large_image = read(file_path_large_image)
filename_large_image = basename(file_path_large_image)
payload_2 = (filename_large_image, file_data_large_image, "binary")
payloads = [payload_1, payload_2] # List of tuples
# Step 1: Create the message envelope (transport-agnostic)
envelope, envelope_json_str = msghandler.smartpack("test.topic",
payloads;
broker_url="nats.yiem.cc",
fileserver_url="http://192.168.88.104:8080")
# Step 2: Send via your chosen transport (NATS in this example)
conn = NATS.connect("nats.yiem.cc")
NATS.publish(conn, "test.topic", envelope_json_str; reply_to="test.replytopic")
NATS.drain(conn)
# OR using request-reply pattern
reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10)
```
### Receive Your First Message
```julia
using msghandler, NATS
conn = NATS.connect("nats.yiem.cc")
NATS.subscribe(conn, "test.topic") do msg
println("Received message on $(msg.subject)")
envelope_json_str = String(msg.payload)
# Step 2: Unpack the envelope (transport-agnostic)
envelope = msghandler.smartunpack(
envelope_json_str;
max_retries = 5,
base_delay = 100,
max_delay = 5000
)
println(envelope.payloads[1])
end
```
---
## API Reference
### smartpack
Sends data via your chosen transport mechanism with intelligent transport selection.
```julia
using msghandler
env, env_json_str = msghandler.smartpack(
subject::String,
data::AbstractArray{Tuple{String, Any, String}};
broker_url::String = "nats://localhost:4222",
fileserver_url = "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 = "msghandler",
receiver_name::String = "",
receiver_id::String = "",
reply_to::String = "",
reply_to_msg_id::String = "",
msg_id::String = string(uuid4()),
sender_id::String = string(uuid4())
)
# Returns: ::Tuple{msg_envelope_v1, String}
```
### smartunpack
Receives and processes messages from your transport.
```julia
using msghandler
env = msghandler.smartunpack(
msg_json_str::String;
fileserver_download_handler::Function = _fetch_with_backoff,
max_retries::Int = 5,
base_delay::Int = 100,
max_delay::Int = 5000
)
# Returns: ::JSON.Object{String, Any}
```
---
## Examples
### Example 1: Chat with Mixed Content
Send text, image, and large file in one message.
```julia
using msghandler
data = [
("message_text", "Hello!", "text"),
("user_avatar", image_data, "image"),
("large_document", large_file_data, "binary")
]
env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080")
```
### Example 2: Dictionary Exchange
Send configuration data.
```julia
using msghandler
config = Dict(
"wifi_ssid" => "MyNetwork",
"wifi_password" => "password123",
"update_interval" => 60
)
data = [("config", config, "dictionary")]
env, env_json_str = smartpack("/device/config", data)
```
### Example 3: Table Data (Arrow IPC - Julia Only)
Send tabular data using Apache Arrow IPC format.
```julia
using msghandler
using DataFrames
df = DataFrame(
id = [1, 2, 3],
name = ["Alice", "Bob", "Charlie"],
score = [95, 88, 92]
)
data = [("students", df, "arrowtable")]
env, env_json_str = smartpack("/data/analysis", data)
```
### Example 3b: Table Data (JSON - JavaScript Compatible)
For JavaScript frontend, use `jsontable` instead of `arrowtable`:
```javascript
const tableData = [
{ id: 1, name: "Alice", score: 95 },
{ id: 2, name: "Bob", score: 88 },
{ id: 3, name: "Charlie", score: 92 }
];
const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysis", [
["students", tableData, "jsontable"]
]);
```
### Example 4: Request-Response Pattern
Bi-directional communication with reply-to support.
```julia
using msghandler, NATS
# Requester
env, env_json_str = msghandler.smartpack(
"/device/command",
[("command", Dict("action" => "read_sensor"), "dictionary")];
broker_url="nats://localhost:4222",
reply_to="/device/response"
)
conn = NATS.connect("nats://localhost:4222")
NATS.publish(conn, "/device/command", env_json_str)
NATS.drain(conn)
# Receiver (in separate application)
conn = NATS.connect("nats://localhost:4222")
NATS.subscribe(conn, "/device/command") do msg
env = msghandler.smartunpack(msg)
println("Received command: ", env["payloads"])
result = Dict("value" => 42)
response_env, response_json = msghandler.smartpack(
"/device/response",
[("result", result, "dictionary")],
reply_to="/device/command",
reply_to_msg_id=env["msg_id"]
)
NATS.publish(conn, "/device/response", response_json)
NATS.drain(conn)
end
```
---
## Testing
### Julia Backend
```bash
# Text message exchange
julia test/test_julia_text_sender.jl
julia test/test_julia_text_receiver.jl
# Dictionary exchange
julia test/test_julia_dict_sender.jl
julia test/test_julia_dict_receiver.jl
# File transfer
julia test/test_julia_file_sender.jl
julia test/test_julia_file_receiver.jl
# Mixed payload types
julia test/test_julia_mix_payloads_sender.jl
julia test/test_julia_mix_payloads_receiver.jl
# Table exchange
julia test/test_julia_table_sender.jl
julia test/test_julia_table_receiver.jl
```
### JavaScript Frontend
To test the JavaScript version in a browser:
1. Start a file server for testing:
```bash
mkdir -p /tmp/fileserver
python3 -m http.server 8080 --directory /tmp/fileserver
```
2. Start a NATS server (optional):
```bash
docker run -p 4222:4222 nats:latest
```
3. Open the test HTML file in a browser:
```bash
# Create test file (example)
cat > test-browser.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>msghandler CSR Test</title></head>
<body>
<script type="module">
import msghandlerCSR from './src/msghandler-csr.js';
// Test smartpack
const [env, json] = await msghandlerCSR.smartpack("/test", [
["msg", "Hello Browser!", "text"]
]);
console.log("Sent:", json);
</script>
</body>
</html>
EOF
python3 -m http.server 8081
# Open http://localhost:8081/test-browser.html
```
---
## Documentation
For detailed architecture and implementation information, see:
- [`docs/architecture.md`](docs/architecture.md) - Architecture and design patterns
- [`docs/requirements.md`](docs/requirements.md) - Business requirements and user stories
- [`docs/spec.md`](docs/spec.md) - Technical specification and contracts
- [`docs/walkthrough.md`](docs/walkthrough.md) - Real-world application building guides
### JavaScript API Reference
The JavaScript version provides the same core functionality as Julia, with browser-compatible APIs:
#### smartpack
```javascript
const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack(
subject,
data, // [[dataname, data, type], ...]
options
);
```
#### smartunpack
```javascript
const envelope = await msghandlerCSR.smartunpack(msg, options);
// envelope.payloads = [[dataname, data, type], ...]
```
#### plikOneshotUpload
```javascript
const result = await msghandlerCSR.plikOneshotUpload(
fileServerUrl,
dataname,
data // Uint8Array
);
// result = { status, uploadid, fileid, url }
```
#### fetchWithBackoff
```javascript
const data = await msghandlerCSR.fetchWithBackoff(
url,
maxRetries,
baseDelay,
maxDelay,
correlationId
);
// data = Uint8Array
```
---
## License
MIT License
Copyright (c) 2026 msghandler Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.