+2
-2
Cargo.lock
+2
-2
Cargo.lock
···
1796
1796
[[package]]
1797
1797
name = "fjall"
1798
1798
version = "2.11.2"
1799
-
source = "git+https://github.com/fjall-rs/fjall.git#42d811f7c8cc9004407d520d37d2a1d8d246c03d"
1799
+
source = "git+https://github.com/fjall-rs/fjall.git?rev=fb229572bb7d1d6966a596994dc1708e47ec57d8#fb229572bb7d1d6966a596994dc1708e47ec57d8"
1800
1800
dependencies = [
1801
1801
"byteorder",
1802
1802
"byteview",
···
6049
6049
"clap",
6050
6050
"dropshot",
6051
6051
"env_logger",
6052
-
"fjall 2.11.2 (git+https://github.com/fjall-rs/fjall.git)",
6052
+
"fjall 2.11.2 (git+https://github.com/fjall-rs/fjall.git?rev=fb229572bb7d1d6966a596994dc1708e47ec57d8)",
6053
6053
"getrandom 0.3.3",
6054
6054
"http",
6055
6055
"jetstream",
+19
-3
constellation/src/bin/main.rs
+19
-3
constellation/src/bin/main.rs
···
26
26
#[arg(long)]
27
27
#[clap(default_value = "0.0.0.0:6789")]
28
28
bind: SocketAddr,
29
+
/// optionally disable the metrics server
30
+
#[arg(long)]
31
+
#[clap(default_value_t = false)]
32
+
collect_metrics: bool,
29
33
/// metrics server's listen address
30
34
#[arg(long)]
31
35
#[clap(default_value = "0.0.0.0:8765")]
···
92
96
let bind = args.bind;
93
97
let metrics_bind = args.bind_metrics;
94
98
99
+
let collect_metrics = args.collect_metrics;
95
100
let stay_alive = CancellationToken::new();
96
101
97
102
match args.backend {
···
102
107
stream,
103
108
bind,
104
109
metrics_bind,
110
+
collect_metrics,
105
111
stay_alive,
106
112
),
107
113
#[cfg(feature = "rocks")]
···
136
142
stream,
137
143
bind,
138
144
metrics_bind,
145
+
collect_metrics,
139
146
stay_alive,
140
147
);
141
148
eprintln!("run finished: {r:?}");
···
147
154
}
148
155
}
149
156
157
+
#[allow(clippy::too_many_lines)]
158
+
#[allow(clippy::too_many_arguments)]
150
159
fn run(
151
160
mut storage: impl LinkStorage,
152
161
fixture: Option<PathBuf>,
···
154
163
stream: String,
155
164
bind: SocketAddr,
156
165
metrics_bind: SocketAddr,
166
+
collect_metrics: bool,
157
167
stay_alive: CancellationToken,
158
168
) -> Result<()> {
159
169
ctrlc::set_handler({
···
198
208
.build()
199
209
.expect("axum startup")
200
210
.block_on(async {
201
-
install_metrics_server(metrics_bind)?;
211
+
// Install metrics server only if requested
212
+
if collect_metrics {
213
+
install_metrics_server(metrics_bind)?;
214
+
}
202
215
serve(readable, bind, staying_alive).await
203
216
})
204
217
.unwrap();
···
206
219
}
207
220
});
208
221
209
-
s.spawn(move || { // monitor thread
222
+
// only spawn monitoring thread if the metrics server is running
223
+
if collect_metrics {
224
+
s.spawn(move || { // monitor thread
210
225
let stay_alive = stay_alive.clone();
211
226
let check_alive = stay_alive.clone();
212
227
···
258
273
}
259
274
}
260
275
stay_alive.drop_guard();
261
-
});
276
+
});
277
+
}
262
278
});
263
279
264
280
println!("byeeee");
+13
-6
constellation/src/consumer/jetstream.rs
+13
-6
constellation/src/consumer/jetstream.rs
···
226
226
println!("jetstream closed the websocket cleanly.");
227
227
break;
228
228
}
229
-
r => eprintln!("jetstream: close result after error: {r:?}"),
229
+
Err(_) => {
230
+
counter!("jetstream_read_fail", "url" => stream.clone(), "reason" => "dirty close").increment(1);
231
+
println!("jetstream failed to close the websocket cleanly.");
232
+
break;
233
+
}
234
+
Ok(r) => {
235
+
eprintln!("jetstream: close result after error: {r:?}");
236
+
counter!("jetstream_read_fail", "url" => stream.clone(), "reason" => "read error")
237
+
.increment(1);
238
+
// if we didn't immediately get ConnectionClosed, we should keep polling read
239
+
// until we get it.
240
+
continue;
241
+
}
230
242
}
231
-
counter!("jetstream_read_fail", "url" => stream.clone(), "reason" => "read error")
232
-
.increment(1);
233
-
// if we didn't immediately get ConnectionClosed, we should keep polling read
234
-
// until we get it.
235
-
continue;
236
243
}
237
244
};
238
245
+2
-2
constellation/src/server/mod.rs
+2
-2
constellation/src/server/mod.rs
···
25
25
26
26
use acceptable::{acceptable, ExtractAccept};
27
27
28
-
const DEFAULT_CURSOR_LIMIT: u64 = 16;
29
-
const DEFAULT_CURSOR_LIMIT_MAX: u64 = 100;
28
+
const DEFAULT_CURSOR_LIMIT: u64 = 100;
29
+
const DEFAULT_CURSOR_LIMIT_MAX: u64 = 1000;
30
30
31
31
fn get_default_cursor_limit() -> u64 {
32
32
DEFAULT_CURSOR_LIMIT
+1
-1
constellation/templates/dids.html.j2
+1
-1
constellation/templates/dids.html.j2
···
27
27
{% for did in linking_dids %}
28
28
<pre style="display: block; margin: 1em 2em" class="code"><strong>DID</strong>: {{ did.0 }}
29
29
-> see <a href="/links/all?target={{ did.0|urlencode }}">links to this DID</a>
30
-
-> browse <a href="https://pdsls.dev/at://{{ did.0|urlencode }}">this DID record</a></pre>
30
+
-> browse <a href="https://pdsls.dev/at://{{ did.0 }}">this DID record</a></pre>
31
31
{% endfor %}
32
32
33
33
{% if let Some(c) = cursor %}
+95
lexicons/blue.microcosm/links/getBacklinks.json
+95
lexicons/blue.microcosm/links/getBacklinks.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "blue.microcosm.links.getBacklinks",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "a list of records linking to any record, identity, or uri",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"subject",
12
+
"source"
13
+
],
14
+
"properties": {
15
+
"subject": {
16
+
"type": "string",
17
+
"format": "uri",
18
+
"description": "the target being linked to (at-uri, did, or uri)"
19
+
},
20
+
"source": {
21
+
"type": "string",
22
+
"description": "collection and path specification (e.g., 'app.bsky.feed.like:subject.uri')"
23
+
},
24
+
"did": {
25
+
"type": "array",
26
+
"description": "filter links to those from specific users",
27
+
"items": {
28
+
"type": "string",
29
+
"format": "did"
30
+
}
31
+
},
32
+
"limit": {
33
+
"type": "integer",
34
+
"minimum": 1,
35
+
"maximum": 100,
36
+
"default": 16,
37
+
"description": "number of results to return"
38
+
}
39
+
}
40
+
},
41
+
"output": {
42
+
"encoding": "application/json",
43
+
"schema": {
44
+
"type": "object",
45
+
"required": [
46
+
"total",
47
+
"records"
48
+
],
49
+
"properties": {
50
+
"total": {
51
+
"type": "integer",
52
+
"description": "total number of matching links"
53
+
},
54
+
"records": {
55
+
"type": "array",
56
+
"items": {
57
+
"type": "ref",
58
+
"ref": "#linkRecord"
59
+
}
60
+
},
61
+
"cursor": {
62
+
"type": "string",
63
+
"description": "pagination cursor"
64
+
}
65
+
}
66
+
}
67
+
}
68
+
},
69
+
"linkRecord": {
70
+
"type": "object",
71
+
"required": [
72
+
"did",
73
+
"collection",
74
+
"rkey"
75
+
],
76
+
"properties": {
77
+
"did": {
78
+
"type": "string",
79
+
"format": "did",
80
+
"description": "the DID of the linking record's repository"
81
+
},
82
+
"collection": {
83
+
"type": "string",
84
+
"format": "nsid",
85
+
"description": "the collection of the linking record"
86
+
},
87
+
"rkey": {
88
+
"type": "string",
89
+
"format": "record-key",
90
+
"description": "the record key of the linking record"
91
+
}
92
+
}
93
+
}
94
+
}
95
+
}
+99
lexicons/blue.microcosm/links/getManyToManyCounts.json
+99
lexicons/blue.microcosm/links/getManyToManyCounts.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "blue.microcosm.links.getManyToManyCounts",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "count many-to-many relationships with secondary link paths",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"subject",
12
+
"source",
13
+
"pathToOther"
14
+
],
15
+
"properties": {
16
+
"subject": {
17
+
"type": "string",
18
+
"format": "uri",
19
+
"description": "the primary target being linked to (at-uri, did, or uri)"
20
+
},
21
+
"source": {
22
+
"type": "string",
23
+
"description": "collection and path specification for the primary link"
24
+
},
25
+
"pathToOther": {
26
+
"type": "string",
27
+
"description": "path to the secondary link in the many-to-many record (e.g., 'otherThing.uri')"
28
+
},
29
+
"did": {
30
+
"type": "array",
31
+
"description": "filter links to those from specific users",
32
+
"items": {
33
+
"type": "string",
34
+
"format": "did"
35
+
}
36
+
},
37
+
"otherSubject": {
38
+
"type": "array",
39
+
"description": "filter secondary links to specific subjects",
40
+
"items": {
41
+
"type": "string"
42
+
}
43
+
},
44
+
"limit": {
45
+
"type": "integer",
46
+
"minimum": 1,
47
+
"maximum": 100,
48
+
"default": 16,
49
+
"description": "number of results to return"
50
+
}
51
+
}
52
+
},
53
+
"output": {
54
+
"encoding": "application/json",
55
+
"schema": {
56
+
"type": "object",
57
+
"required": [
58
+
"counts_by_other_subject"
59
+
],
60
+
"properties": {
61
+
"counts_by_other_subject": {
62
+
"type": "array",
63
+
"items": {
64
+
"type": "ref",
65
+
"ref": "#countBySubject"
66
+
}
67
+
},
68
+
"cursor": {
69
+
"type": "string",
70
+
"description": "pagination cursor"
71
+
}
72
+
}
73
+
}
74
+
}
75
+
},
76
+
"countBySubject": {
77
+
"type": "object",
78
+
"required": [
79
+
"subject",
80
+
"total",
81
+
"distinct"
82
+
],
83
+
"properties": {
84
+
"subject": {
85
+
"type": "string",
86
+
"description": "the secondary subject being counted"
87
+
},
88
+
"total": {
89
+
"type": "integer",
90
+
"description": "total number of links to this subject"
91
+
},
92
+
"distinct": {
93
+
"type": "integer",
94
+
"description": "number of distinct DIDs linking to this subject"
95
+
}
96
+
}
97
+
}
98
+
}
99
+
}
+56
lexicons/com.bad-example/identity/resolveMiniDoc.json
+56
lexicons/com.bad-example/identity/resolveMiniDoc.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.bad-example.identity.resolveMiniDoc",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "like com.atproto.identity.resolveIdentity but instead of the full didDoc it returns an atproto-relevant subset",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"identifier"
12
+
],
13
+
"properties": {
14
+
"identifier": {
15
+
"type": "string",
16
+
"format": "at-identifier",
17
+
"description": "handle or DID to resolve"
18
+
}
19
+
}
20
+
},
21
+
"output": {
22
+
"encoding": "application/json",
23
+
"schema": {
24
+
"type": "object",
25
+
"required": [
26
+
"did",
27
+
"handle",
28
+
"pds",
29
+
"signing_key"
30
+
],
31
+
"properties": {
32
+
"did": {
33
+
"type": "string",
34
+
"format": "did",
35
+
"description": "DID, bi-directionally verified if a handle was provided in the query"
36
+
},
37
+
"handle": {
38
+
"type": "string",
39
+
"format": "handle",
40
+
"description": "the validated handle of the account or 'handle.invalid' if the handle did not bi-directionally match the DID document"
41
+
},
42
+
"pds": {
43
+
"type": "string",
44
+
"format": "uri",
45
+
"description": "the identity's PDS URL"
46
+
},
47
+
"signing_key": {
48
+
"type": "string",
49
+
"description": "the atproto signing key publicKeyMultibase"
50
+
}
51
+
}
52
+
}
53
+
}
54
+
}
55
+
}
56
+
}
+54
lexicons/com.bad-example/repo/getUriRecord.json
+54
lexicons/com.bad-example/repo/getUriRecord.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.bad-example.repo.getUriRecord",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "ergonomic complement to com.atproto.repo.getRecord which accepts an at-uri instead of individual repo/collection/rkey params",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"at_uri"
12
+
],
13
+
"properties": {
14
+
"at_uri": {
15
+
"type": "string",
16
+
"format": "at-uri",
17
+
"description": "the at-uri of the record (identifier can be a DID or handle)"
18
+
},
19
+
"cid": {
20
+
"type": "string",
21
+
"format": "cid",
22
+
"description": "optional CID of the version of the record. if not specified, return the most recent version. if specified and a newer version exists, returns 404."
23
+
}
24
+
}
25
+
},
26
+
"output": {
27
+
"encoding": "application/json",
28
+
"schema": {
29
+
"type": "object",
30
+
"required": [
31
+
"uri",
32
+
"value"
33
+
],
34
+
"properties": {
35
+
"uri": {
36
+
"type": "string",
37
+
"format": "at-uri",
38
+
"description": "at-uri for this record"
39
+
},
40
+
"cid": {
41
+
"type": "string",
42
+
"format": "cid",
43
+
"description": "CID for this exact version of the record"
44
+
},
45
+
"value": {
46
+
"type": "unknown",
47
+
"description": "the record itself"
48
+
}
49
+
}
50
+
}
51
+
}
52
+
}
53
+
}
54
+
}
+2
spacedust/src/error.rs
+2
spacedust/src/error.rs
···
30
30
TooManySourcesWanted,
31
31
#[error("more wantedSubjectDids were requested than allowed (max 10,000)")]
32
32
TooManyDidsWanted,
33
+
#[error("more wantedSubjectPrefixes were requested than allowed (max 100)")]
34
+
TooManySubjectPrefixesWanted,
33
35
#[error("more wantedSubjects were requested than allowed (max 50,000)")]
34
36
TooManySubjectsWanted,
35
37
}
+11
-2
spacedust/src/server.rs
+11
-2
spacedust/src/server.rs
···
227
227
#[serde(default)]
228
228
pub wanted_subjects: HashSet<String>,
229
229
#[serde(default)]
230
+
pub wanted_subject_prefixes: HashSet<String>,
231
+
#[serde(default)]
230
232
pub wanted_subject_dids: HashSet<String>,
231
233
#[serde(default)]
232
234
pub wanted_sources: HashSet<String>,
···
241
243
///
242
244
/// The at-uri must be url-encoded
243
245
///
244
-
/// Pass this parameter multiple times to specify multiple collections, like
246
+
/// Pass this parameter multiple times to specify multiple subjects, like
245
247
/// `wantedSubjects=[...]&wantedSubjects=[...]`
246
248
pub wanted_subjects: String,
249
+
/// One or more at-uri, URI, or DID prefixes to receive links about
250
+
///
251
+
/// The uri must be url-encoded
252
+
///
253
+
/// Pass this parameter multiple times to specify multiple prefixes, like
254
+
/// `wantedSubjectPrefixes=[...]&wantedSubjectPrefixes=[...]`
255
+
pub wanted_subject_prefixes: String,
247
256
/// One or more DIDs to receive links about
248
257
///
249
-
/// Pass this parameter multiple times to specify multiple collections
258
+
/// Pass this parameter multiple times to specify multiple subjects
250
259
pub wanted_subject_dids: String,
251
260
/// One or more link sources to receive links about
252
261
///
+10
-1
spacedust/src/subscriber.rs
+10
-1
spacedust/src/subscriber.rs
···
124
124
let query = &self.query;
125
125
126
126
// subject + subject DIDs are logical OR
127
-
if !(query.wanted_subjects.is_empty() && query.wanted_subject_dids.is_empty()
127
+
if !(query.wanted_subjects.is_empty()
128
+
&& query.wanted_subject_prefixes.is_empty()
129
+
&& query.wanted_subject_dids.is_empty()
128
130
|| query.wanted_subjects.contains(&properties.subject)
131
+
|| query
132
+
.wanted_subject_prefixes
133
+
.iter()
134
+
.any(|p| properties.subject.starts_with(p))
129
135
|| properties
130
136
.subject_did
131
137
.as_ref()
···
154
160
}
155
161
if opts.wanted_subject_dids.len() > 10_000 {
156
162
return Err(SubscriberUpdateError::TooManyDidsWanted);
163
+
}
164
+
if opts.wanted_subject_prefixes.len() > 100 {
165
+
return Err(SubscriberUpdateError::TooManySubjectPrefixesWanted);
157
166
}
158
167
if opts.wanted_subjects.len() > 50_000 {
159
168
return Err(SubscriberUpdateError::TooManySubjectsWanted);
+1
-1
ufos/Cargo.toml
+1
-1
ufos/Cargo.toml
···
13
13
clap = { version = "4.5.31", features = ["derive"] }
14
14
dropshot = "0.16.0"
15
15
env_logger = "0.11.7"
16
-
fjall = { git = "https://github.com/fjall-rs/fjall.git", features = ["lz4"] }
16
+
fjall = { git = "https://github.com/fjall-rs/fjall.git", rev = "fb229572bb7d1d6966a596994dc1708e47ec57d8", features = ["lz4"] }
17
17
getrandom = "0.3.3"
18
18
http = "1.3.1"
19
19
jetstream = { path = "../jetstream", features = ["metrics"] }