@procedures/client 0.50.0 → 0.51.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.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The encoding of a frame with a shared value (roadmap CLI-112). The publisher of events sends the same event to each
3
+ * subscriber. Each connection puts it in its own frame, with its own id and number. Without this module, each
4
+ * connection encodes the whole event again, so one event costs subscribers x event bytes.
5
+ *
6
+ * The publisher marks the data of an event as shared, once for each receiver of the event. `encodeFrame` encodes a
7
+ * marked value once, at its first frame, and puts that text in each later frame of the same event. The text of a frame
8
+ * is byte-equal to `JSON.stringify` of the frame at that first encode.
9
+ *
10
+ * A source can publish the same object again after a change. A receiver that gets the object a second time starts a
11
+ * new event, so the next frame encodes the object again. The text of one event is the value at its first encode.
12
+ */
13
+
14
+ /** One event of a shared value: the receivers that got it, and its text after the first frame. */
15
+ interface Share {
16
+ readonly receivers: WeakSet<object>;
17
+ text?: string;
18
+ }
19
+
20
+ /** The current event of each shared value. */
21
+ const shares = new WeakMap<object, Share>();
22
+ /** The count of the encodes of shared values, for the tests. */
23
+ let encodes = 0;
24
+
25
+ /**
26
+ * The depth of the frame that `encodeFrame` examines for a shared value. A WebSocket frame and an NDJSON line hold an
27
+ * event at depth 2: the frame, its `payload`, then the `data` of the payload.
28
+ */
29
+ const DEPTH = 3;
30
+
31
+ /**
32
+ * This function marks a value as shared by the frames of the receivers of one event, and gives the value back. When the
33
+ * receiver got the value before, the value starts a new event, and its first frame encodes the value again.
34
+ */
35
+ export function shareEncoding<T>(value: T, receiver: object): T {
36
+ if (typeof value !== "object" || value === null) return value;
37
+ const share = shares.get(value);
38
+ if (share === undefined || share.receivers.has(receiver)) shares.set(value, { receivers: new WeakSet([receiver]) });
39
+ else share.receivers.add(receiver);
40
+ return value;
41
+ }
42
+
43
+ /** This function gives the count of the encodes of shared values since the start of the process. */
44
+ export function sharedEncodes(): number {
45
+ return encodes;
46
+ }
47
+
48
+ /**
49
+ * This function gives the JSON text of a frame. The function encodes a shared plain object once for all frames. The
50
+ * object can be the frame, or a value in a plain object of the frame up to depth 3. The text is byte-equal to
51
+ * `JSON.stringify(frame)`.
52
+ */
53
+ export function encodeFrame(frame: unknown): string {
54
+ const text = encode(frame, DEPTH);
55
+ // `JSON.stringify` gives undefined for a value with no JSON form. A frame is always an object
56
+ if (text === undefined) throw new TypeError("A frame must have a JSON form");
57
+ return text;
58
+ }
59
+
60
+ /** The text of a shared value, from the first encode of its current event. */
61
+ function sharedText(value: object, share: Share): string | undefined {
62
+ if (share.text !== undefined) return share.text;
63
+ encodes++;
64
+ const text = JSON.stringify(value);
65
+ if (text !== undefined) share.text = text;
66
+ return text;
67
+ }
68
+
69
+ /** True for an object that `JSON.stringify` writes key by key: a plain object with no `toJSON`. */
70
+ function isPlain(value: object): value is Record<string, unknown> {
71
+ if (Array.isArray(value)) return false;
72
+ const prototype = Object.getPrototypeOf(value) as unknown;
73
+ if (prototype !== Object.prototype && prototype !== null) return false;
74
+ return typeof (value as { toJSON?: unknown }).toJSON !== "function";
75
+ }
76
+
77
+ /**
78
+ * The JSON text of the value of a key, as `JSON.stringify` writes it in an object. A `toJSON` method gets the key, as
79
+ * in the object.
80
+ */
81
+ function asProperty(key: string, value: unknown): string | undefined {
82
+ const text = JSON.stringify({ [key]: value });
83
+ return text === "{}" ? undefined : text.slice(JSON.stringify(key).length + 2, -1);
84
+ }
85
+
86
+ /** The JSON text of the value of a key, or undefined when the value has no JSON form, as `JSON.stringify` gives. */
87
+ function encode(value: unknown, depth: number, key = ""): string | undefined {
88
+ // Only an object and a bigint can have a `toJSON` method
89
+ if (typeof value === "bigint") return asProperty(key, value);
90
+ if (typeof value !== "object" || value === null) return JSON.stringify(value);
91
+ if (!isPlain(value)) return asProperty(key, value);
92
+ const share = shares.get(value);
93
+ if (share !== undefined) return sharedText(value, share);
94
+ if (depth === 0) return JSON.stringify(value);
95
+ const parts: string[] = [];
96
+ for (const name of Object.keys(value)) {
97
+ const part = encode(value[name], depth - 1, name);
98
+ if (part !== undefined) parts.push(`${JSON.stringify(name)}:${part}`);
99
+ }
100
+ return `{${parts.join(",")}}`;
101
+ }
@@ -186,8 +186,8 @@ export type StorageObserver<T> = (changes: StorageChangeBatch<T>) => void;
186
186
  * - The order of a full result is the order of the entries. The order of a delta is the order of
187
187
  * the last change of each id.
188
188
  * - A result gives each write at least one time. A write can come again in two cases, with the same
189
- * value or a later value. A full result takes its version before it lists the items, so it can
190
- * hold a write above that version, and the next delta gives the write again. A write that ends
189
+ * value or a later value. A full result takes its version after it lists the items, and it can
190
+ * hold a write above that version, so the next delta gives the write again. A write that ends
191
191
  * after its lease can come again in a later delta, with a new version. A consumer applies a change
192
192
  * by its id, and does not count changes.
193
193
  */