@fedify/fedify 2.3.0-dev.1158 → 2.3.0-dev.1172
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.
- package/dist/{builder-B66L9i5E.mjs → builder-JoFBmqfM.mjs} +2 -2
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{deno-O_rwum1q.mjs → deno-Cb_y5qEi.mjs} +1 -1
- package/dist/{docloader-Ct8PhKFS.mjs → docloader-Bv4TW6eo.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +305 -3
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +80 -1
- package/dist/federation/middleware.test.mjs +20 -6
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-BoRhhcgB.mjs → http--aE0vk2u.mjs} +3 -3
- package/dist/{http-CFP8WMMv.js → http-C0XZv7iH.js} +92 -2
- package/dist/{http-DlPd_LYM.cjs → http-D_HNhC57.cjs} +115 -1
- package/dist/{key-DyATZSWG.mjs → key-Cl_bixZo.mjs} +2 -2
- package/dist/{kv-cache-BJo6COYN.cjs → kv-cache-CdOuPFgC.cjs} +1 -1
- package/dist/{kv-cache-DeJE8EeD.mjs → kv-cache-DQUblF4f.mjs} +1 -1
- package/dist/{kv-cache-dH0biV98.js → kv-cache-DsbVBK7Y.js} +1 -1
- package/dist/{ld-B8wjsKDJ.mjs → ld-xVq6y31b.mjs} +3 -3
- package/dist/{metrics-oMUWaw6W.mjs → metrics-CKticT28.mjs} +92 -2
- package/dist/{middleware-DwZ1ofL9.js → middleware-BSuEI4Qf.js} +318 -107
- package/dist/{middleware-BzOa0ncb.mjs → middleware-BmPIKmb4.mjs} +1 -1
- package/dist/{middleware-xtTRaiJL.mjs → middleware-DHM2Pjqf.mjs} +327 -116
- package/dist/{middleware-CcJyVEpv.cjs → middleware-hxnyAewn.cjs} +318 -107
- package/dist/mod.cjs +4 -4
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-BxjgK8PG.mjs → owner-DmU2qEh_.mjs} +2 -2
- package/dist/{proof-42Q9NiqN.mjs → proof-1XBgQ0Z0.mjs} +3 -3
- package/dist/{proof-B_6gAVQ2.js → proof-CmS6yxgt.js} +1 -1
- package/dist/{proof-CfttNzWW.cjs → proof-wm6UxUoM.cjs} +1 -1
- package/dist/{send-R1_K46CH.mjs → send-CmtB8w5D.mjs} +3 -3
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/{temporal-8kDX3E4q.mjs → temporal-DE9_a2nI.mjs} +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
|
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./chunk-CRNNMoPX.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
|
|
5
|
-
import { C as recordWebFingerHandle,
|
|
6
|
-
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-
|
|
5
|
+
import { C as recordFanoutRecipients, D as recordWebFingerHandle, E as recordOutboxEnqueue, M as name, N as version, O as formatAcceptSignature, T as recordOutboxActivity, a as verifyRequestDetailed, b as recordCollectionRequest, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCollectionDispatchDuration, w as recordInboxActivity, x as recordCollectionTotalItems, y as recordCollectionPageItems } from "./http-C0XZv7iH.js";
|
|
6
|
+
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-CmS6yxgt.js";
|
|
7
7
|
import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DsbVBK7Y.js";
|
|
9
9
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
10
10
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
@@ -1211,6 +1211,33 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
1211
1211
|
Vary: "Accept"
|
|
1212
1212
|
} });
|
|
1213
1213
|
}
|
|
1214
|
+
const BUILT_IN_COLLECTION_METRIC_KINDS = new Set([
|
|
1215
|
+
"inbox",
|
|
1216
|
+
"outbox",
|
|
1217
|
+
"following",
|
|
1218
|
+
"followers",
|
|
1219
|
+
"liked",
|
|
1220
|
+
"featured",
|
|
1221
|
+
"featured_tags"
|
|
1222
|
+
]);
|
|
1223
|
+
function getCollectionMetricKind(name) {
|
|
1224
|
+
const normalized = name.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/\s+/g, "_");
|
|
1225
|
+
return BUILT_IN_COLLECTION_METRIC_KINDS.has(normalized) ? normalized : "custom";
|
|
1226
|
+
}
|
|
1227
|
+
function collectionAttributes(base, result, response) {
|
|
1228
|
+
return {
|
|
1229
|
+
...base,
|
|
1230
|
+
result,
|
|
1231
|
+
...response == null ? {} : { statusCode: response.status }
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
1235
|
+
const attrs = collectionAttributes(base, result, options.response);
|
|
1236
|
+
recordCollectionRequest(meterProvider, attrs);
|
|
1237
|
+
if (options.dispatchDurationMs != null) recordCollectionDispatchDuration(meterProvider, options.dispatchDurationMs, attrs);
|
|
1238
|
+
if (options.itemCount != null) recordCollectionPageItems(meterProvider, options.itemCount, attrs);
|
|
1239
|
+
if (options.totalItems != null) recordCollectionTotalItems(meterProvider, options.totalItems, attrs);
|
|
1240
|
+
}
|
|
1214
1241
|
/**
|
|
1215
1242
|
* Handles a collection request.
|
|
1216
1243
|
* @template TItem The type of items in the collection.
|
|
@@ -1221,36 +1248,118 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
1221
1248
|
* @param parameters The parameters for handling the collection.
|
|
1222
1249
|
* @returns A promise that resolves to an HTTP response.
|
|
1223
1250
|
*/
|
|
1224
|
-
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
|
|
1251
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
1225
1252
|
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
1226
1253
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
1227
1254
|
const tracer = tracerProvider.getTracer(name, version);
|
|
1228
1255
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1256
|
+
const metricBase = {
|
|
1257
|
+
kind: getCollectionMetricKind(name$1),
|
|
1258
|
+
page: cursor != null,
|
|
1259
|
+
dispatcher: "built_in"
|
|
1260
|
+
};
|
|
1261
|
+
let dispatchDurationMs;
|
|
1262
|
+
let itemCount;
|
|
1263
|
+
let totalItemCount;
|
|
1264
|
+
const finish = (response, result) => {
|
|
1265
|
+
recordCollectionMetrics(meterProvider, metricBase, result, {
|
|
1266
|
+
response,
|
|
1267
|
+
dispatchDurationMs,
|
|
1268
|
+
itemCount,
|
|
1269
|
+
totalItems: totalItemCount
|
|
1270
|
+
});
|
|
1271
|
+
return response;
|
|
1272
|
+
};
|
|
1273
|
+
try {
|
|
1274
|
+
if (collectionCallbacks == null) return finish(await onNotFound(request), "not_found");
|
|
1275
|
+
let collection;
|
|
1276
|
+
const baseUri = uriGetter(identifier);
|
|
1277
|
+
if (cursor == null) {
|
|
1278
|
+
const firstCursor = await collectionCallbacks.firstCursor?.(context, identifier);
|
|
1279
|
+
const totalItems = filter == null ? await collectionCallbacks.counter?.(context, identifier) : void 0;
|
|
1280
|
+
totalItemCount = totalItems == null ? void 0 : Number(totalItems);
|
|
1281
|
+
if (firstCursor == null) {
|
|
1282
|
+
const itemsOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection ${spanName}`, {
|
|
1283
|
+
kind: SpanKind.SERVER,
|
|
1284
|
+
attributes: {
|
|
1285
|
+
"activitypub.collection.id": baseUri.href,
|
|
1286
|
+
"activitypub.collection.type": OrderedCollection.typeId.href
|
|
1287
|
+
}
|
|
1288
|
+
}, async (span) => {
|
|
1289
|
+
if (totalItemCount != null) span.setAttribute("activitypub.collection.total_items", totalItemCount);
|
|
1290
|
+
const started = performance.now();
|
|
1291
|
+
try {
|
|
1292
|
+
const page = await collectionCallbacks.dispatcher(context, identifier, null, filter);
|
|
1293
|
+
dispatchDurationMs = getDurationMs(started);
|
|
1294
|
+
if (page == null) {
|
|
1295
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1296
|
+
return await onNotFound(request);
|
|
1297
|
+
}
|
|
1298
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
1299
|
+
itemCount = items.length;
|
|
1300
|
+
span.setAttribute("fedify.collection.items", itemCount);
|
|
1301
|
+
return items;
|
|
1302
|
+
} catch (e) {
|
|
1303
|
+
if (dispatchDurationMs == null) dispatchDurationMs = getDurationMs(started);
|
|
1304
|
+
span.setStatus({
|
|
1305
|
+
code: SpanStatusCode.ERROR,
|
|
1306
|
+
message: String(e)
|
|
1307
|
+
});
|
|
1308
|
+
throw e;
|
|
1309
|
+
} finally {
|
|
1310
|
+
span.end();
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
if (itemsOrResponse instanceof Response) return finish(itemsOrResponse, "not_found");
|
|
1314
|
+
collection = new OrderedCollection({
|
|
1315
|
+
id: baseUri,
|
|
1316
|
+
totalItems: totalItemCount ?? null,
|
|
1317
|
+
items: itemsOrResponse
|
|
1318
|
+
});
|
|
1319
|
+
} else {
|
|
1320
|
+
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
1321
|
+
const first = new URL(context.url);
|
|
1322
|
+
first.searchParams.set("cursor", firstCursor);
|
|
1323
|
+
let last = null;
|
|
1324
|
+
if (lastCursor != null) {
|
|
1325
|
+
last = new URL(context.url);
|
|
1326
|
+
last.searchParams.set("cursor", lastCursor);
|
|
1327
|
+
}
|
|
1328
|
+
collection = new OrderedCollection({
|
|
1329
|
+
id: baseUri,
|
|
1330
|
+
totalItems: totalItemCount ?? null,
|
|
1331
|
+
first,
|
|
1332
|
+
last
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
} else {
|
|
1336
|
+
const uri = new URL(baseUri);
|
|
1337
|
+
uri.searchParams.set("cursor", cursor);
|
|
1338
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
1237
1339
|
kind: SpanKind.SERVER,
|
|
1238
1340
|
attributes: {
|
|
1239
|
-
"activitypub.collection.id":
|
|
1240
|
-
"activitypub.collection.type":
|
|
1341
|
+
"activitypub.collection.id": uri.href,
|
|
1342
|
+
"activitypub.collection.type": OrderedCollectionPage.typeId.href,
|
|
1343
|
+
"fedify.collection.cursor": cursor
|
|
1241
1344
|
}
|
|
1242
1345
|
}, async (span) => {
|
|
1243
|
-
|
|
1346
|
+
const started = performance.now();
|
|
1244
1347
|
try {
|
|
1245
|
-
const page = await collectionCallbacks.dispatcher(context, identifier,
|
|
1348
|
+
const page = await collectionCallbacks.dispatcher(context, identifier, cursor, filter);
|
|
1349
|
+
dispatchDurationMs = getDurationMs(started);
|
|
1246
1350
|
if (page == null) {
|
|
1247
1351
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1248
1352
|
return await onNotFound(request);
|
|
1249
1353
|
}
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1354
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
1355
|
+
itemCount = items.length;
|
|
1356
|
+
span.setAttribute("fedify.collection.items", itemCount);
|
|
1357
|
+
return {
|
|
1358
|
+
...page,
|
|
1359
|
+
items
|
|
1360
|
+
};
|
|
1253
1361
|
} catch (e) {
|
|
1362
|
+
if (dispatchDurationMs == null) dispatchDurationMs = getDurationMs(started);
|
|
1254
1363
|
span.setStatus({
|
|
1255
1364
|
code: SpanStatusCode.ERROR,
|
|
1256
1365
|
message: String(e)
|
|
@@ -1260,87 +1369,44 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
1260
1369
|
span.end();
|
|
1261
1370
|
}
|
|
1262
1371
|
});
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
} else {
|
|
1270
|
-
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
1271
|
-
const first = new URL(context.url);
|
|
1272
|
-
first.searchParams.set("cursor", firstCursor);
|
|
1273
|
-
let last = null;
|
|
1274
|
-
if (lastCursor != null) {
|
|
1275
|
-
last = new URL(context.url);
|
|
1276
|
-
last.searchParams.set("cursor", lastCursor);
|
|
1277
|
-
}
|
|
1278
|
-
collection = new OrderedCollection({
|
|
1279
|
-
id: baseUri,
|
|
1280
|
-
totalItems: totalItems == null ? null : Number(totalItems),
|
|
1281
|
-
first,
|
|
1282
|
-
last
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
} else {
|
|
1286
|
-
const uri = new URL(baseUri);
|
|
1287
|
-
uri.searchParams.set("cursor", cursor);
|
|
1288
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
1289
|
-
kind: SpanKind.SERVER,
|
|
1290
|
-
attributes: {
|
|
1291
|
-
"activitypub.collection.id": uri.href,
|
|
1292
|
-
"activitypub.collection.type": OrderedCollectionPage.typeId.href,
|
|
1293
|
-
"fedify.collection.cursor": cursor
|
|
1372
|
+
if (pageOrResponse instanceof Response) return finish(pageOrResponse, "not_found");
|
|
1373
|
+
const { items, prevCursor, nextCursor } = pageOrResponse;
|
|
1374
|
+
let prev = null;
|
|
1375
|
+
if (prevCursor != null) {
|
|
1376
|
+
prev = new URL(context.url);
|
|
1377
|
+
prev.searchParams.set("cursor", prevCursor);
|
|
1294
1378
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1300
|
-
return await onNotFound(request);
|
|
1301
|
-
}
|
|
1302
|
-
span.setAttribute("fedify.collection.items", page.items.length);
|
|
1303
|
-
return page;
|
|
1304
|
-
} catch (e) {
|
|
1305
|
-
span.setStatus({
|
|
1306
|
-
code: SpanStatusCode.ERROR,
|
|
1307
|
-
message: String(e)
|
|
1308
|
-
});
|
|
1309
|
-
throw e;
|
|
1310
|
-
} finally {
|
|
1311
|
-
span.end();
|
|
1379
|
+
let next = null;
|
|
1380
|
+
if (nextCursor != null) {
|
|
1381
|
+
next = new URL(context.url);
|
|
1382
|
+
next.searchParams.set("cursor", nextCursor);
|
|
1312
1383
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1384
|
+
const partOf = new URL(context.url);
|
|
1385
|
+
partOf.searchParams.delete("cursor");
|
|
1386
|
+
collection = new OrderedCollectionPage({
|
|
1387
|
+
id: uri,
|
|
1388
|
+
prev,
|
|
1389
|
+
next,
|
|
1390
|
+
items,
|
|
1391
|
+
partOf
|
|
1392
|
+
});
|
|
1320
1393
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
next = new URL(context.url);
|
|
1324
|
-
next.searchParams.set("cursor", nextCursor);
|
|
1394
|
+
if (collectionCallbacks.authorizePredicate != null) {
|
|
1395
|
+
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
1325
1396
|
}
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1397
|
+
const jsonLd = await collection.toJsonLd(context);
|
|
1398
|
+
return finish(new Response(JSON.stringify(jsonLd), { headers: {
|
|
1399
|
+
"Content-Type": "application/activity+json",
|
|
1400
|
+
Vary: "Accept"
|
|
1401
|
+
} }), "served");
|
|
1402
|
+
} catch (e) {
|
|
1403
|
+
recordCollectionMetrics(meterProvider, metricBase, "error", {
|
|
1404
|
+
dispatchDurationMs,
|
|
1405
|
+
itemCount,
|
|
1406
|
+
totalItems: totalItemCount
|
|
1334
1407
|
});
|
|
1408
|
+
throw e;
|
|
1335
1409
|
}
|
|
1336
|
-
if (collectionCallbacks.authorizePredicate != null) {
|
|
1337
|
-
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return await onUnauthorized(request);
|
|
1338
|
-
}
|
|
1339
|
-
const jsonLd = await collection.toJsonLd(context);
|
|
1340
|
-
return new Response(JSON.stringify(jsonLd), { headers: {
|
|
1341
|
-
"Content-Type": "application/activity+json",
|
|
1342
|
-
Vary: "Accept"
|
|
1343
|
-
} });
|
|
1344
1410
|
}
|
|
1345
1411
|
/**
|
|
1346
1412
|
* Filters collection items based on the provided predicate.
|
|
@@ -2043,11 +2109,31 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
2043
2109
|
* @since 1.8.0
|
|
2044
2110
|
*/
|
|
2045
2111
|
const handleCustomCollection = exceptWrapper(_handleCustomCollection);
|
|
2046
|
-
|
|
2112
|
+
const pendingCollectionMetricRecorders = /* @__PURE__ */ new WeakMap();
|
|
2113
|
+
function deferPendingCollectionMetrics(error, recorder) {
|
|
2114
|
+
if (error == null || typeof error !== "object" && typeof error !== "function") return false;
|
|
2115
|
+
pendingCollectionMetricRecorders.set(error, recorder);
|
|
2116
|
+
return true;
|
|
2117
|
+
}
|
|
2118
|
+
function recordDeferredPendingCollectionMetrics(error, result, response) {
|
|
2119
|
+
if (error == null || typeof error !== "object" && typeof error !== "function") return;
|
|
2120
|
+
const recorder = pendingCollectionMetricRecorders.get(error);
|
|
2121
|
+
pendingCollectionMetricRecorders.delete(error);
|
|
2122
|
+
recorder?.(result, response);
|
|
2123
|
+
}
|
|
2124
|
+
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
2047
2125
|
verifyDefined(callbacks);
|
|
2048
2126
|
await authIfNeeded(context, values, callbacks);
|
|
2049
2127
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
2050
|
-
|
|
2128
|
+
const handler = new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, Collection, CollectionPage, filterPredicate).fetchCollection(cursor);
|
|
2129
|
+
try {
|
|
2130
|
+
const response = await handler.toJsonLd().then(respondAsActivity);
|
|
2131
|
+
handler.recordPendingCollectionMetrics("served", response);
|
|
2132
|
+
return response;
|
|
2133
|
+
} catch (e) {
|
|
2134
|
+
if (!deferPendingCollectionMetrics(e, (result, response) => handler.recordPendingCollectionMetrics(result, response))) handler.recordPendingCollectionMetrics("error");
|
|
2135
|
+
throw e;
|
|
2136
|
+
}
|
|
2051
2137
|
}
|
|
2052
2138
|
/**
|
|
2053
2139
|
* Handles an ordered collection request.
|
|
@@ -2061,11 +2147,19 @@ async function _handleCustomCollection(request, { name, values, context, tracerP
|
|
|
2061
2147
|
* @since 1.8.0
|
|
2062
2148
|
*/
|
|
2063
2149
|
const handleOrderedCollection = exceptWrapper(_handleOrderedCollection);
|
|
2064
|
-
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
2150
|
+
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
2065
2151
|
verifyDefined(callbacks);
|
|
2066
2152
|
await authIfNeeded(context, values, callbacks);
|
|
2067
2153
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
2068
|
-
|
|
2154
|
+
const handler = new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, OrderedCollection, OrderedCollectionPage, filterPredicate).fetchCollection(cursor);
|
|
2155
|
+
try {
|
|
2156
|
+
const response = await handler.toJsonLd().then(respondAsActivity);
|
|
2157
|
+
handler.recordPendingCollectionMetrics("served", response);
|
|
2158
|
+
return response;
|
|
2159
|
+
} catch (e) {
|
|
2160
|
+
if (!deferPendingCollectionMetrics(e, (result, response) => handler.recordPendingCollectionMetrics(result, response))) handler.recordPendingCollectionMetrics("error");
|
|
2161
|
+
throw e;
|
|
2162
|
+
}
|
|
2069
2163
|
}
|
|
2070
2164
|
/**
|
|
2071
2165
|
* Handling custom collections with support for pagination and filtering.
|
|
@@ -2085,6 +2179,7 @@ var CustomCollectionHandler = class {
|
|
|
2085
2179
|
context;
|
|
2086
2180
|
callbacks;
|
|
2087
2181
|
tracerProvider;
|
|
2182
|
+
meterProvider;
|
|
2088
2183
|
Collection;
|
|
2089
2184
|
CollectionPage;
|
|
2090
2185
|
filterPredicate;
|
|
@@ -2112,6 +2207,7 @@ var CustomCollectionHandler = class {
|
|
|
2112
2207
|
*/
|
|
2113
2208
|
#dispatcher;
|
|
2114
2209
|
#collection = null;
|
|
2210
|
+
#pendingCollectionMetrics = [];
|
|
2115
2211
|
/**
|
|
2116
2212
|
* Creates a new CustomCollection instance.
|
|
2117
2213
|
* @param name The name of the collection.
|
|
@@ -2123,12 +2219,13 @@ var CustomCollectionHandler = class {
|
|
|
2123
2219
|
* @param CollectionPage The CollectionPage constructor.
|
|
2124
2220
|
* @param filterPredicate Optional filter predicate for items.
|
|
2125
2221
|
*/
|
|
2126
|
-
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
|
|
2222
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
|
|
2127
2223
|
this.name = name$2;
|
|
2128
2224
|
this.values = values;
|
|
2129
2225
|
this.context = context;
|
|
2130
2226
|
this.callbacks = callbacks;
|
|
2131
2227
|
this.tracerProvider = tracerProvider;
|
|
2228
|
+
this.meterProvider = meterProvider;
|
|
2132
2229
|
this.Collection = Collection;
|
|
2133
2230
|
this.CollectionPage = CollectionPage;
|
|
2134
2231
|
this.filterPredicate = filterPredicate;
|
|
@@ -2181,10 +2278,12 @@ var CustomCollectionHandler = class {
|
|
|
2181
2278
|
const { prevCursor, nextCursor } = pages;
|
|
2182
2279
|
const partOf = new URL(id);
|
|
2183
2280
|
partOf.searchParams.delete("cursor");
|
|
2281
|
+
const items = this.filterItems(pages.items);
|
|
2282
|
+
this.recordPendingCollectionItemCount(true, items.length);
|
|
2184
2283
|
return {
|
|
2185
2284
|
id,
|
|
2186
2285
|
partOf,
|
|
2187
|
-
items
|
|
2286
|
+
items,
|
|
2188
2287
|
prev: this.appendToUrl(prevCursor),
|
|
2189
2288
|
next: this.appendToUrl(nextCursor)
|
|
2190
2289
|
};
|
|
@@ -2197,11 +2296,16 @@ var CustomCollectionHandler = class {
|
|
|
2197
2296
|
*/
|
|
2198
2297
|
async getProps(firstCursor) {
|
|
2199
2298
|
const lastCursor = await this.callbacks.lastCursor?.(this.context, this.values);
|
|
2299
|
+
const totalItems = await this.totalItems;
|
|
2300
|
+
if (totalItems != null) this.#pendingCollectionMetrics.push({
|
|
2301
|
+
page: false,
|
|
2302
|
+
totalItems: Number(totalItems)
|
|
2303
|
+
});
|
|
2200
2304
|
return {
|
|
2201
2305
|
id: this.#id,
|
|
2202
2306
|
first: this.appendToUrl(firstCursor),
|
|
2203
2307
|
last: this.appendToUrl(lastCursor),
|
|
2204
|
-
totalItems
|
|
2308
|
+
totalItems
|
|
2205
2309
|
};
|
|
2206
2310
|
}
|
|
2207
2311
|
/**
|
|
@@ -2211,10 +2315,12 @@ var CustomCollectionHandler = class {
|
|
|
2211
2315
|
async getPropsWithoutCursor() {
|
|
2212
2316
|
const totalItems = await this.totalItems;
|
|
2213
2317
|
const pages = await this.getPages({ totalItems });
|
|
2318
|
+
const items = this.filterItems(pages.items);
|
|
2319
|
+
this.recordPendingCollectionItemCount(false, items.length);
|
|
2214
2320
|
return {
|
|
2215
2321
|
id: this.#id,
|
|
2216
2322
|
totalItems,
|
|
2217
|
-
items
|
|
2323
|
+
items
|
|
2218
2324
|
};
|
|
2219
2325
|
}
|
|
2220
2326
|
/**
|
|
@@ -2249,12 +2355,24 @@ var CustomCollectionHandler = class {
|
|
|
2249
2355
|
* @returns A function that handles the span operation.
|
|
2250
2356
|
*/
|
|
2251
2357
|
spanPages = ({ totalItems = null, cursor = null }) => async (span) => {
|
|
2358
|
+
const pageMetricBase = this.metricBase(cursor !== null);
|
|
2359
|
+
const started = performance.now();
|
|
2252
2360
|
try {
|
|
2253
2361
|
if (totalItems !== null) span.setAttribute(this.ATTRS.TOTAL_ITEMS, totalItems);
|
|
2254
2362
|
const page = await this.dispatch(cursor);
|
|
2363
|
+
const durationMs = getDurationMs(started);
|
|
2255
2364
|
span.setAttribute(this.ATTRS.ITEMS, page.items.length);
|
|
2365
|
+
this.#pendingCollectionMetrics.push({
|
|
2366
|
+
page: pageMetricBase.page,
|
|
2367
|
+
dispatchDurationMs: durationMs,
|
|
2368
|
+
totalItems: totalItems == null ? void 0 : Number(totalItems)
|
|
2369
|
+
});
|
|
2256
2370
|
return page;
|
|
2257
2371
|
} catch (e) {
|
|
2372
|
+
this.#pendingCollectionMetrics.push({
|
|
2373
|
+
page: cursor !== null,
|
|
2374
|
+
dispatchDurationMs: getDurationMs(started)
|
|
2375
|
+
});
|
|
2258
2376
|
const message = e instanceof Error ? e.message : String(e);
|
|
2259
2377
|
span.setStatus({
|
|
2260
2378
|
code: SpanStatusCode.ERROR,
|
|
@@ -2281,6 +2399,37 @@ var CustomCollectionHandler = class {
|
|
|
2281
2399
|
filterItems(items) {
|
|
2282
2400
|
return filterCollectionItems(items, this.name, this.filterPredicate);
|
|
2283
2401
|
}
|
|
2402
|
+
metricBase(page) {
|
|
2403
|
+
return {
|
|
2404
|
+
kind: "custom",
|
|
2405
|
+
page,
|
|
2406
|
+
dispatcher: "custom"
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
metricAttributes(page, result, response) {
|
|
2410
|
+
return collectionAttributes(this.metricBase(page), result, response);
|
|
2411
|
+
}
|
|
2412
|
+
recordPendingCollectionItemCount(page, itemCount) {
|
|
2413
|
+
for (let i = this.#pendingCollectionMetrics.length - 1; i >= 0; i--) {
|
|
2414
|
+
const measurement = this.#pendingCollectionMetrics[i];
|
|
2415
|
+
if (measurement.page === page && measurement.dispatchDurationMs != null && measurement.itemCount == null) {
|
|
2416
|
+
measurement.itemCount = itemCount;
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
this.#pendingCollectionMetrics.push({
|
|
2421
|
+
page,
|
|
2422
|
+
itemCount
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
recordPendingCollectionMetrics(result, response) {
|
|
2426
|
+
for (const measurement of this.#pendingCollectionMetrics.splice(0)) {
|
|
2427
|
+
const attrs = this.metricAttributes(measurement.page, result, response);
|
|
2428
|
+
if (measurement.dispatchDurationMs != null) recordCollectionDispatchDuration(this.meterProvider, measurement.dispatchDurationMs, attrs);
|
|
2429
|
+
if (measurement.itemCount != null) recordCollectionPageItems(this.meterProvider, measurement.itemCount, attrs);
|
|
2430
|
+
if (measurement.totalItems != null) recordCollectionTotalItems(this.meterProvider, measurement.totalItems, attrs);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2284
2433
|
/**
|
|
2285
2434
|
* Appends a cursor to the URL if it exists.
|
|
2286
2435
|
* @param cursor The cursor to append, or null/undefined.
|
|
@@ -2344,14 +2493,36 @@ var CustomCollectionHandler = class {
|
|
|
2344
2493
|
*/
|
|
2345
2494
|
function exceptWrapper(handler) {
|
|
2346
2495
|
return async (request, handlerParams) => {
|
|
2496
|
+
const page = new URL(request.url).searchParams.get("cursor") != null;
|
|
2497
|
+
const { meterProvider } = handlerParams;
|
|
2498
|
+
const metricBase = {
|
|
2499
|
+
kind: "custom",
|
|
2500
|
+
page,
|
|
2501
|
+
dispatcher: "custom"
|
|
2502
|
+
};
|
|
2347
2503
|
try {
|
|
2348
|
-
|
|
2504
|
+
const response = await handler(request, handlerParams);
|
|
2505
|
+
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "served", response));
|
|
2506
|
+
return response;
|
|
2349
2507
|
} catch (error) {
|
|
2350
2508
|
const { onNotFound, onUnauthorized } = handlerParams;
|
|
2351
2509
|
switch (error?.constructor) {
|
|
2352
|
-
case ItemsNotFoundError:
|
|
2353
|
-
|
|
2354
|
-
|
|
2510
|
+
case ItemsNotFoundError: {
|
|
2511
|
+
const response = await onNotFound(request);
|
|
2512
|
+
recordDeferredPendingCollectionMetrics(error, "not_found", response);
|
|
2513
|
+
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "not_found", response));
|
|
2514
|
+
return response;
|
|
2515
|
+
}
|
|
2516
|
+
case UnauthorizedError: {
|
|
2517
|
+
const response = await onUnauthorized(request);
|
|
2518
|
+
recordDeferredPendingCollectionMetrics(error, "unauthorized", response);
|
|
2519
|
+
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "unauthorized", response));
|
|
2520
|
+
return response;
|
|
2521
|
+
}
|
|
2522
|
+
default:
|
|
2523
|
+
recordDeferredPendingCollectionMetrics(error, "error");
|
|
2524
|
+
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "error"));
|
|
2525
|
+
throw error;
|
|
2355
2526
|
}
|
|
2356
2527
|
}
|
|
2357
2528
|
};
|
|
@@ -4028,7 +4199,15 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4028
4199
|
}
|
|
4029
4200
|
if (request.method !== "POST" && !acceptsJsonLd(request)) {
|
|
4030
4201
|
metricState.endpoint = "not_acceptable";
|
|
4031
|
-
|
|
4202
|
+
const response = await onNotAcceptable(request);
|
|
4203
|
+
const collectionRoute = getCollectionMetricRoute(routeName);
|
|
4204
|
+
if (collectionRoute != null) recordCollectionRequest(this._meterProvider, {
|
|
4205
|
+
...collectionRoute,
|
|
4206
|
+
page: url.searchParams.get("cursor") != null,
|
|
4207
|
+
result: "not_acceptable",
|
|
4208
|
+
statusCode: response.status
|
|
4209
|
+
});
|
|
4210
|
+
return response;
|
|
4032
4211
|
}
|
|
4033
4212
|
switch (routeName) {
|
|
4034
4213
|
case "actor":
|
|
@@ -4089,6 +4268,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4089
4268
|
context,
|
|
4090
4269
|
collectionCallbacks: this.outboxCallbacks,
|
|
4091
4270
|
tracerProvider: this.tracerProvider,
|
|
4271
|
+
meterProvider: this._meterProvider,
|
|
4092
4272
|
onUnauthorized,
|
|
4093
4273
|
onNotFound
|
|
4094
4274
|
});
|
|
@@ -4100,6 +4280,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4100
4280
|
context,
|
|
4101
4281
|
collectionCallbacks: this.inboxCallbacks,
|
|
4102
4282
|
tracerProvider: this.tracerProvider,
|
|
4283
|
+
meterProvider: this._meterProvider,
|
|
4103
4284
|
onUnauthorized,
|
|
4104
4285
|
onNotFound
|
|
4105
4286
|
});
|
|
@@ -4139,6 +4320,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4139
4320
|
context,
|
|
4140
4321
|
collectionCallbacks: this.followingCallbacks,
|
|
4141
4322
|
tracerProvider: this.tracerProvider,
|
|
4323
|
+
meterProvider: this._meterProvider,
|
|
4142
4324
|
onUnauthorized,
|
|
4143
4325
|
onNotFound
|
|
4144
4326
|
});
|
|
@@ -4162,6 +4344,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4162
4344
|
filterPredicate: baseUrl != null ? ((i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl)) : void 0,
|
|
4163
4345
|
collectionCallbacks: this.followersCallbacks,
|
|
4164
4346
|
tracerProvider: this.tracerProvider,
|
|
4347
|
+
meterProvider: this._meterProvider,
|
|
4165
4348
|
onUnauthorized,
|
|
4166
4349
|
onNotFound
|
|
4167
4350
|
});
|
|
@@ -4173,6 +4356,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4173
4356
|
context,
|
|
4174
4357
|
collectionCallbacks: this.likedCallbacks,
|
|
4175
4358
|
tracerProvider: this.tracerProvider,
|
|
4359
|
+
meterProvider: this._meterProvider,
|
|
4176
4360
|
onUnauthorized,
|
|
4177
4361
|
onNotFound
|
|
4178
4362
|
});
|
|
@@ -4183,6 +4367,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4183
4367
|
context,
|
|
4184
4368
|
collectionCallbacks: this.featuredCallbacks,
|
|
4185
4369
|
tracerProvider: this.tracerProvider,
|
|
4370
|
+
meterProvider: this._meterProvider,
|
|
4186
4371
|
onUnauthorized,
|
|
4187
4372
|
onNotFound
|
|
4188
4373
|
});
|
|
@@ -4193,6 +4378,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4193
4378
|
context,
|
|
4194
4379
|
collectionCallbacks: this.featuredTagsCallbacks,
|
|
4195
4380
|
tracerProvider: this.tracerProvider,
|
|
4381
|
+
meterProvider: this._meterProvider,
|
|
4196
4382
|
onUnauthorized,
|
|
4197
4383
|
onNotFound
|
|
4198
4384
|
});
|
|
@@ -4205,6 +4391,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4205
4391
|
values: route.values,
|
|
4206
4392
|
collectionCallbacks: callbacks,
|
|
4207
4393
|
tracerProvider: this.tracerProvider,
|
|
4394
|
+
meterProvider: this._meterProvider,
|
|
4208
4395
|
onUnauthorized,
|
|
4209
4396
|
onNotFound
|
|
4210
4397
|
});
|
|
@@ -4218,6 +4405,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4218
4405
|
values: route.values,
|
|
4219
4406
|
collectionCallbacks: callbacks,
|
|
4220
4407
|
tracerProvider: this.tracerProvider,
|
|
4408
|
+
meterProvider: this._meterProvider,
|
|
4221
4409
|
onUnauthorized,
|
|
4222
4410
|
onNotFound
|
|
4223
4411
|
});
|
|
@@ -4250,6 +4438,29 @@ function getEndpointCategory(routeName) {
|
|
|
4250
4438
|
default: return "not_found";
|
|
4251
4439
|
}
|
|
4252
4440
|
}
|
|
4441
|
+
function getCollectionMetricRoute(routeName) {
|
|
4442
|
+
switch (routeName) {
|
|
4443
|
+
case "inbox":
|
|
4444
|
+
case "outbox":
|
|
4445
|
+
case "following":
|
|
4446
|
+
case "followers":
|
|
4447
|
+
case "liked":
|
|
4448
|
+
case "featured": return {
|
|
4449
|
+
kind: routeName,
|
|
4450
|
+
dispatcher: "built_in"
|
|
4451
|
+
};
|
|
4452
|
+
case "featuredTags": return {
|
|
4453
|
+
kind: "featured_tags",
|
|
4454
|
+
dispatcher: "built_in"
|
|
4455
|
+
};
|
|
4456
|
+
case "collection":
|
|
4457
|
+
case "orderedCollection": return {
|
|
4458
|
+
kind: "custom",
|
|
4459
|
+
dispatcher: "custom"
|
|
4460
|
+
};
|
|
4461
|
+
default: return;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4253
4464
|
const FANOUT_THRESHOLD = 5;
|
|
4254
4465
|
var ContextImpl = class ContextImpl {
|
|
4255
4466
|
url;
|