@owlmeans/socket 0.1.18-rc.2 → 0.1.18-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ Shared WebSocket connection types and message protocol for OwlMeans real-time co
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/socket
15
+ bun add @owlmeans/socket@^0.1.18-rc.20
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -91,7 +91,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
91
91
  your project's skill store (`.agents/skills/`):
92
92
 
93
93
  ```sh
94
- npx @owlmeans/agent-skills
94
+ npx @owlmeans/agent-skills@^0.1.18-rc.24
95
95
  ```
96
96
 
97
97
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/socket",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.508Z",
4
+ "version": "0.1.18-rc.21",
5
+ "generatedAt": "2026-09-15T12:59:01.049Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: socket
3
- description: How to use @owlmeans/socket — abstract WebSocket protocol model, message types, and socket errors shared between client-socket and server-socket. Auto-invoked when importing socket types or message constants.
3
+ description: How to use @owlmeans/socket — the transport-agnostic Connection model shared by client-socket and server-socket, its message types (call/request/event/auth/system), the type guards, and the socket error classes. Auto-invoked when importing socket types, message constants or the connection model.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,29 +8,126 @@ user-invocable: false
8
8
  # @owlmeans/socket
9
9
 
10
10
  **Layer:** Core
11
- **Install:** `"@owlmeans/socket": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/socket": "^0.1.18-rc.21"` in `dependencies`
12
+
13
+ Contracts and one implementation-free connection model. It knows nothing about WebSockets: the
14
+ browser side is `@owlmeans/client-socket`, the Fastify side `@owlmeans/server-socket`, and each
15
+ supplies the members the model leaves abstract — `send`, `close`, `authenticate` and `prepare` —
16
+ that make it concrete. Both halves of an application therefore speak the same frames.
12
17
 
13
18
  ## Key Exports
14
19
 
15
20
  | Export | Description |
16
21
  |--------|-------------|
17
- | `Message` types | Wire message shape (envelope, payload, headers) |
18
- | `SocketEvent` types | Socket lifecycle events |
19
- | Errors | Typed socket errors (Disconnected, AuthFailed, etc.) |
20
- | Constants | Message types, channel names |
21
- | Helpers | Encode/decode messages |
22
+ | `createBasicConnection()` | The connection model — everything below the wire. A carrier assigns `send` / `close` / `authenticate` / `prepare` and feeds bytes to `receive` |
23
+ | `Connection` | What a handler is handed: the messaging verbs, `stage`, `getListeners` |
24
+ | `Message<T>` | The frame — `{ type, payload, id?, sender?, recipient?, dt?, rawData? }` |
25
+ | `CallMessage<T>` / `EventMessage<T>` / `AuthMessage<T>` | The three frames that add a field: `method` + `timeout`, `event`, `stage` |
26
+ | `MessageType` | `Call` `Result` `Error` `Request` `Response` `Event` `Message` `Auth` `System` |
27
+ | `isMessage` / `isEventMessage` / `isCallMessage` / `isAuthMessage` | Type guards — `isMessage(msg, true)` excludes system frames, `isEventMessage(msg, true)` keeps only them |
28
+ | `ConnectionListener` / `CallHendler` / `RequestHandler` / `CallResolver` | The callback shapes |
29
+ | `SocketSystemEvent` | The `event` values a `MessageType.System` frame carries — see below |
30
+ | `SOCKET_HEARTBEAT_TIMEOUT_CODE` | `4000` — the close code `client-socket`'s carrier uses when it force-closes a socket that has gone silent |
31
+ | `SocketError` and subclasses | `SocketInitializationError`, `SocketConnectionError`, `SocketUnauthorized`, `SocketUnsupported`, `SocketTimeout`, `SocketMessageError`, `SocketMessageMalformed` — all registered with `ResilientError` |
32
+ | `CALL_TIMEOUT` | 60 000 ms, the fallback when neither the message nor `connection.defaultCallTimeout` says |
33
+
34
+ ## The four ways to say something
35
+
36
+ Pick by who is expected to answer and how often — they are separate registries, and a handler
37
+ bound to one never sees the others.
22
38
 
