remove demo app md

This commit is contained in:
2026-07-27 10:44:50 +00:00
parent d29258e739
commit c4b2ffad9c
-267
View File
@@ -1,267 +0,0 @@
# Demo App Transport Migration
This guide describes the client-facing protocol for the route-agnostic transport
architecture.
## Connection Model
HTTP, SSE, and WebSocket do not authenticate at the transport layer. Do not send
request-auth headers such as `X-PublicKey`, `X-Signature`, or `X-Timestamp`
for normal route dispatch.
Authentication is scoped to each resource operation. Every written resource
carries its own `publicKey`, `timestamp`, and `signature`.
All route input belongs in the request body. Paths are exact route names; there
are no path parameters, query parameters, or alternate HTTP methods.
## Request Size Limit
The server accepts at most 1048576 encoded bytes per HTTP request body or
complete WebSocket message by default. Deployments can change this with
`SERVER_MAX_REQUEST_BODY_BYTES`. The count is over the Extended JSON wire text,
so hexadecimal `Uint8Array` values use approximately twice their decoded byte
length.
HTTP limits use Hono's built-in `bodyLimit` middleware and return `413`.
WebSocket messages do not pass through HTTP middleware; Hono's Node adapter uses
`ws`, whose native `maxPayload` option rejects oversized frames before the
application message handler. Response bodies are not limited.
## HTTP Requests
All application HTTP routes use POST.
Read:
```http
POST /data/get
Content-Type: application/json
{
"resourceId": ["resource-a", "resource-b"]
}
```
Write:
```http
POST /data/write
Content-Type: application/json
{
"resources": [
{
"id": "resource-a",
"publicKey": "...hex...",
"timestamp": 1730000000000,
"signature": "...der-hex...",
"value": "<Uint8Array: deadbeef>"
}
]
}
```
Write response:
```json
{
"resources": [
{
"id": "resource-a",
"publicKey": "...hex...",
"blob": "<Uint8Array: deadbeef>",
"timestamp": 1730000001000
}
]
}
```
If any item has invalid authorization, the whole batch fails.
## SSE Subscriptions
The client explicitly requests SSE using `Accept: text/event-stream`:
```http
POST /data/subscribe
Accept: text/event-stream
Content-Type: application/json
{
"resourceId": ["resource-a"]
}
```
A subscription without this Accept header receives a `406` JSON error.
The server keeps the SSE request open while its resource topics remain
subscribed. Subscribing sends no current state; it only enables future
publications. Request current state separately through `/data/get` when needed.
SSE publication:
```text
id: 1730000001000
event: instance-changed
data: {"resourceId":"resource-a"}
```
After SSE begins, failures arrive as error events and the server closes that SSE
connection:
```text
event: error
data: {"statusCode":500,"error":"Internal Server Error"}
```
To change an SSE subscription, abort the existing request and open a new
`/data/subscribe` request with the complete desired `resourceId` list.
## WebSocket
Connect without a connection-auth message:
```ts
const ws = new WebSocket("ws://host:port/ws");
```
Every client message uses this strict envelope. Messages may execute
concurrently; the server binds `id` and `body` to each individual dispatch so
responses remain correctly correlated even when they finish out of order:
```ts
type WsRequest = {
id?: string;
path: string;
body?: unknown;
};
```
Old fields such as `event`, `type`, `url`, `method`, `headers`,
`params`, and `data` are rejected.
Read:
```json
{
"id": "read-1",
"path": "/data/get",
"body": {
"resourceId": ["resource-a", "resource-b"]
}
}
```
Write:
```json
{
"id": "write-1",
"path": "/data/write",
"body": {
"resources": [
{
"id": "resource-a",
"publicKey": "...hex...",
"timestamp": 1730000000000,
"signature": "...der-hex...",
"value": "<Uint8Array: deadbeef>"
}
]
}
}
```
Subscribe:
```json
{
"id": "sub-1",
"path": "/data/subscribe",
"body": {
"resourceId": ["resource-a"]
}
}
```
The subscribe request sends no immediate message. Its dispatch remains active
until those topics are removed or the connection closes. Request current state
separately through `/data/get` when needed.
The client should consolidate its local subscribers and send only the topic set
needed by the shared WebSocket.
Unsubscribe selected resources without closing the socket:
```json
{
"id": "unsub-1",
"path": "/data/unsubscribe",
"body": {
"resourceId": ["resource-a"]
}
}
```
Unsubscribe is idempotent. The response is a normal correlated response:
```json
{
"id": "unsub-1",
"type": "response",
"statusCode": 200,
"body": {}
}
```
Normal response:
```json
{
"id": "write-1",
"type": "response",
"statusCode": 200,
"body": {}
}
```
Error response:
```json
{
"id": "write-1",
"type": "error",
"statusCode": 400,
"error": "Validation Error",
"details": []
}
```
Message-level errors do not close the WebSocket. The client decides whether to
retry, alter its subscription, or reconnect.
Published events retain the shared stream shape:
```json
{
"id": "server-event-id",
"type": "instance-changed",
"data": {}
}
```
## Signing
For each written resource, sign this canonical payload:
```ts
`${timestamp}${resourceId}${canonicalBody(value)}`;
```
`value` is the `Uint8Array` payload. `canonicalBody(value)` uses the same
Extended JSON rules as the server:
```json
"<Uint8Array: deadbeef>"
```