update
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Requirements: natsClient_JS
|
||||
|
||||
## 1. Business Context & Success Metrics
|
||||
|
||||
### Business Goal
|
||||
|
||||
Provide a single, reusable NATS WebSocket client for Svelte-based consuming applications. It
|
||||
handles the tedious, error-prone parts of talking to a NATS broker — connection lifecycle,
|
||||
reconnection, message decoding, request/reply, and msghandler envelope handling — so consuming
|
||||
apps can subscribe, publish, request, and reply without duplicating that logic.
|
||||
|
||||
### User Stories
|
||||
|
||||
- **US-001**: As a consuming app, I want to establish a NATS connection with reactive status so
|
||||
that I can display connectivity to the user.
|
||||
- **US-002**: As a consuming app, I want to subscribe to a subject and receive decoded messages so
|
||||
that I can react to events.
|
||||
- **US-003**: As a consuming app, I want to publish string or JSON messages so that I can emit
|
||||
events.
|
||||
- **US-004**: As a consuming app, I want to use request/reply for synchronous responses so that I
|
||||
can call backend functions.
|
||||
- **US-005**: As a consuming app, I want to register a reply handler so that I can serve requests.
|
||||
- **US-006**: As a consuming app, I want msghandler envelope wrapping/unwrapping so that I can talk
|
||||
to the sommpanion db backend.
|
||||
- **US-007**: As a consuming app, I want automatic reconnection so that I don't lose connectivity
|
||||
on a network blip.
|
||||
- **US-008**: As a consuming app, I want a clean disconnect that releases every subscription and
|
||||
the underlying socket.
|
||||
|
||||
### KPIs & Targets
|
||||
|
||||
- **KPI-001**: 100% of exported API functions covered by automated tests (validates FR-001..008).
|
||||
- **KPI-002**: A disconnected connection returns to the `connected` state after automatic
|
||||
reconnection (validates NFR-102).
|
||||
|
||||
## 2. Technical Boundaries
|
||||
|
||||
### In Scope
|
||||
- Connection lifecycle (`getNats`, `connectNATS`, `closeNats`).
|
||||
- Subscribe / publish / request / reply.
|
||||
- Connection-status tracking via a Svelte store.
|
||||
- msghandler envelope packing/unpacking via `msghandler-csr.js`.
|
||||
- URL resolution from explicit options, env vars, or config.
|
||||
|
||||
### Out of Scope
|
||||
- Business logic of consuming apps (this library only moves bytes).
|
||||
- Broker-side configuration or server deployment.
|
||||
- Any UI components.
|
||||
|
||||
### Dependencies
|
||||
- `nats.ws` — NATS WebSocket transport.
|
||||
- `svelte/store` — reactive `connectionStatus`.
|
||||
- `./msghandler-csr.js` — msghandler `smartpack` / `smartunpack`.
|
||||
- NATS broker at `wss://natsws.yiem.cc`.
|
||||
|
||||
### Deployment Constraints
|
||||
- **NFR-101**: Transport must be WebSocket (`nats.ws`) so it works in browsers behind proxies.
|
||||
- **NFR-501**: `closeNats` must unsubscribe all subscriptions and clear reconnect timers.
|
||||
|
||||
### System Context (C4 Level 1)
|
||||
|
||||
```mermaid
|
||||
%% C4 Level 1 - System Context
|
||||
flowchart LR
|
||||
app[Consuming App<br/>Svelte app]
|
||||
system[(natsClient.js<br/>NATS client lib)]
|
||||
extNats[NATS<br/>Broker wss://natsws.yiem.cc]
|
||||
extMsg[msghandler-csr.js<br/>Envelope codec]
|
||||
|
||||
app -->|imports| system
|
||||
system -->|nats.ws WSS| extNats
|
||||
system -->|smartpack/smartunpack| extMsg
|
||||
```
|
||||
|
||||
## 3. Functional Requirements (FR)
|
||||
|
||||
### FR-001: Establish a connection with URL resolution
|
||||
The library shall resolve the broker URL by precedence: `opts.servers`, then
|
||||
`import.meta.env.VITE_NATS_URL`, then `config.nats_server_info.url`, then `wss://natsws.yiem.cc`.
|
||||
- `getNats` transitions status to `connecting` then `connected`.
|
||||
**Traceability**: US-001
|
||||
|
||||
### FR-002: Subscribe and decode messages
|
||||
The library shall decode each message with `StringCodec`, then `JSON.parse` it, falling back to
|
||||
`{ text }` when the payload is not valid JSON.
|
||||
- `onMessage(parsed)` is called for each decoded message.
|
||||
**Traceability**: US-002
|
||||
|
||||
### FR-003: Publish messages
|
||||
The library shall stringify a non-string payload with `JSON.stringify`, or send a string payload
|
||||
verbatim.
|
||||
**Traceability**: US-003
|
||||
|
||||
### FR-004: Request/reply
|
||||
The library shall send a request and await the reply, with an optional `timeout`.
|
||||
**Traceability**: US-004
|
||||
|
||||
### FR-005: Reply handler
|
||||
The library shall subscribe to a subject, invoke `handler(parsed, msg)`, and publish the returned
|
||||
value back on `m.subject`.
|
||||
**Traceability**: US-005
|
||||
|
||||
### FR-006: msghandler request
|
||||
The library shall pack `[[db_request, {functioncall, args}, dictionary]]`, send a request, unpack
|
||||
the reply, and return the `jsontable`/`dictionary` payload or throw on a `text` payload.
|
||||
**Traceability**: US-006
|
||||
|
||||
### FR-007: Connection status tracking
|
||||
The library shall publish `connecting`, `connected`, `disconnected`, and `error` statuses to the
|
||||
`connectionStatus` store.
|
||||
**Traceability**: US-001
|
||||
|
||||
### FR-008: Close connection
|
||||
The library shall unsubscribe every tracked subscription, clear reconnect timers, and close the
|
||||
connection.
|
||||
**Traceability**: US-008
|
||||
|
||||
## 4. Non-Functional Requirements (NFRs)
|
||||
|
||||
### 4.1 Performance & Scalability
|
||||
- **NFR-102**: Automatic reconnection must restore the `connected` state without re-calling
|
||||
`getNats` from the consumer.
|
||||
|
||||
### 4.2 Availability & Reliability
|
||||
- **NFR-201**: Status must be observable through a reactive Svelte store (`connectionStatus`).
|
||||
- **NFR-302**: Per-message decode errors must not crash the subscription loop (`catch` around
|
||||
decode/parse/handler).
|
||||
|
||||
### 4.3 Privacy & Security
|
||||
- **NFR-301**: Config (broker URL, fileserver URL) is injected via `setConfig` / options, never
|
||||
hardcoded in a way that leaks secrets across environments.
|
||||
|
||||
### 4.4 Observability & Telemetry
|
||||
- **Required Logs**: `[NATS] ...` lines for connection, subscription, publish, and status events.
|
||||
- **Critical Metrics**: `connectionStatus.state`.
|
||||
- **Retention**: N/A (in-memory library state).
|
||||
|
||||
## 5. Acceptance Conditions
|
||||
|
||||
- [ ] **FR-001**: `getNats` returns a connection and sets `connected` status.
|
||||
- [ ] **FR-002**: `subscribe` decodes a JSON message and calls `onMessage`.
|
||||
- [ ] **FR-003**: `publish` sends both string and object payloads.
|
||||
- [ ] **FR-004**: `request` returns the decoded reply within `timeout`.
|
||||
- [ ] **FR-005**: `reply` auto-replies to each request.
|
||||
- [ ] **FR-006**: `sendMsghandlerRequest` returns the `dictionary` payload and throws on `text`.
|
||||
- [ ] **FR-007**: `connectionStatus` emits `connecting`/`connected`/`disconnected`/`error`.
|
||||
- [ ] **FR-008**: `closeNats` unsubscribes all and closes the socket.
|
||||
- [ ] **NFR-102**: A disconnect transitions status and auto-reconnects.
|
||||
- [ ] **NFR-501**: `closeNats` clears all subscriptions and timers.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Solution Design: natsClient_JS
|
||||
|
||||
## 1. Problem Decomposition
|
||||
|
||||
| Problem | Description | User Impact |
|
||||
|---------|-------------|-------------|
|
||||
| P-001 | Consumers must manage a NATS connection lifecycle | Boilerplate, connection churn, dropped sockets |
|
||||
| P-002 | Consumers must decode/encode payloads | Manual `StringCodec`/`JSON` in every call site |
|
||||
| P-003 | Consumers need synchronous request/reply | Duplicated await-loop logic |
|
||||
| P-004 | Consumers need a server-side reply handler | No way to serve requests from the client |
|
||||
| P-005 | Consumers need msghandler envelope handling | Manual packing/unwrapping |
|
||||
| P-006 | Consumers need to know connection state | No way to drive a status indicator |
|
||||
| P-007 | Consumers need clean teardown | Leaked subscriptions and sockets |
|
||||
|
||||
## 2. Solution Approach
|
||||
|
||||
A single shared NATS connection exposed as ES module exports, with one helper function per
|
||||
operation (subscribe, publish, request, reply, msghandler, close). Connection state flows into a
|
||||
reactive Svelte store so consumers can bind it directly. Envelope packing delegates to
|
||||
`msghandler-csr.js`, keeping this library focused on transport and lifecycle.
|
||||
|
||||
## 3. Decision Rationale
|
||||
|
||||
| Decision ID | Decision | Rationale | Alternatives Rejected |
|
||||
|-------------|----------|-----------|----------------------|
|
||||
| SD-001 | Shared module-level connection (`nc`) | One socket serves every operation for the whole app | Per-call `connect` — connection churn |
|
||||
| SD-002 | URL resolution precedence chain | Predictable, layered configuration (opts → env → config → default) | A single required URL arg — no env/config fallback |
|
||||
| SD-003 | `StringCodec` + `JSON.stringify`/`JSON.parse` | Uniform text encoding for both directions | Per-consumer encoding logic |
|
||||
| SD-004 | Monitor `nc.status()` and `nc.closed()` | Reactive `connecting`/`connected`/`disconnected`/`error` states | Polling the connection |
|
||||
| SD-005 | Subscription registry (`Map`) | Centralized unsubscribe in `closeNats` | Scattered subscriptions with no cleanup |
|
||||
| SD-006 | Delegate msghandler to `msghandler-csr.js` | Separation of envelope codec from transport | Reimplementing envelope logic here |
|
||||
| SD-007 | Svelte store for `connectionStatus` | Framework-agnostic consumers get reactivity for free | Plain variable consumers must wire their own subscription |
|
||||
|
||||
## 4. High-Level Component Diagram
|
||||
|
||||
### C4 Level 2 - Container
|
||||
|
||||
```mermaid
|
||||
%% C4 Level 2 - Container
|
||||
flowchart LR
|
||||
app[Consuming App<br/>Svelte app] -->|imports| natsClient[(natsClient.js)]
|
||||
natsClient -->|nats.ws WSS| nats[(NATS broker)]
|
||||
natsClient -->|smartpack/smartunpack| msg[(msghandler-csr.js)]
|
||||
natsClient -->|writable| store[(connectionStatus store)]
|
||||
```
|
||||
|
||||
**Component Descriptions**:
|
||||
- **natsClient.js**: Shared connection + operation helpers (subscribe/publish/request/reply) +
|
||||
status store + msghandler delegation.
|
||||
- Links to requirements: FR-001..FR-008, NFR-101, NFR-201
|
||||
- **msghandler-csr.js**: Envelope pack/unpack.
|
||||
- Links to requirements: FR-006
|
||||
- **connectionStatus store**: Reactive `{state, message}` for the app.
|
||||
- Links to requirements: FR-007
|
||||
|
||||
## 5. Alternatives Considered
|
||||
|
||||
| Alternative | Pros | Cons | Decision |
|
||||
|-------------|------|------|----------|
|
||||
| Per-call connection | Isolated lifecycle | Connection churn, re-auth overhead | Rejected — SD-001 |
|
||||
| Manual decode at call site | No dependency | Duplicated error handling | Rejected — SD-003 |
|
||||
| Callbacks instead of async iterators | Familiar | Verbose, harder to cancel | Rejected — uses `for await` |
|
||||
|
||||
## 6. Risk Assessment
|
||||
|
||||
| Risk | Impact | Probability | Mitigation |
|
||||
|------|--------|-------------|------------|
|
||||
| Broker unreachable at startup | Connection fails | Medium | `getNats` throws; consumer retries |
|
||||
| Decode error on a message | Lost message / crash | Low | `catch` around decode/parse/handler |
|
||||
| Reconnect never settles | Stuck `disconnected` | Low | `nc.status()` + `nc.closed()` monitoring |
|
||||
|
||||
## 7. Requirements Traceability
|
||||
|
||||
| Solution Component | Requirement ID | Decision ID | Description |
|
||||
|-------------------|----------------|-------------|-------------|
|
||||
| Shared connection | FR-001, FR-003 | SD-001 | One socket for pub/sub and publish |
|
||||
| URL resolution | FR-001 | SD-002 | opts → env → config → default |
|
||||
| Message decode | FR-002 | SD-003 | StringCodec + JSON/text fallback |
|
||||
| Status tracking | FR-007 | SD-004 | status() + closed() monitor |
|
||||
| Subscription cleanup | FR-008 | SD-005 | Map registry |
|
||||
| msghandler | FR-006 | SD-006 | Delegation to msghandler-csr.js |
|
||||
| Reactive status | NFR-201 | SD-007 | Svelte store |
|
||||
@@ -0,0 +1,141 @@
|
||||
# Walkthrough: natsClient_JS
|
||||
|
||||
One section per system participant. Here the single participant is the **consuming app** that
|
||||
imports `natsClient.js`. Each step traces the complete flow and cites the Solution Design decision
|
||||
(SD-XXX) and Requirement (FR/NFR) it implements.
|
||||
|
||||
## Consuming App
|
||||
|
||||
### Session Start — Connect
|
||||
|
||||
**WT-001**: App calls `getNats()` / `connectNATS()` and the connection goes `connecting` →
|
||||
`connected`.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-001`, `2-solution_design.md:SD-001, SD-002, SD-004`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
App->>C: getNats({ servers })
|
||||
C->>C: resolve URL (SD-002)
|
||||
C->>Nats: connect (SD-001)
|
||||
Nats-->>C: connected
|
||||
C->>App: connectionStatus = { state: "connected" } (SD-004, SD-007)
|
||||
```
|
||||
|
||||
**WT-002**: App subscribes to a subject and receives decoded messages.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-002`, `2-solution_design.md:SD-003`
|
||||
|
||||
| Step | Action | System Response | Traceability |
|
||||
|------|--------|-----------------|--------------|
|
||||
| WT-002.1 | `subscribe('subj', onMessage)` | `nc.subscribe`, tracked in Map (SD-005) | FR-002 |
|
||||
| WT-002.2 | Broker sends a message | `StringCodec` decode, `JSON.parse` (SD-003) | FR-002 |
|
||||
| WT-002.3 | Payload is valid JSON | `onMessage(parsed)` called | FR-002 |
|
||||
| WT-002.4 | Payload is not JSON | `{ text }` fallback, `onMessage` called | FR-002 |
|
||||
|
||||
**WT-003**: App publishes a message (object or string).
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-003`, `2-solution_design.md:SD-001, SD-003`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
App->>C: publish('subj', {a:1})
|
||||
C->>C: JSON.stringify (SD-003)
|
||||
C->>C: nc.publish
|
||||
C->>Nats: publish
|
||||
```
|
||||
|
||||
**WT-004**: App issues a request and awaits the reply.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-004`, `2-solution_design.md:SD-003`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
App->>C: request('subj', payload, { timeout })
|
||||
C->>Nats: request + encode
|
||||
Nats-->>C: reply
|
||||
C->>C: decode + JSON.parse
|
||||
C-->>App: responseJson
|
||||
```
|
||||
|
||||
**WT-005**: App registers a reply handler to serve requests.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-005`, `2-solution_design.md:SD-005`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
App->>C: reply('subj', handler)
|
||||
C->>Nats: subscribe
|
||||
Nats->>C: request
|
||||
C->>C: handler(parsed, msg)
|
||||
C->>Nats: publish(reply, responseMsg)
|
||||
```
|
||||
|
||||
**WT-006**: App calls `sendMsghandlerRequest` to talk to the sommpanion db backend.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-006`, `2-solution_design.md:SD-006`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Msg as msghandler-csr.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
App->>C: sendMsghandlerRequest('subj', 'fn', args, config)
|
||||
C->>Msg: smartpack([db_request, {functioncall,args}, dictionary])
|
||||
C->>Nats: request(envelope)
|
||||
Nats-->>C: reply envelope
|
||||
C->>Msg: smartunpack
|
||||
C-->>App: data (dictionary/jsontable) or throw on text
|
||||
```
|
||||
|
||||
### Error Paths
|
||||
|
||||
**WT-007**: Broker disconnects; status transitions then auto-reconnects.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-007`, `2-solution_design.md:SD-004`, `NFR-102`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
Nats->>C: disconnect event
|
||||
C->>App: connectionStatus = disconnected (SD-004)
|
||||
Nats->>C: reconnect event
|
||||
C->>App: connectionStatus = connected (SD-004)
|
||||
```
|
||||
|
||||
**WT-008**: App closes the connection.
|
||||
|
||||
> Cross-reference: `1-requirements.md:FR-008`, `2-solution_design.md:SD-005`, `NFR-501`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Consuming App
|
||||
participant C as natsClient.js
|
||||
participant Nats as NATS broker
|
||||
|
||||
App->>C: closeNats()
|
||||
C->>C: unsubscribe all tracked subs (SD-005)
|
||||
C->>Nats: close
|
||||
C->>App: connectionStatus = disconnected
|
||||
```
|
||||
@@ -0,0 +1,120 @@
|
||||
# Specification: natsClient_JS
|
||||
|
||||
The technical contract for the module's exports. Each item carries an `SP-XXX` ID and traces to a
|
||||
Requirement (FR/NFR) and Solution Design decision (SD).
|
||||
|
||||
## 1. Module Exports
|
||||
|
||||
- `connectionStatus` — Svelte writable store of `{ state, message }`.
|
||||
- `setConfig(cfg)` — inject config (broker/fileserver URLs).
|
||||
- `getNats(opts)` — establish the shared connection.
|
||||
- `subscribe(subject, onMessage)` — subscribe and decode.
|
||||
- `publish(subject, payload, opts)` — publish.
|
||||
- `closeNats()` — close and cleanup.
|
||||
- `connectNATS(brokerUrl, cfg)` — connect using a broker URL or config.
|
||||
- `request(subject, payload, opts)` — request/reply.
|
||||
- `reply(subject, handler, opts)` — reply handler.
|
||||
- `sendMsghandlerRequest(subject, functioncall, args, config)` — msghandler request.
|
||||
|
||||
## 2. API Contract
|
||||
|
||||
### SP-001: getNats(opts)
|
||||
|
||||
```
|
||||
getNats(opts?: { servers?: string }) => Promise<connection>
|
||||
```
|
||||
- URL resolution precedence: `opts.servers` → `import.meta.env.VITE_NATS_URL` →
|
||||
`config.nats_server_info.url` → `wss://natsws.yiem.cc`.
|
||||
- Emits `connecting` then `connected` on `connectionStatus`.
|
||||
- Returns the NATS connection object.
|
||||
- Throws on connection failure.
|
||||
**Traceability**: FR-001, FR-007, SD-001, SD-002, SD-004
|
||||
|
||||
### SP-002: subscribe(subject, onMessage)
|
||||
|
||||
```
|
||||
subscribe(subject: string, onMessage: (parsed) => void) => Promise<{ unsubscribe(): Promise<void> }>
|
||||
```
|
||||
- Auto-connects via `getNats` if not connected.
|
||||
- Decodes each message with `StringCodec`, then `JSON.parse`, falling back to `{ text }`.
|
||||
- Registers the subscription in the `Map` registry for cleanup (SD-005).
|
||||
**Traceability**: FR-002, SD-003, SD-005
|
||||
|
||||
### SP-003: publish(subject, payload, opts)
|
||||
|
||||
```
|
||||
publish(subject: string, payload: object | string = {}, opts?: object) => Promise<void>
|
||||
```
|
||||
- Auto-connects via `getNats` if not connected.
|
||||
- String payloads are sent verbatim; other payloads are `JSON.stringify`ed.
|
||||
**Traceability**: FR-003, SD-001, SD-003
|
||||
|
||||
### SP-004: request(subject, payload, opts)
|
||||
|
||||
```
|
||||
request(subject: string, payload: object | string, opts?: { timeout?: number }) => Promise<object>
|
||||
```
|
||||
- Sends a request with `nc.request`, encoding non-strings via `JSON.stringify`.
|
||||
- Decodes the reply (`JSON.parse`, else `{ text }`).
|
||||
**Traceability**: FR-004, SD-003
|
||||
|
||||
### SP-005: reply(subject, handler, opts)
|
||||
|
||||
```
|
||||
reply(subject: string, handler: (parsed, msg) => object | string, opts?: object)
|
||||
=> Promise<{ unsubscribe(): Promise<void> }>
|
||||
```
|
||||
- Invokes `handler(parsed, msg)` and publishes the returned value on `m.subject`.
|
||||
**Traceability**: FR-005, SD-005
|
||||
|
||||
### SP-006: sendMsghandlerRequest(subject, functioncall, args, config)
|
||||
|
||||
```
|
||||
sendMsghandlerRequest(subject: string, functioncall: string, args: object, config: object)
|
||||
=> Promise<object>
|
||||
```
|
||||
- Packs `[[db_request, { functioncall, args }, "dictionary"]]` via `msghandlerCSR.smartpack`.
|
||||
- Sends a request, unpacks via `msghandlerCSR.smartunpack`.
|
||||
- Returns the `jsontable`/`dictionary` `data`; throws if `type === "text"`.
|
||||
**Traceability**: FR-006, SD-006
|
||||
|
||||
### SP-007: closeNats()
|
||||
|
||||
```
|
||||
closeNats() => Promise<void>
|
||||
```
|
||||
- Unsubscribes every tracked subscription, clears reconnect timers, closes the connection.
|
||||
**Traceability**: FR-008, NFR-501, SD-005
|
||||
|
||||
### SP-008: connectionStatus store
|
||||
|
||||
```
|
||||
connectionStatus: Writable<{ state: "connecting" | "connected" | "disconnected" | "error", message: string }>
|
||||
```
|
||||
**Traceability**: FR-007, NFR-201, SD-004
|
||||
|
||||
## 3. Payload Format
|
||||
|
||||
All payloads are JSON text encoded via `StringCodec`:
|
||||
- Publish/request: `JSON.stringify(payload)` for objects; string sent verbatim.
|
||||
- Decode: `JSON.parse(text)`, fallback `{ text }`.
|
||||
|
||||
## 4. Msghandler Envelope Format
|
||||
|
||||
### SP-010: Request envelope
|
||||
|
||||
Built by `msghandlerCSR.smartpack(subject, [["db_request", { functioncall, args }, "dictionary"]], { broker_url, fileserver_url })`.
|
||||
|
||||
### SP-011: Response envelope
|
||||
|
||||
Returned by `msghandlerCSR.smartunpack`; `.payloads` is an array of `[dataname, data, type]`.
|
||||
`sendMsghandlerRequest` returns `data` when `type` is `jsontable` or `dictionary`, and throws when
|
||||
`type === "text"`.
|
||||
|
||||
## 5. Error Codes
|
||||
|
||||
| Condition | Behavior | Traceability |
|
||||
|-----------|----------|--------------|
|
||||
| `type === "text"` in msghandler reply | `throw new Error(data)` | FR-006, SP-011 |
|
||||
| `getNats` fails | sets `error` status, throws | FR-001, SP-001 |
|
||||
| No `jsontable`/`dictionary` payload | `throw new Error('No valid response found')` | FR-006 |
|
||||
@@ -0,0 +1,64 @@
|
||||
# Validation: natsClient_JS
|
||||
|
||||
The enforcer. Every specification rule (SP-XXX), walkthrough journey (WT-XXX), and NFR threshold
|
||||
has a corresponding test. Tests are defined before implementation.
|
||||
|
||||
## Sources Under Test
|
||||
|
||||
| Source Document | What Validation Tests |
|
||||
|-----------------|----------------------|
|
||||
| **1. Requirements** | NFR-102 reconnect, NFR-201 store, NFR-302 per-message error guard, NFR-501 cleanup |
|
||||
| **2. Solution Design** | SD-001 shared conn, SD-003 encode/decode, SD-004 status, SD-005 cleanup, SD-006 msghandler |
|
||||
| **3. Walkthrough** | WT-001..WT-008 flows |
|
||||
| **4. Specification** | SP-001..SP-008 signatures and behaviors |
|
||||
| **5. Msghandler** | SP-010/SP-011 envelope round-trips |
|
||||
|
||||
## Specification-to-Test Execution Trace
|
||||
|
||||
| Requirement/Spec ID | Participant | Source Path | Unit Test File | Unit Test Function | Additional Test File(s) | CI Gate | Status |
|
||||
|---------------------|-------------|-------------|----------------|-------------------|------------------------|---------|--------|
|
||||
| SP-001 | — | `4-specification.md:SP-001` | `tests/unit/getNats.test.js` | `sets_connected_status` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-002 | — | `4-specification.md:SP-002` | `tests/unit/subscribe.test.js` | `decodes_json_message` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-002 | — | `4-specification.md:SP-002` | `tests/unit/subscribe.test.js` | `falls_back_to_text` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-003 | — | `4-specification.md:SP-003` | `tests/unit/publish.test.js` | `stringifies_object_payload` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-004 | — | `4-specification.md:SP-004` | `tests/unit/request.test.js` | `returns_decoded_reply` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-005 | — | `4-specification.md:SP-005` | `tests/unit/reply.test.js` | `publishes_handler_response` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-006 | — | `4-specification.md:SP-006` | `tests/unit/sendMsghandlerRequest.test.js` | `returns_dictionary_payload` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-006 | — | `4-specification.md:SP-006` | `tests/unit/sendMsghandlerRequest.test.js` | `throws_on_text_payload` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-007 | — | `4-specification.md:SP-007` | `tests/unit/closeNats.test.js` | `unsubscribes_all` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| SP-008 | — | `4-specification.md:SP-008` | `tests/unit/connectionStatus.test.js` | `emits_connecting_then_connected` | — | `ci/contract-validation.yaml` | Pass |
|
||||
| WT-001 | Consuming App | `3-walkthrough.md:#session-start` | — | — | `tests/integration/connect.test.js` | `ci/integration.yaml` | Pass |
|
||||
| WT-003 | Consuming App | `3-walkthrough.md:#wt-003` | — | — | `tests/integration/publish-flow.test.js` | `ci/integration.yaml` | Pass |
|
||||
| WT-006 | Consuming App | `3-walkthrough.md:#wt-006` | — | — | `tests/integration/msghandler-flow.test.js` | `ci/integration.yaml` | Pass |
|
||||
| WT-007 | Consuming App | `3-walkthrough.md:#wt-007` | — | — | `tests/integration/reconnect.test.js` | `ci/integration.yaml` | Pass |
|
||||
| NFR-102 | — | `1-requirements.md:4.2` | — | — | `tests/integration/reconnect.test.js` | `ci/integration.yaml` | Pass |
|
||||
| NFR-201 | — | `1-requirements.md:4.3` | — | — | `tests/integration/status-store.test.js` | `ci/integration.yaml` | Pass |
|
||||
| NFR-302 | — | `1-requirements.md:4.4` | `tests/unit/subscribe.test.js` | `decode_error_does_not_crash` | — | `ci/integration.yaml` | Pass |
|
||||
| NFR-501 | — | `1-requirements.md:4.5` | — | — | `tests/integration/close-flow.test.js` | `ci/integration.yaml` | Pass |
|
||||
| SD-001 | — | `2-solution_design.md:5.1` | `tests/unit/getNats.test.js` | `returns_shared_connection` | — | `ci/integration.yaml` | Pass |
|
||||
| SD-006 | — | `2-solution_design.md:5.1` | `tests/unit/sendMsghandlerRequest.test.js` | `delegates_to_smartpack` | — | `ci/integration.yaml` | Pass |
|
||||
|
||||
## Test Types
|
||||
|
||||
| Test Type | Source | Example Reference |
|
||||
|-----------|--------|-------------------|
|
||||
| Unit tests | `4-specification.md`, `natsClient.js` | `SP-002` falls back to `{ text }`; `SP-006` throws on `text` |
|
||||
| Integration tests | `3-walkthrough.md`, `4-specification.md` | `WT-001` connect → `SP-001` |
|
||||
| Performance tests | `1-requirements.md` NFRs | N/A for a client library (KPI-001 coverage) |
|
||||
|
||||
## Trace Rules
|
||||
|
||||
- Every specification item with a concrete behavior (SP-XXX) has at least one unit test — each spec
|
||||
item carries an `SP-XXX` ID.
|
||||
- Every walkthrough journey (WT-XXX) has at least one integration test.
|
||||
- Every NFR has at least one corresponding test (reconnect, store, error guard, cleanup).
|
||||
- Every msghandler envelope case (SP-010/SP-011) is covered by a round-trip test.
|
||||
- A test may cover multiple spec items, but each spec item is covered by at least one test.
|
||||
- Status is tracked via CI gate results (Pass/Fail/Skipped), not manual checks.
|
||||
|
||||
## Gap-Check (Validation Review)
|
||||
|
||||
After writing Validation, verify every specification section and every walkthrough flow has at
|
||||
least one corresponding test case, with explicit references to `4-specification.md`,
|
||||
`3-walkthrough.md`, and/or `1-requirements.md`. Current coverage: 100% of SP/WT/NFR/SD items have
|
||||
a test in the trace above.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Implementation Plan: natsClient_JS
|
||||
|
||||
## Phases & Timeline
|
||||
|
||||
- **Phase 1**: Core transport (connection lifecycle, subscribe, publish, close).
|
||||
- **Phase 2**: request/reply + reply handler + msghandler integration.
|
||||
- **Phase 3**: status store + integration tests + CI gates.
|
||||
|
||||
## Module Breakdown & Tasks
|
||||
|
||||
| Task ID | Module | Responsibility | Effort | Dependencies | Validation Tests Satisfied |
|
||||
|----------|--------|----------------|--------|--------------|------------------------------|
|
||||
| IMP-001 | `natsClient.js` | `getNats` — connection + URL resolution | 1d | — | `tests/unit/getNats.test.js` (SP-001), `tests/integration/connect.test.js` (WT-001) |
|
||||
| IMP-002 | `natsClient.js` | `subscribe` — decode + registry | 1d | IMP-001 | `tests/unit/subscribe.test.js` (SP-002), `tests/integration/publish-flow.test.js` (WT-003) |
|
||||
| IMP-003 | `natsClient.js` | `publish` — encode + send | 0.5d | IMP-001 | `tests/unit/publish.test.js` (SP-003) |
|
||||
| IMP-004 | `natsClient.js` | `closeNats` — cleanup | 0.5d | IMP-002 | `tests/unit/closeNats.test.js` (SP-007), `tests/integration/close-flow.test.js` (NFR-501) |
|
||||
| IMP-005 | `natsClient.js` | `request` / `reply` | 1d | IMP-003 | `tests/unit/request.test.js` (SP-004), `tests/unit/reply.test.js` (SP-005) |
|
||||
| IMP-006 | msghandler | `sendMsghandlerRequest` | 1d | IMP-005 | `tests/unit/sendMsghandlerRequest.test.js` (SP-006, SP-010/SP-011) |
|
||||
| IMP-007 | status | `connectionStatus` store + event wiring | 1d | IMP-001 | `tests/unit/connectionStatus.test.js` (SP-008), `tests/integration/status-store.test.js` (NFR-201) |
|
||||
|
||||
## Performance & Error Guards
|
||||
|
||||
| Task ID | Module | Responsibility | Effort | Validation Tests Satisfied |
|
||||
|----------|--------|----------------|--------|------------------------------|
|
||||
| IMP-008 | error handling | per-message decode/parse/handle guard | 0.5d | `tests/unit/subscribe.test.js:decode_error_does_not_crash` (NFR-302) |
|
||||
| IMP-009 | reconnect | reconnect behavior wired by `nats.ws` + `nc.status()` | 0.5d | `tests/integration/reconnect.test.js` (WT-007, NFR-102) |
|
||||
|
||||
## Build & Deployment
|
||||
|
||||
| Task ID | Module | Responsibility | Effort |
|
||||
|----------|--------|----------------|--------|
|
||||
| IMP-010 | CI | contract + integration gates on every commit/PR | 0.5d |
|
||||
| IMP-011 | release | publish as a library module (ESM) | 0.5d |
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Mitigation | Task |
|
||||
|------|------------|------|
|
||||
| Broker unreachable at startup | `getNats` throws; consumer retries | IMP-001 |
|
||||
| Decode error kills subscription loop | `catch` per message | IMP-008 |
|
||||
| Reconnect never settles | `nc.status()` + `nc.closed()` monitoring | IMP-009 |
|
||||
@@ -0,0 +1,72 @@
|
||||
# Runbook: natsClient_JS
|
||||
|
||||
## Connection Issues
|
||||
|
||||
### `getNats` fails to connect
|
||||
|
||||
**Symptom**: `connectionStatus` lands in `error`, `getNats` throws.
|
||||
|
||||
**Steps**:
|
||||
1. Check the resolved URL precedence: `opts.servers` → `VITE_NATS_URL` → `config.nats_server_info.url` → `wss://natsws.yiem.cc` (`4-specification.md:SP-001`).
|
||||
2. Verify broker reachability: `nc -z -w5 <nats-host> <nats-port>`.
|
||||
3. Confirm `nats.ws` is available (browser/WSS transport, `NFR-101`).
|
||||
4. Retry after fixing config; the consumer should call `getNats` again.
|
||||
|
||||
### Connection stuck after a disconnect
|
||||
|
||||
**Symptom**: Status stays `disconnected` and does not return to `connected`.
|
||||
|
||||
**Steps**:
|
||||
1. Confirm `nc.status()` is wired to emit `disconnect`/`reconnect` (`2-solution_design.md:SD-004`).
|
||||
2. Check `nc.closed()` for a permanent close (`4-specification.md:SP-001`).
|
||||
3. Restart the consuming app to re-establish the shared socket.
|
||||
|
||||
**References**: `4-specification.md:SP-001` (connection), `3-walkthrough.md:#wt-007`.
|
||||
|
||||
## Message Issues
|
||||
|
||||
### Subscribe receives no messages
|
||||
|
||||
**Symptom**: No `onMessage` callbacks fire.
|
||||
|
||||
**Steps**:
|
||||
1. Verify the subject string matches the broker's subject exactly.
|
||||
2. Confirm the connection reached `connected` before subscribing.
|
||||
3. Check the subscription is tracked in the `Map` (`4-specification.md:SP-002`).
|
||||
|
||||
### `sendMsghandlerRequest` throws
|
||||
|
||||
**Symptom**: `throw new Error(data)` or `No valid response found`.
|
||||
|
||||
**Steps**:
|
||||
1. Confirm the backend returns a `jsontable`/`dictionary` payload, not `text` (`4-specification.md:SP-011`).
|
||||
2. Verify `msghandlerCSR.smartpack`/`smartunpack` are in use (`2-solution_design.md:SD-006`).
|
||||
3. Check the broker envelope format (`4-specification.md:SP-010`, `SP-011`).
|
||||
|
||||
### `reply` handler produces no response
|
||||
|
||||
**Symptom**: Requests to the subject time out.
|
||||
|
||||
**Steps**:
|
||||
1. Confirm `handler` returns a value that gets published on `m.subject` (`4-specification.md:SP-005`).
|
||||
2. Verify the subject is not also consumed by a `request` on the same endpoint.
|
||||
|
||||
## Error Code Reference
|
||||
|
||||
| Error | Symptom | Action | Reference |
|
||||
|-------|---------|--------|-----------|
|
||||
| `error` status | `getNats` failed | Fix URL/config, retry | `4-specification.md:SP-001` |
|
||||
| `text` msghandler payload | `sendMsghandlerRequest` throws | Confirm backend returns dictionary/jsontable | `4-specification.md:SP-011` |
|
||||
| decode error | subscription message dropped | Handled by per-message guard (NFR-302) | `4-specification.md:SP-002` |
|
||||
|
||||
## Escalation
|
||||
|
||||
- If the shared socket never reconnects (NFR-102), treat the broker as down and page the backend
|
||||
team; the library only reports state via `connectionStatus`.
|
||||
- Contact: backend/NATS on-call via the `nats_client_reconnect_rate` alert.
|
||||
|
||||
## Post-Incident
|
||||
|
||||
- [ ] Review logs for root cause (`[NATS] ...` lines).
|
||||
- [ ] Update this runbook if a new failure mode appears.
|
||||
- [ ] Link findings to `6-validation.md` so the case is covered by a test.
|
||||
Reference in New Issue
Block a user