Experiments for playing with atproto in Gleam.
14

Configure Feed

Select the types of activity you want to include in your feed.

🚧 Implement tid parsing and conversions.

Hayleigh Thompson (Jul 16, 2026, 2:16 AM +0200) 44d807c9 dd95147e

+236
+236
src/at/tid.gleam
··· 1 + // IMPORTS --------------------------------------------------------------------- 2 + 3 + import gleam/dynamic/decode.{type Decoder} 4 + import gleam/int 5 + import gleam/json.{type Json} 6 + import gleam/order.{type Order} 7 + import gleam/time/timestamp.{type Timestamp} 8 + 9 + // TYPES ----------------------------------------------------------------------- 10 + 11 + /// 12 + /// 13 + pub opaque type Tid { 14 + Tid(microseconds: Int, clock: Int) 15 + } 16 + 17 + // CONSTANTS ------------------------------------------------------------------- 18 + 19 + const zero = Tid(microseconds: 0, clock: 0) 20 + 21 + // CONSTRUCTORS ---------------------------------------------------------------- 22 + 23 + /// 24 + /// 25 + pub fn from_string(value: String) -> Result(Tid, Nil) { 26 + case parse_tid_string(value) { 27 + Ok(<<0:int-size(1), microseconds:int-size(53), clock:int-size(10)>>) -> 28 + Ok(Tid(microseconds:, clock:)) 29 + 30 + Ok(_) | Error(_) -> Error(Nil) 31 + } 32 + } 33 + 34 + fn parse_tid_string(value: String) -> Result(BitArray, Nil) { 35 + case value { 36 + "2" <> rest -> do_parse_tid_string(rest, 1, 0) 37 + "3" <> rest -> do_parse_tid_string(rest, 1, 1) 38 + "4" <> rest -> do_parse_tid_string(rest, 1, 2) 39 + "5" <> rest -> do_parse_tid_string(rest, 1, 3) 40 + "6" <> rest -> do_parse_tid_string(rest, 1, 4) 41 + "7" <> rest -> do_parse_tid_string(rest, 1, 5) 42 + "a" <> rest -> do_parse_tid_string(rest, 1, 6) 43 + "b" <> rest -> do_parse_tid_string(rest, 1, 7) 44 + "c" <> rest -> do_parse_tid_string(rest, 1, 8) 45 + "d" <> rest -> do_parse_tid_string(rest, 1, 9) 46 + "e" <> rest -> do_parse_tid_string(rest, 1, 10) 47 + "f" <> rest -> do_parse_tid_string(rest, 1, 11) 48 + "g" <> rest -> do_parse_tid_string(rest, 1, 12) 49 + "h" <> rest -> do_parse_tid_string(rest, 1, 13) 50 + "i" <> rest -> do_parse_tid_string(rest, 1, 14) 51 + "j" <> rest -> do_parse_tid_string(rest, 1, 15) 52 + _ -> Error(Nil) 53 + } 54 + } 55 + 56 + fn do_parse_tid_string( 57 + value: String, 58 + length: Int, 59 + int: Int, 60 + ) -> Result(BitArray, Nil) { 61 + case value { 62 + _ if length > 13 -> Error(Nil) 63 + 64 + "" if length == 13 -> Ok(<<int:64>>) 65 + "" if length < 13 -> Error(Nil) 66 + 67 + "2" <> rest -> do_parse_tid_string(rest, length + 1, int * 32) 68 + "3" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 1) 69 + "4" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 2) 70 + "5" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 3) 71 + "6" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 4) 72 + "7" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 5) 73 + "a" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 6) 74 + "b" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 7) 75 + "c" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 8) 76 + "d" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 9) 77 + "e" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 10) 78 + "f" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 11) 79 + "g" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 12) 80 + "h" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 13) 81 + "i" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 14) 82 + "j" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 15) 83 + "k" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 16) 84 + "l" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 17) 85 + "m" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 18) 86 + "n" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 19) 87 + "o" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 20) 88 + "p" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 21) 89 + "q" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 22) 90 + "r" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 23) 91 + "s" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 24) 92 + "t" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 25) 93 + "u" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 26) 94 + "v" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 27) 95 + "w" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 28) 96 + "x" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 29) 97 + "y" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 30) 98 + "z" <> rest -> do_parse_tid_string(rest, length + 1, int * 32 + 31) 99 + 100 + _ -> Error(Nil) 101 + } 102 + } 103 + 104 + /// 105 + /// 106 + /// > *Note*: be careful when generating the timestamp for a [`Tid`](#Tid) using 107 + /// > [`timestamp.system_time`](https://gleam-time.hexdocs.pm/gleam/time/timestamp.html#system_time) 108 + /// > as monotonicity is _not_ guaranteed. For Erlang, you may consider 109 + /// > [`erlang:monotonic_time()`](https://www.erlang.org/doc/apps/erts/erlang.html#monotonic_time/0) 110 + /// > as an alternative. For the JavaScript target, you may consider 111 + /// > [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now). 112 + /// 113 + pub fn from_timestamp(value: Timestamp, clock: Int) -> Tid { 114 + let clock = case clock { 115 + id if id < 0 -> 0 116 + id if id > 1023 -> 1023 117 + id -> id 118 + } 119 + 120 + let #(seconds, nanoseconds) = timestamp.to_unix_seconds_and_nanoseconds(value) 121 + let microseconds = seconds * 1_000_000 + nanoseconds / 1000 122 + 123 + Tid(microseconds:, clock:) 124 + } 125 + 126 + /// 127 + /// 128 + pub fn from_microseconds(microseconds: Int, clock: Int) -> Tid { 129 + let clock = case clock { 130 + id if id < 0 -> 0 131 + id if id > 1023 -> 1023 132 + id -> id 133 + } 134 + 135 + Tid(microseconds:, clock:) 136 + } 137 + 138 + /// 139 + /// 140 + pub fn decoder() -> Decoder(Tid) { 141 + use string <- decode.then(decode.string) 142 + 143 + case from_string(string) { 144 + Ok(tid) -> decode.success(tid) 145 + Error(_) -> decode.failure(zero, "Tid") 146 + } 147 + } 148 + 149 + // QUERIES --------------------------------------------------------------------- 150 + 151 + /// 152 + /// 153 + pub fn microseconds(tid: Tid) -> Int { 154 + tid.microseconds 155 + } 156 + 157 + /// 158 + /// 159 + pub fn clock(tid: Tid) -> Int { 160 + tid.clock 161 + } 162 + 163 + /// 164 + /// 165 + pub fn compare(a: Tid, b: Tid) -> Order { 166 + int.compare(a.microseconds, b.microseconds) 167 + } 168 + 169 + // CONVERSIONS ----------------------------------------------------------------- 170 + 171 + /// 172 + /// 173 + pub fn to_string(tid: Tid) -> String { 174 + let bits = <<0:1, tid.microseconds:53, tid.clock:10>> 175 + let assert <<int:64>> = bits 176 + 177 + do_to_string(int, 0, "") 178 + } 179 + 180 + fn do_to_string(value: Int, length: Int, acc: String) -> String { 181 + case value % 32 { 182 + _ if length == 13 -> acc 183 + 184 + 0 -> do_to_string(value / 32, length + 1, "2" <> acc) 185 + 1 -> do_to_string(value / 32, length + 1, "3" <> acc) 186 + 2 -> do_to_string(value / 32, length + 1, "4" <> acc) 187 + 3 -> do_to_string(value / 32, length + 1, "5" <> acc) 188 + 4 -> do_to_string(value / 32, length + 1, "6" <> acc) 189 + 5 -> do_to_string(value / 32, length + 1, "7" <> acc) 190 + 6 -> do_to_string(value / 32, length + 1, "a" <> acc) 191 + 7 -> do_to_string(value / 32, length + 1, "b" <> acc) 192 + 8 -> do_to_string(value / 32, length + 1, "c" <> acc) 193 + 9 -> do_to_string(value / 32, length + 1, "d" <> acc) 194 + 10 -> do_to_string(value / 32, length + 1, "e" <> acc) 195 + 11 -> do_to_string(value / 32, length + 1, "f" <> acc) 196 + 12 -> do_to_string(value / 32, length + 1, "g" <> acc) 197 + 13 -> do_to_string(value / 32, length + 1, "h" <> acc) 198 + 14 -> do_to_string(value / 32, length + 1, "i" <> acc) 199 + 15 -> do_to_string(value / 32, length + 1, "j" <> acc) 200 + 16 -> do_to_string(value / 32, length + 1, "k" <> acc) 201 + 17 -> do_to_string(value / 32, length + 1, "l" <> acc) 202 + 18 -> do_to_string(value / 32, length + 1, "m" <> acc) 203 + 19 -> do_to_string(value / 32, length + 1, "n" <> acc) 204 + 20 -> do_to_string(value / 32, length + 1, "o" <> acc) 205 + 21 -> do_to_string(value / 32, length + 1, "p" <> acc) 206 + 22 -> do_to_string(value / 32, length + 1, "q" <> acc) 207 + 23 -> do_to_string(value / 32, length + 1, "r" <> acc) 208 + 24 -> do_to_string(value / 32, length + 1, "s" <> acc) 209 + 25 -> do_to_string(value / 32, length + 1, "t" <> acc) 210 + 26 -> do_to_string(value / 32, length + 1, "u" <> acc) 211 + 27 -> do_to_string(value / 32, length + 1, "v" <> acc) 212 + 28 -> do_to_string(value / 32, length + 1, "w" <> acc) 213 + 29 -> do_to_string(value / 32, length + 1, "x" <> acc) 214 + 30 -> do_to_string(value / 32, length + 1, "y" <> acc) 215 + 31 -> do_to_string(value / 32, length + 1, "z" <> acc) 216 + 217 + _ -> panic as "..." 218 + } 219 + } 220 + 221 + /// 222 + /// 223 + pub fn to_timestamp(tid: Tid) -> Timestamp { 224 + let seconds = tid.microseconds / 1_000_000 225 + let nanoseconds = { tid.microseconds % 1_000_000 } * 1000 226 + 227 + timestamp.from_unix_seconds_and_nanoseconds(seconds, nanoseconds) 228 + } 229 + 230 + /// 231 + /// 232 + pub fn to_json(tid: Tid) -> Json { 233 + tid 234 + |> to_string 235 + |> json.string 236 + }