23
- ## Usage
39
+ | Verb | Answered by | Shape |
40
+ |---|---|---|
41
+ | `notify(event, payload)` | `observe(event, handler)` | Fire-and-forget, fanned out to every observer of that event name |
42
+ | `call(method, ...args)` | `perform(method, handler)` | One RPC, resolved with the handler's return value or rejected with its error |
43
+ | `request(payload, observer?)` | `acknowledge(handler)`, answered with `reply(id, payload)` | An open question — acknowledgers run in turn until one takes it |
44
+ | `enqueue(payload, id?)` | `consume(filter?)` | A mailbox the far side drains on its own schedule; `enqueued()` is its depth |
24
45
 
25
46
  ```typescript
26
- import type { Message } from '@owlmeans/socket'
47
+ import { MessageType } from '@owlmeans/socket'
48
+ import type { Connection, EventMessage } from '@owlmeans/socket'
49
+
50
+ connection.observe<Progress>('job-event', async message => render(message.payload))
51
+ await connection.notify('job-event', { id, progress: 0.5 })
52
+
53
+ connection.perform<Report, [string]>('report', async id => await build(id))
54
+ const report = await connection.call<Report, [string]>('report', id)
55
+ ```
56
+
57
+ A `call` carries an id and a timeout, and the model arms the timer on both sides: the caller
58
+ rejects with `SocketTimeout` when the answer does not arrive, and the performer stops sending one
59
+ once it has elapsed. `timeout: 0` disables it. A performer that throws is answered with a
60
+ `MessageType.Error` frame carrying the marshalled error, so the caller's `call` rejects with the
61
+ original class rather than with a string.
62
+
63
+ ## Frames a listener sees
27
64
 
28
- const msg: Message = { type: 'subscribe', channel: 'projects', payload: { entityId } }
65
+ `listen(listener)` receives EVERY inbound frame after the model has routed it — the escape hatch
66
+ for what the verbs above do not cover. It also receives the frames a carrier synthesises, which is
67
+ how a handler learns the connection is gone:
68
+
69
+ ```typescript
70
+ connection.listen(async message => {
71
+ if (typeof message !== 'object') {
72
+ return
73
+ }
74
+ const msg = message as EventMessage<void>
75
+ if (msg.type === MessageType.System && msg.event === 'close') {
76
+ await cleanUp()
77
+ }
78
+ })
29
79
  ```
30
80
 
31
- Concrete implementations live in `@owlmeans/client-socket` (browser/native) and `@owlmeans/server-socket` (Fastify integration).
81
+ Both carriers emit exactly that frame — `MessageType.System`, `event: 'close'`, payload
82
+ `{ code }` — when the socket closes for good. Nothing else reports a TERMINAL disconnect, so any
83
+ subscription a handler opened is released there.
84
+
85
+ **`SocketSystemEvent`** is the full vocabulary a `MessageType.System` frame's `event` can carry —
86
+ `client-socket`'s reconnecting carrier is what emits the other four:
87
+
88
+ | Event | Meaning |
89
+ |---|---|
90
+ | `close` | The connection is gone for good — see above |
91
+ | `disconnected` | The socket dropped and a retry IS scheduled (client-socket only) — `{ code }` |
92
+ | `reconnecting` | Before each retry attempt (client-socket only) — `{ attempt, delay }` |
93
+ | `reconnected` | A retry succeeded, same `Connection` model (client-socket only) — `{ attempts }` |
94
+ | `lost` | The retry budget elapsed with no success, immediately followed by `close` (client-socket only) |
95
+
96
+ `close` is the only one of the five a plain carrier with no retry logic (like `server-socket`, or
97
+ `client-socket` itself with `reconnect: false`) will ever emit — a listener written against `close`
98
+ alone, before reconnect support existed, still sees exactly the frame it always did once a
99
+ reconnecting carrier's retries give up.
100
+
101
+ ## What the model expects of a carrier
102
+
103
+ - `receive(raw)` takes the raw string. It only parses text that starts with `{` or `[`; anything
104
+ else is dropped without reaching a listener. The carriers' own heartbeat IS JSON
105
+ (`{ type: 'ping' }`), so it is parsed: it matches no `MessageType` and routes nowhere, yet it
106
+ still reaches every `listen` listener — a listener has to recognise the frames it wants.
107
+ - A frame with no `type` is read as `MessageType.Message` and a frame with no `payload` is treated
108
+ as its own payload, so a plain JSON body from a foreign client still arrives as a message.
109
+ - `prepare(message, isRequest?)` is the carrier's hook for stamping a frame — timestamps,
110
+ `sender` / `recipient`. It runs on every outbound frame and on every inbound one.
111
+ - `send`, `close` and `authenticate` throw `SyntaxError` until a carrier assigns them, so one that
112
+ forgets a member fails loudly rather than dropping frames. `prepare` is the exception: it is
113
+ optional on the interface and simply absent until assigned, and the model calls it defensively —
114
+ a carrier that omits it stamps nothing.
115
+
116
+ ## Authentication
117
+
118
+ `auth(stage, payload)` sends an `AuthMessage` and waits. The far side's `authenticate` answers with
119
+ the next stage and its payload, or throws — a rejection travels back as an `AuthMessage` with a
120
+ null stage and is rebuilt by the initiator. `connection.stage` holds the current
121
+ `AuthenticationStage` throughout. Only the server carrier implements a real sequence; see the
122
+ `server-socket` skill.
32
123
 
