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