1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 | ;; JS-semantics helpers used across the port.
;;
;; Two whole classes of silent bug live here, both from translating JavaScript
;; into Clojure rather than into "Clojure that means what the JavaScript meant".
;;
;; 1. TRUTHINESS. In JS `0`, `""`, `NaN`, `null` and `undefined` are all falsy;
;; in Clojure only `nil` and `false` are. The TypeScript original leans on
;; this constantly and load-bearingly — `if (rec.lastBeacon)` means "has this
;; peer ever proved membership" (0 = never), `if (this.#redialTimer)` means
;; "is a redial already scheduled" (a timer id of 0 means no), `if (!op ||
;; typeof op !== "object")` drops empty payloads. A naive `(when x …)` makes
;; every one of those always-true. `truthy?` is the exact JS test, and it is
;; used everywhere the original wrote a bare condition on a non-boolean.
;;
;; 2. KEY ORDER. `js-obj` / `#js {}` preserves literal key order only up to
;; EIGHT pairs; at nine or more it routes through a PersistentHashMap and
;; emits in HASH order, at every optimization level, with no warning
;; (dev/docs/CLJS.md). Everything this kit JSON.stringifies before sealing —
;; envelopes, presence beacons, hellos, draw previews — and every LogOp it
;; appends is a wire shape, so `ordered` builds them with sequential
;; unchecked-set calls regardless of width, and test/vectors/protocol.json
;; pins the resulting bytes.
(ns ardegazu.rooms.js)
(defn ^boolean truthy?
"JavaScript truthiness: false for false/null/undefined/0/NaN/\"\"."
[x]
(js* "!!(~{})" x))
(defn nn
"JavaScript `a ?? b`: only null/undefined fall through (0 and \"\" do not)."
[a b]
(if (or (nil? a) (identical? a js/undefined)) b a))
(defn oset*
"Set `kvs` (k1 v1 k2 v2 …) on `o` in argument order, then return `o`."
[o kvs]
(loop [kv kvs]
(when (seq kv)
(unchecked-set o (first kv) (second kv))
(recur (nnext kv))))
o)
(defn ordered
"A fresh JS object with `kvs` set in order — the key-order-safe `js-obj`."
[& kvs]
(oset* (js-obj) kvs))
(defn ^boolean js-object?
"Exactly TS's `typeof x === \"object\"` (arrays and null pass the typeof;
callers add the null gate the original had)."
[x]
(identical? "object" (js* "typeof ~{}" x)))
;; ---- async iteration -------------------------------------------------------
;;
;; ClojureScript has no `for await`, and the ESM packages this kit drives are
;; full of async generators: the oplog iterator, unixfs `cat`, Helia's
;; `pins.add`/`pins.rm`, and it-length-prefixed's decoded frame stream. These
;; two helpers drive the async-iteration protocol by hand and return promises.
(defn for-each!
"Call `(f value)` for every value of an async iterable; resolves to nil.
`f` may return a promise, which is awaited before the next step."
[iterable f]
(let [it (.call (unchecked-get iterable js/Symbol.asyncIterator) iterable)]
(letfn [(step []
(.then (.next ^js it)
(fn [r]
(if (truthy? (.-done ^js r))
nil
(.then (js/Promise.resolve (f (.-value ^js r)))
(fn [_] (step)))))))]
(step))))
(defn drain!
"Consume an async iterable for its side effects only."
[iterable]
(for-each! iterable (fn [_] nil)))
(defn collect!
"Resolve to a JS array of every value an async iterable yields."
[iterable]
(let [out (array)]
(.then (for-each! iterable (fn [v] (.push out v) nil))
(fn [_] out))))
;; ---- promise composition ---------------------------------------------------
;;
;; `shadow.cljs.modern/js-await` is pure `.then` sugar, so every awaiting step
;; in this kit is a link in a hand-written chain. Four chain SHAPES recurred
;; often enough across lib/log and lib/net that each was a separate place to get
;; the same thing subtly wrong; naming them makes the intent checkable.
;;
;; Everything here stays a NATIVE js/Promise. promesa was measured for this kit
;; and rejected — deps.edn records why, and the short version is that this build
;; is :simple, so nothing DCEs the runtime away, and its PromiseImpl is a
;; different promise implementation with a different microtask hop count than
;; the one the recorded transcripts were taken under.
;;
;; SEQUENTIAL BY CONTRACT: PROTOCOL.md mandates the ORDER of the side effects
;; these compose (mailbox replay/deposit, the log's oldest-first emit). Neither
;; helper below may ever become `js/Promise.all`.
(defn later
"Run `(f)` as the first link of a promise chain, so a SYNCHRONOUS throw inside
`f` — `CID.parse` on garbage, a getter on a missing field — becomes a
rejection the chain can catch instead of an exception at the call site."
[f]
(.then (js/Promise.resolve nil) (fn [_] (f))))
(defn attempt
"`later`, resolving to `fallback` if anything rejects. The decrypt-or-drop
shape (PROTOCOL.md §3: any failure ⇒ drop silently, never throw)."
[f fallback]
(.catch (later f) (fn [_] fallback)))
(defn each-in-order!
"Call `(f x)` for every `x`, awaiting each result before starting the next;
resolves to nil. Distinct from `for-each!` above: that one drives an ASYNC
ITERABLE, this one walks a collection already in hand."
[xs f]
(reduce (fn [p x] (.then p (fn [_] (f x)))) (js/Promise.resolve nil) xs))
(defn every-in-order!
"Sequential short-circuiting AND: resolves true when every `(f x)` resolved
truthy, false at the first that did not — and `f` is not called again after
that. Returns a promise, hence the `!` rather than a `?`."
[xs f]
(reduce (fn [p x] (.then p (fn [ok] (if ok (f x) false))))
(js/Promise.resolve true)
xs))
(defn parse-json
"`JSON.parse`, or `js/undefined` when the text is not JSON — wire bytes are
attacker-controlled, so a parse failure is a drop, not a throw. `undefined`
is an unambiguous sentinel because JSON.parse can never RETURN it, and a
wire-shaped `null` still parses to nil and reaches the caller unchanged."
[s]
(try (js/JSON.parse s) (catch :default _ js/undefined)))
|