33
124
  ## Depends On
34
125
 
35
- - `@owlmeans/error`, `@owlmeans/i18n`
36
- - `@owlmeans/basic-envelope` — message envelopes
126
+ - `@owlmeans/error` — `ResilientError`, which every socket error registers with
127
+ - `@owlmeans/auth` — `AuthenticationStage`, the vocabulary the auth frames carry
128
+ - `@owlmeans/basic-ids` — `uuid` for call and request ids
129
+
130
+ ## Related
131
+
132
+ - `client-socket` — the browser carrier and `useWs`
133
+ - `server-socket` — the Fastify carrier, guard enforcement and `connection(protocol, callback)`
package/build/consts.d.ts CHANGED
@@ -10,4 +10,26 @@ export declare enum MessageType {
10
10
  System = "system"
11
11
  }
12
12
  export declare const CALL_TIMEOUT = 60000;
13
+ /**
14
+ * System frames a carrier synthesises around the lifecycle of the underlying transport.
15
+ *
16
+ * `Close` keeps its original meaning: the connection is gone for good — a client-initiated
17
+ * close, a terminal server code, or a reconnect budget exhausted. A drop the carrier intends to
18
+ * retry is reported as `Disconnected` instead, so a listener that only knew about `close` before
19
+ * this carried reconnect support still sees exactly the frame it always did once retries give up.
20
+ */
21
+ export declare enum SocketSystemEvent {
22
+ Close = "close",
23
+ Disconnected = "disconnected",
24
+ Reconnecting = "reconnecting",
25
+ Reconnected = "reconnected",
26
+ Lost = "lost"
27
+ }
28
+ /**
29
+ * The close code a carrier uses when it drops a socket itself — a missed heartbeat pong within
30
+ * `pongTimeout`, catching a half-open TCP connection long before the OS would notice one.
31
+ * Reserved in the 4000–4999 private-use range so it is never confused with a code either
32
+ * endpoint's own WebSocket stack could produce.
33
+ */
34
+ export declare const SOCKET_HEARTBEAT_TIMEOUT_CODE = 4000;
13
35
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,MAAM,WAAW;CAClB;AAED,eAAO,MAAM,YAAY,QAAQ,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,MAAM,WAAW;CAClB;AAED,eAAO,MAAM,YAAY,QAAQ,CAAA;AAEjC;;;;;;;GAOG;AACH,oBAAY,iBAAiB;IAC3B,KAAK,UAAU;IACf,YAAY,iBAAiB;IAC7B,YAAY,iBAAiB;IAC7B,WAAW,gBAAgB;IAC3B,IAAI,SAAS;CACd;AAED;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,OAAO,CAAA"}
package/build/consts.js CHANGED
@@ -11,4 +11,27 @@ export var MessageType;
11
11
  MessageType["System"] = "system";
12
12
  })(MessageType || (MessageType = {}));
13
13
  export const CALL_TIMEOUT = 60000;
