@ccmsg/protocol 1.22.1 → 2.0.0

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.
@@ -7,7 +7,7 @@ import { InstanceId, Mid, Sender, Sid, Timestamp } from "../identifiers.ts";
7
7
  * These are not errors: the op succeeded and the message is held in the
8
8
  * recipient's inbox. They tell the sender what to do next — wait, resend to
9
9
  * another session, or give up. The op itself fails only when `to` names no
10
- * session anywhere in the cluster (`session_not_found`). */
10
+ * session anywhere in the mesh (`session_not_found`). */
11
11
  export const UndeliveredReason = Type.Union(
12
12
  [
13
13
  /** Alive, but not yet listening. The daemon delivers when it starts. */
@@ -89,11 +89,57 @@ export const InboxMessage = Type.Object(
89
89
  text: Type.String(),
90
90
  reply_to: Type.Optional(Mid),
91
91
  sent_at: Timestamp,
92
+ /** Who it is addressed to. A session's own subscription is already the
93
+ * recipient, so a row reaching one says nothing by repeating it; a person
94
+ * holds the inbox of every session in one subscription, and a row that
95
+ * does not name its recipient cannot be placed against any of them. So the
96
+ * instance states it on the rows it answers a person with. */
97
+ to: Type.Optional(Sid),
92
98
  },
93
99
  { $id: "InboxMessage" },
94
100
  );
95
101
  export type InboxMessage = Static<typeof InboxMessage>;
96
102
 
103
+ /** Why a message is no longer in the inbox.
104
+ *
105
+ * The three are apart because they are three different things to have happened
106
+ * to a message, and a reader watching for one it sent draws each differently:
107
+ * `delivered` means the recipient has it and its own account of it follows,
108
+ * while the other two mean it never arrived and never will. A removal with no
109
+ * reason would leave a waiting message and an abandoned one looking alike. */
110
+ export const InboxRemovedReason = Type.Union(
111
+ [
112
+ /** Handed to the recipient. */
113
+ Type.Literal("delivered"),
114
+ /** `INBOX_RETENTION_MS` ran out with the recipient never taking it. */
115
+ Type.Literal("expired"),
116
+ /** Dropped, oldest first, to take a newer message into a full inbox —
117
+ * the same event the newer message's sender was told as `inbox_full`. */
118
+ Type.Literal("dropped"),
119
+ ],
120
+ { $id: "InboxRemovedReason" },
121
+ );
122
+ export type InboxRemovedReason = Static<typeof InboxRemovedReason>;
123
+
124
+ /** A message that has left the inbox.
125
+ *
126
+ * A removal has to be a marked element rather than an absence, since a frame
127
+ * carries only what changed and an absence in it says nothing. It names the
128
+ * `mid` every row is matched by, and why — there is no message left to
129
+ * describe, and the reason is the one thing the reader cannot derive. */
130
+ export const InboxRemoved = Type.Object(
131
+ {
132
+ mid: Mid,
133
+ removed: Type.Literal(true),
134
+ reason: InboxRemovedReason,
135
+ },
136
+ { $id: "InboxRemoved" },
137
+ );
138
+ export type InboxRemoved = Static<typeof InboxRemoved>;
139
+
140
+ export const InboxElement = Type.Union([InboxMessage, InboxRemoved], { $id: "InboxElement" });
141
+ export type InboxElement = Static<typeof InboxElement>;
142
+
97
143
  /** How long an undelivered message is kept for its recipient. The same window
98
144
  * a lost session stays listed for: a message outliving the session it was
99
145
  * addressed to would be offered to no one, and a session outliving what was
@@ -106,6 +152,21 @@ export const INBOX_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
106
152
  * accepts is one the recipient can still be handed. */
107
153
  export const INBOX_MAX_PER_SID = 256;
108
154
 
109
- /** The `inbox` topic. Its snapshot is whatever is still undelivered for this
110
- * session; each later frame is one newly arrived message. */
111
- export const InboxFrame = topicFrame("inbox", Type.Array(InboxMessage));
155
+ /** The `inbox` topic: what is waiting, for whoever may see it.
156
+ *
157
+ * Elements, matched by `mid`. The snapshot is what is still undelivered and
158
+ * each later frame is what changed — a message arriving, or one leaving as an
159
+ * `InboxRemoved`.
160
+ *
161
+ * **A session's subscription is the delivery and a person's is a view.** What
162
+ * the session is handed it has been given, and the message leaves its inbox;
163
+ * what a person reads leaves the inbox exactly as it was, because a person is
164
+ * not who any of it was addressed to. The asymmetry is the point rather than an
165
+ * exception: without the view there is no way to see that something sent is
166
+ * still waiting, and a view that consumed what it looked at would deliver
167
+ * messages to no one by being opened.
168
+ *
169
+ * A person therefore sees a message twice over: waiting here, and afterwards in
170
+ * the recipient's own transcript. `mid` is what joins the two, and the removal
171
+ * marked `delivered` is what says the second is coming. */
172
+ export const InboxFrame = topicFrame("inbox", Type.Array(InboxElement));
@@ -1,6 +1,6 @@
1
1
  import { type Static, Type } from "@sinclair/typebox";
2
2
  import { request, response, topicFrame } from "../envelope.ts";
3
- import { Sid, Timestamp } from "../identifiers.ts";
3
+ import { Mid, Sid, Timestamp } from "../identifiers.ts";
4
4
 
5
5
  /** A short line meant to reach a person watching, not the session's own turn.
6
6
  * Delivery is best effort and unacknowledged; unlike `message.send`, nothing is
@@ -9,6 +9,8 @@ export const NotifySendArgs = Type.Object({
9
9
  /** The session the notification is about. Omit to mean the caller. */
10
10
  sid: Type.Optional(Sid),
11
11
  text: Type.String({ minLength: 1 }),
12
+ /** The `mid` this line answers, when it answers one. */
13
+ reply_to: Type.Optional(Mid),
12
14
  });
13
15
  export type NotifySendArgs = Static<typeof NotifySendArgs>;
14
16
 
@@ -24,6 +26,15 @@ export const Notification = Type.Object(
24
26
  /** How the session should be shown, resolved by the issuing instance. */
25
27
  sid_label: Type.String(),
26
28
  text: Type.String(),
29
+ /** What this line answers, when it answers something. A notification is
30
+ * shown while the session's own account of the same answer is still being
31
+ * written, so a reader holding both needs to know they are one thing: the
32
+ * `mid` is the key it matches on, and without it the two stand as two.
33
+ *
34
+ * It says what is answered and never what kind of line this is. A
35
+ * notification is one thing whoever it came from, and a kind would be read
36
+ * as a reason to draw it differently. */
37
+ reply_to: Type.Optional(Mid),
27
38
  sent_at: Timestamp,
28
39
  },
29
40
  { $id: "Notification" },