@foony/realtime 0.0.1

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/src/wire.ts ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Wire protocol types for the Foony Realtime WebSocket service.
3
+ *
4
+ * Mirrors `services/realtime-saas/internal/wire/wire.go` exactly — any
5
+ * change here MUST be mirrored on the Go side and vice versa.
6
+ *
7
+ * Conventions:
8
+ * - Every frame has a single-character `t` discriminator.
9
+ * - Client-originated frames carry a numeric `id`; the server echoes it
10
+ * back on the matching `ack` / `err` frame so SDKs can correlate
11
+ * requests to responses.
12
+ * - Field names favor readability over brevity (`channel`, not `ch`),
13
+ * except for `t`, which we keep short because every frame carries it.
14
+ */
15
+
16
+ /** Discriminator values for the `t` field. */
17
+ export type FrameType =
18
+ | 'auth'
19
+ | 'sub'
20
+ | 'unsub'
21
+ | 'pub'
22
+ | 'pres'
23
+ | 'hist'
24
+ | 'ping'
25
+ | 'connected'
26
+ | 'ack'
27
+ | 'msg'
28
+ | 'presEvt'
29
+ | 'err'
30
+ | 'pong'
31
+ | 'histRes';
32
+
33
+ /** Recognized presence transition values. */
34
+ export type PresenceAction = 'enter' | 'leave' | 'update';
35
+
36
+ // ---- Client -> Server frames ----
37
+
38
+ /** First frame after the WebSocket handshake. Carries either a JWT token or API key. */
39
+ export type AuthFrame = {
40
+ readonly t: 'auth';
41
+ readonly token?: string;
42
+ readonly key?: string;
43
+ readonly clientId?: string;
44
+ };
45
+
46
+ /** Start delivering messages + presence for `channel`. */
47
+ export type SubscribeFrame = {
48
+ readonly t: 'sub';
49
+ readonly channel: string;
50
+ readonly id: number;
51
+ /** Optional resume cursor; when set, replay messages with id > this. */
52
+ readonly lastMessageId?: string;
53
+ };
54
+
55
+ /** Stop delivering messages + presence for `channel`. */
56
+ export type UnsubscribeFrame = {
57
+ readonly t: 'unsub';
58
+ readonly channel: string;
59
+ readonly id: number;
60
+ };
61
+
62
+ /** Publish a single application-level message to `channel`. */
63
+ export type PublishFrame = {
64
+ readonly t: 'pub';
65
+ readonly channel: string;
66
+ readonly name: string;
67
+ readonly data: unknown;
68
+ readonly id: number;
69
+ };
70
+
71
+ /** Mutate the publisher's presence membership in `channel`. */
72
+ export type PresenceFrame = {
73
+ readonly t: 'pres';
74
+ readonly channel: string;
75
+ readonly action: PresenceAction;
76
+ readonly data?: unknown;
77
+ readonly id: number;
78
+ };
79
+
80
+ /** Application-level liveness probe. Server replies with `pong`. */
81
+ export type PingFrame = {
82
+ readonly t: 'ping';
83
+ };
84
+
85
+ // ---- Server -> Client frames ----
86
+
87
+ /** Sent once after a successful auth handshake. */
88
+ export type ConnectedFrame = {
89
+ readonly t: 'connected';
90
+ readonly connectionId: string;
91
+ readonly keepAliveMs: number;
92
+ readonly clientId: string;
93
+ };
94
+
95
+ /** Acks a client request that does not need a structured reply. */
96
+ export type AckFrame = {
97
+ readonly t: 'ack';
98
+ readonly id: number;
99
+ };
100
+
101
+ /** Server-originated channel message. */
102
+ export type MessageFrame = {
103
+ readonly t: 'msg';
104
+ readonly channel: string;
105
+ readonly name: string;
106
+ readonly data: unknown;
107
+ readonly timestamp: number;
108
+ readonly messageId: string;
109
+ readonly clientId?: string;
110
+ };
111
+
112
+ /** Server-originated presence transition. */
113
+ export type PresenceEventFrame = {
114
+ readonly t: 'presEvt';
115
+ readonly channel: string;
116
+ readonly action: PresenceAction;
117
+ readonly clientId: string;
118
+ readonly connectionId: string;
119
+ readonly data?: unknown;
120
+ readonly timestamp: number;
121
+ };
122
+
123
+ /** Protocol or authorization error related to a specific client request. */
124
+ export type ErrorFrame = {
125
+ readonly t: 'err';
126
+ readonly id?: number;
127
+ readonly code: number;
128
+ readonly message: string;
129
+ };
130
+
131
+ /** Response to `ping`. */
132
+ export type PongFrame = {
133
+ readonly t: 'pong';
134
+ };
135
+
136
+ /** Response to `hist`. Messages oldest-first. */
137
+ export type HistoryResponseFrame = {
138
+ readonly t: 'histRes';
139
+ readonly id: number;
140
+ readonly channel: string;
141
+ readonly messages: readonly MessageFrame[];
142
+ readonly more?: boolean;
143
+ };
144
+
145
+ /** Any frame the client may send. */
146
+ export type ClientFrame =
147
+ | AuthFrame
148
+ | SubscribeFrame
149
+ | UnsubscribeFrame
150
+ | PublishFrame
151
+ | PresenceFrame
152
+ | PingFrame;
153
+
154
+ /** Any frame the server may send. */
155
+ export type ServerFrame =
156
+ | ConnectedFrame
157
+ | AckFrame
158
+ | MessageFrame
159
+ | PresenceEventFrame
160
+ | ErrorFrame
161
+ | PongFrame
162
+ | HistoryResponseFrame;
163
+
164
+ /**
165
+ * Error codes the server uses on `err` frames. Mirrors the Go
166
+ * `internal/wire` constants — keep in sync.
167
+ */
168
+ export const ErrorCode = {
169
+ BadFrame: 40001,
170
+ BadAuth: 40101,
171
+ AuthExpired: 40102,
172
+ Forbidden: 40300,
173
+ NotFound: 40400,
174
+ Server: 50000,
175
+ } as const;
176
+
177
+ export type ErrorCodeName = keyof typeof ErrorCode;