14
+ /**
15
+ * System frames a carrier synthesises around the lifecycle of the underlying transport.
16
+ *
17
+ * `Close` keeps its original meaning: the connection is gone for good — a client-initiated
18
+ * close, a terminal server code, or a reconnect budget exhausted. A drop the carrier intends to
19
+ * retry is reported as `Disconnected` instead, so a listener that only knew about `close` before
20
+ * this carried reconnect support still sees exactly the frame it always did once retries give up.
21
+ */
22
+ export var SocketSystemEvent;
23
+ (function (SocketSystemEvent) {
24
+ SocketSystemEvent["Close"] = "close";
25
+ SocketSystemEvent["Disconnected"] = "disconnected";
26
+ SocketSystemEvent["Reconnecting"] = "reconnecting";
27
+ SocketSystemEvent["Reconnected"] = "reconnected";
28
+ SocketSystemEvent["Lost"] = "lost";
29
+ })(SocketSystemEvent || (SocketSystemEvent = {}));
30
+ /**
31
+ * The close code a carrier uses when it drops a socket itself — a missed heartbeat pong within
32
+ * `pongTimeout`, catching a half-open TCP connection long before the OS would notice one.
33
+ * Reserved in the 4000–4999 private-use range so it is never confused with a code either
34
+ * endpoint's own WebSocket stack could produce.
35
+ */
36
+ export const SOCKET_HEARTBEAT_TIMEOUT_CODE = 4000;
14
37
  //# sourceMappingURL=consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAN,IAAY,WAUX;AAVD,WAAY,WAAW;IACrB,4BAAa,CAAA;IACb,gCAAiB,CAAA;IACjB,8BAAe,CAAA;IACf,kCAAmB,CAAA;IACnB,oCAAqB,CAAA;IACrB,8BAAe,CAAA;IACf,kCAAmB,CAAA;IACnB,4BAAa,CAAA;IACb,gCAAiB,CAAA;AACnB,CAAC,EAVW,WAAW,KAAX,WAAW,QAUtB;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAN,IAAY,WAUX;AAVD,WAAY,WAAW;IACrB,4BAAa,CAAA;IACb,gCAAiB,CAAA;IACjB,8BAAe,CAAA;IACf,kCAAmB,CAAA;IACnB,oCAAqB,CAAA;IACrB,8BAAe,CAAA;IACf,kCAAmB,CAAA;IACnB,4BAAa,CAAA;IACb,gCAAiB,CAAA;AACnB,CAAC,EAVW,WAAW,KAAX,WAAW,QAUtB;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAA;AAEjC;;;;;;;GAOG;AACH,MAAM,CAAN,IAAY,iBAMX;AAND,WAAY,iBAAiB;IAC3B,oCAAe,CAAA;IACf,kDAA6B,CAAA;IAC7B,kDAA6B,CAAA;IAC7B,gDAA2B,CAAA;IAC3B,kCAAa,CAAA;AACf,CAAC,EANW,iBAAiB,KAAjB,iBAAiB,QAM5B;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/socket",
3
- "version": "0.1.18-rc.2",
3
+ "version": "0.1.18-rc.21",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,9 +21,9 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/auth": "^0.1.18-rc.2",
25
- "@owlmeans/basic-ids": "^0.1.18-rc.2",
26
- "@owlmeans/error": "^0.1.18-rc.2"
24
+ "@owlmeans/auth": "^0.1.18-rc.21",
25
+ "@owlmeans/basic-ids": "^0.1.18-rc.21",
26
+ "@owlmeans/error": "^0.1.18-rc.20"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@owlmeans/dep-config": "workspace:*",
package/src/consts.ts CHANGED
@@ -12,3 +12,27 @@ export enum MessageType {
12
12
  }
13
13
 
14
14
  export const CALL_TIMEOUT = 60000
15
+
16
+ /**
17
+ * System frames a carrier synthesises around the lifecycle of the underlying transport.
18
+ *
19
+ * `Close` keeps its original meaning: the connection is gone for good — a client-initiated
20
+ * close, a terminal server code, or a reconnect budget exhausted. A drop the carrier intends to
21
+ * retry is reported as `Disconnected` instead, so a listener that only knew about `close` before
22
+ * this carried reconnect support still sees exactly the frame it always did once retries give up.
23
+ */
24
+ export enum SocketSystemEvent {
25
+ Close = 'close',
26
+ Disconnected = 'disconnected',
27
+ Reconnecting = 'reconnecting',
28
+ Reconnected = 'reconnected',
29
+ Lost = 'lost'
30
+ }
31
+
32
+ /**
33
+ * The close code a carrier uses when it drops a socket itself — a missed heartbeat pong within
34
+ * `pongTimeout`, catching a half-open TCP connection long before the OS would notice one.
35
+ * Reserved in the 4000–4999 private-use range so it is never confused with a code either
36
+ * endpoint's own WebSocket stack could produce.
37
+ */
38
+ export const SOCKET_HEARTBEAT_TIMEOUT_CODE = 4000
package/build/.gitkeep DELETED
File without changes