@fedify/vocab 2.4.0-dev.2129 → 2.4.0-dev.2149

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.
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Object$1, B as QuoteAuthorization, C as Intent, D as Measure, E as Link, F as Person, G as Tombstone, H as Reject, I as Place, L as PropertyValue, N as OrderedCollectionPage, O as Multikey, P as Organization, R as Proposal, S as Image, T as InteractionRule, U as Service, V as QuoteRequest, W as Source, X as vocab_exports, _ as FeaturedCollection, a as Application, b as Group, d as CryptographicKey, f as Delete, g as FeatureRequest, h as FeatureAuthorization, i as Announce, j as Offer, k as Note, l as Commitment, m as Endpoints, n as Activity, p as Document, r as Agreement, s as Collection, t as Accept, u as Create, v as FeaturedItem, w as InteractionPolicy, x as Hashtag, y as Follow, z as Question } from "./vocab-DFtGFGsb.mjs";
4
- import { t as esm_default } from "./esm-CU6UbTlZ.mjs";
3
+ import { A as Object$1, B as QuoteAuthorization, C as Intent, D as Measure, E as Link, F as Person, G as Tombstone, H as Reject, I as Place, L as PropertyValue, N as OrderedCollectionPage, O as Multikey, P as Organization, R as Proposal, S as Image, T as InteractionRule, U as Service, V as QuoteRequest, W as Source, X as vocab_exports, _ as FeaturedCollection, a as Application, b as Group, d as CryptographicKey, f as Delete, g as FeatureRequest, h as FeatureAuthorization, i as Announce, j as Offer, k as Note, l as Commitment, m as Endpoints, n as Activity, p as Document, r as Agreement, s as Collection, t as Accept, u as Create, v as FeaturedItem, w as InteractionPolicy, x as Hashtag, y as Follow, z as Question } from "./vocab-CVEaA48h.mjs";
4
+ import { t as esm_default } from "./esm-CxYTzURx.mjs";
5
5
  import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
6
6
  import { mockDocumentLoader, test } from "@fedify/fixture";
7
7
  import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.4.0-dev.2129+c0ddb433",
3
+ "version": "2.4.0-dev.2149+e2302034",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,9 +43,9 @@
43
43
  "jsonld": "^9.0.0",
44
44
  "pkijs": "^3.3.3",
45
45
  "temporal-polyfill": "^1.0.1",
46
- "@fedify/vocab-runtime": "2.4.0-dev.2129+c0ddb433",
47
- "@fedify/webfinger": "2.4.0-dev.2129+c0ddb433",
48
- "@fedify/vocab-tools": "2.4.0-dev.2129+c0ddb433"
46
+ "@fedify/vocab-tools": "2.4.0-dev.2149+e2302034",
47
+ "@fedify/vocab-runtime": "2.4.0-dev.2149+e2302034",
48
+ "@fedify/webfinger": "2.4.0-dev.2149+e2302034"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^24.12.2",
@@ -2113,48 +2113,34 @@ test("objects use the verifyPortableObject option they were parsed with by defau
2113
2113
  deepStrictEqual(verifier.calls.length, 2);
2114
2114
  });
2115
2115
 
2116
- await t.step("an explicit option applies to that call only", async () => {
2117
- const stored = createRecordingVerifier();
2118
- const given = createRecordingVerifier();
2119
- const activity = await createCompatibleActivity(
2120
- compatibleNoteId,
2121
- undefined,
2122
- { verifyPortableObject: stored },
2123
- );
2124
- const object = await activity.getObject({
2125
- documentLoader,
2126
- contextLoader: mockDocumentLoader,
2127
- verifyPortableObject: given,
2128
- });
2129
- assertInstanceOf(object, Note);
2130
- deepStrictEqual([stored.calls.length, given.calls.length], [0, 1]);
2131
- // The fetched object inherits the parent's default, not the verifier
2132
- // given to the call:
2133
- await object.getAttribution({
2134
- documentLoader,
2135
- contextLoader: mockDocumentLoader,
2136
- gateways: ["https://gw.example"],
2137
- });
2138
- deepStrictEqual([stored.calls.length, given.calls.length], [1, 1]);
2139
-
2140
- // Without a default, the fetched object has none either:
2141
- const plain = await createCompatibleActivity(compatibleNoteId);
2142
- const object2 = await plain.getObject({
2143
- documentLoader,
2144
- contextLoader: mockDocumentLoader,
2145
- verifyPortableObject: given,
2146
- });
2147
- assertInstanceOf(object2, Note);
2148
- await rejects(
2149
- async () =>
2150
- await object2.getAttribution({
2151
- documentLoader,
2152
- contextLoader: mockDocumentLoader,
2153
- gateways: ["https://gw.example"],
2154
- }),
2155
- TypeError,
2156
- );
2157
- });
2116
+ await t.step(
2117
+ "an explicit option is passed on to fetched objects",
2118
+ async () => {
2119
+ const stored = createRecordingVerifier();
2120
+ const given = createRecordingVerifier();
2121
+ const activity = await createCompatibleActivity(
2122
+ compatibleNoteId,
2123
+ undefined,
2124
+ { verifyPortableObject: stored },
2125
+ );
2126
+ const object = await activity.getObject({
2127
+ documentLoader,
2128
+ contextLoader: mockDocumentLoader,
2129
+ verifyPortableObject: given,
2130
+ });
2131
+ assertInstanceOf(object, Note);
2132
+ deepStrictEqual([stored.calls.length, given.calls.length], [0, 1]);
2133
+ // The fetched object uses the verifier given to the call by default:
2134
+ await object.getAttribution({
2135
+ documentLoader,
2136
+ contextLoader: mockDocumentLoader,
2137
+ gateways: ["https://gw.example"],
2138
+ });
2139
+ deepStrictEqual([stored.calls.length, given.calls.length], [0, 2]);
2140
+ // ...but the parent keeps its own default:
2141
+ ok(getDefaultVerifier(activity) === stored);
2142
+ },
2143
+ );
2158
2144
 
2159
2145
  await t.step("lookupObject()", async () => {
2160
2146
  const verifier = createRecordingVerifier();
@@ -2186,9 +2172,430 @@ test("objects use the verifyPortableObject option they were parsed with by defau
2186
2172
  }, { contextLoader: mockDocumentLoader, verifyPortableObject: verifier });
2187
2173
  const icon = await person.getIcon({ documentLoader });
2188
2174
  assertInstanceOf(icon, Image);
2189
- ok(
2190
- (icon as unknown as { _verifyPortableObject: unknown })
2191
- ._verifyPortableObject === verifier,
2175
+ ok(getDefaultVerifier(icon) === verifier);
2176
+ });
2177
+ });
2178
+
2179
+ function getDefaultVerifier(
2180
+ object: object,
2181
+ ): PortableObjectVerifier | undefined {
2182
+ return (object as unknown as {
2183
+ _verifyPortableObject?: PortableObjectVerifier;
2184
+ })
2185
+ ._verifyPortableObject;
2186
+ }
2187
+
2188
+ test("accessors pass the verifier of a call on to the objects they fetch", async (t) => {
2189
+ const actorId = `ap://${did}/actor`;
2190
+ const attributedNote = (id: string) => ({
2191
+ ...note(id),
2192
+ attributedTo: actorId,
2193
+ });
2194
+ const actor = {
2195
+ "@context": "https://www.w3.org/ns/activitystreams",
2196
+ id: actorId,
2197
+ type: "Person",
2198
+ inbox: `${actorId}/inbox`,
2199
+ };
2200
+ const actorUrl = gatewayUrl("https://gw.example", "/actor");
2201
+ const plainNoteUrl = "https://example.com/notes/1";
2202
+ const plainActivityUrl = "https://example.com/activities/1";
2203
+ const documentLoader = createLoader({
2204
+ [compatibleNoteId]: attributedNote(compatibleNoteId),
2205
+ [actorUrl]: actor,
2206
+ [plainNoteUrl]: { ...attributedNote(plainNoteUrl), id: plainNoteUrl },
2207
+ [plainActivityUrl]: {
2208
+ "@context": "https://www.w3.org/ns/activitystreams",
2209
+ type: "Create",
2210
+ id: plainActivityUrl,
2211
+ object: compatibleNoteId,
2212
+ },
2213
+ });
2214
+ const options = { documentLoader, contextLoader: mockDocumentLoader };
2215
+ const secondHop = { ...options, gateways: ["https://gw.example"] };
2216
+
2217
+ await t.step("from objects without a default", async () => {
2218
+ const roots: [string, () => Promise<Create>][] = [
2219
+ [
2220
+ "constructed",
2221
+ // deno-lint-ignore require-await
2222
+ async () =>
2223
+ new Create({
2224
+ id: new URL(gatewayUrl("https://gw.example", "/activities/1")),
2225
+ object: new URL(compatibleNoteId),
2226
+ }, { contextLoader: mockDocumentLoader }),
2227
+ ],
2228
+ ["parsed", () => createCompatibleActivity(compatibleNoteId)],
2229
+ [
2230
+ "looked up",
2231
+ async () => {
2232
+ const found = await lookupObject(plainActivityUrl, options);
2233
+ assertInstanceOf(found, Create);
2234
+ return found;
2235
+ },
2236
+ ],
2237
+ ];
2238
+ for (const [name, createRoot] of roots) {
2239
+ // Singular accessors:
2240
+ const verifier = createRecordingVerifier();
2241
+ const root = await createRoot();
2242
+ ok(getDefaultVerifier(root) == null, name);
2243
+ const object = await root.getObject({
2244
+ ...options,
2245
+ verifyPortableObject: verifier,
2246
+ });
2247
+ assertInstanceOf(object, Note);
2248
+ deepStrictEqual(verifier.calls.length, 1, name);
2249
+ assertInstanceOf(await object.getAttribution(secondHop), Person);
2250
+ deepStrictEqual(verifier.calls.length, 2, name);
2251
+ ok(getDefaultVerifier(root) == null, name);
2252
+
2253
+ // Plural accessors:
2254
+ const verifier2 = createRecordingVerifier();
2255
+ const objects = await Array.fromAsync((await createRoot()).getObjects({
2256
+ ...options,
2257
+ verifyPortableObject: verifier2,
2258
+ }));
2259
+ deepStrictEqual(objects.length, 1, name);
2260
+ assertInstanceOf(objects[0], Note);
2261
+ const authors = await Array.fromAsync(
2262
+ (objects[0] as Note).getAttributions(secondHop),
2263
+ );
2264
+ deepStrictEqual(authors.length, 1, name);
2265
+ assertInstanceOf(authors[0], Person);
2266
+ deepStrictEqual(verifier2.calls.length, 2, name);
2267
+ }
2268
+ });
2269
+
2270
+ await t.step("precedence", async () => {
2271
+ const stored = createRecordingVerifier();
2272
+ const given = createRecordingVerifier();
2273
+ const override = createRecordingVerifier();
2274
+ const counts = () => [
2275
+ stored.calls.length,
2276
+ given.calls.length,
2277
+ override.calls.length,
2278
+ ];
2279
+
2280
+ // Without the option, the stored default applies to the call and is
2281
+ // passed on:
2282
+ const byDefault = await (await createCompatibleActivity(
2283
+ compatibleNoteId,
2284
+ undefined,
2285
+ { verifyPortableObject: stored },
2286
+ )).getObject({ ...options, inheritPortableObjectVerifier: false });
2287
+ assertInstanceOf(byDefault, Note);
2288
+ ok(getDefaultVerifier(byDefault) === stored);
2289
+ deepStrictEqual(counts(), [1, 0, 0]);
2290
+
2291
+ // With the opt-out, the given verifier applies to the call only, and the
2292
+ // fetched object gets the stored default:
2293
+ const optedOut = await (await createCompatibleActivity(
2294
+ compatibleNoteId,
2295
+ undefined,
2296
+ { verifyPortableObject: stored },
2297
+ )).getObject({
2298
+ ...options,
2299
+ verifyPortableObject: given,
2300
+ inheritPortableObjectVerifier: false,
2301
+ });
2302
+ assertInstanceOf(optedOut, Note);
2303
+ deepStrictEqual(counts(), [1, 1, 0]);
2304
+ ok(getDefaultVerifier(optedOut) === stored);
2305
+ assertInstanceOf(await optedOut.getAttribution(secondHop), Person);
2306
+ deepStrictEqual(counts(), [2, 1, 0]);
2307
+
2308
+ // Without a stored default, the fetched object gets none:
2309
+ const noDefault = await (await createCompatibleActivity(compatibleNoteId))
2310
+ .getObject({
2311
+ ...options,
2312
+ verifyPortableObject: given,
2313
+ inheritPortableObjectVerifier: false,
2314
+ });
2315
+ assertInstanceOf(noDefault, Note);
2316
+ deepStrictEqual(counts(), [2, 2, 0]);
2317
+ ok(getDefaultVerifier(noDefault) == null);
2318
+ await rejects(
2319
+ async () => await noDefault.getAttribution(secondHop),
2320
+ TypeError,
2321
+ );
2322
+
2323
+ // The opt-out itself is not passed on, and an explicit option given to
2324
+ // the fetched object overrides its inherited default:
2325
+ const inherited = await (await createCompatibleActivity(
2326
+ compatibleNoteId,
2327
+ undefined,
2328
+ { verifyPortableObject: stored },
2329
+ )).getObject({ ...options, verifyPortableObject: given });
2330
+ assertInstanceOf(inherited, Note);
2331
+ deepStrictEqual(counts(), [2, 3, 0]);
2332
+ ok(getDefaultVerifier(inherited) === given);
2333
+ assertInstanceOf(
2334
+ await inherited.getAttribution({
2335
+ ...secondHop,
2336
+ verifyPortableObject: override,
2337
+ }),
2338
+ Person,
2339
+ );
2340
+ deepStrictEqual(counts(), [2, 3, 1]);
2341
+
2342
+ // The opt-out works for plural accessors too:
2343
+ const optedOuts = await Array.fromAsync((await createCompatibleActivity(
2344
+ [compatibleNoteId],
2345
+ undefined,
2346
+ { verifyPortableObject: stored },
2347
+ )).getObjects({
2348
+ ...options,
2349
+ verifyPortableObject: given,
2350
+ inheritPortableObjectVerifier: false,
2351
+ }));
2352
+ deepStrictEqual(optedOuts.length, 1);
2353
+ ok(getDefaultVerifier(optedOuts[0]) === stored);
2354
+ });
2355
+
2356
+ await t.step("ordinary HTTP(S) responses", async () => {
2357
+ const given = createRecordingVerifier();
2358
+ const stored = createRecordingVerifier();
2359
+ const object = await (await createHttpActivity(plainNoteUrl)).getObject({
2360
+ ...options,
2361
+ verifyPortableObject: given,
2362
+ });
2363
+ assertInstanceOf(object, Note);
2364
+ // Fetched as an ordinary object, so nothing is verified, but it uses the
2365
+ // call's verifier for its own references:
2366
+ deepStrictEqual(given.calls.length, 0);
2367
+ ok(getDefaultVerifier(object) === given);
2368
+ assertInstanceOf(await object.getAttribution(secondHop), Person);
2369
+ deepStrictEqual(given.calls.length, 1);
2370
+ // Having inherited a verifier does not mean it was verified:
2371
+ const { referrer } = given.calls[0].options;
2372
+ ok(referrer?.object === object);
2373
+ deepStrictEqual(referrer?.acceptance, undefined);
2374
+
2375
+ const optedOut = await (await Create.fromJsonLd({
2376
+ "@context": "https://www.w3.org/ns/activitystreams",
2377
+ type: "Create",
2378
+ id: plainActivityUrl,
2379
+ object: plainNoteUrl,
2380
+ }, { contextLoader: mockDocumentLoader, verifyPortableObject: stored }))
2381
+ .getObject({
2382
+ ...options,
2383
+ verifyPortableObject: given,
2384
+ inheritPortableObjectVerifier: false,
2385
+ });
2386
+ assertInstanceOf(optedOut, Note);
2387
+ ok(getDefaultVerifier(optedOut) === stored);
2388
+ });
2389
+
2390
+ await t.step(
2391
+ "HTTP(S) responses that stand for portable objects",
2392
+ async () => {
2393
+ const redirecting = createRedirectingLoader({
2394
+ [plainNoteUrl]: {
2395
+ documentUrl: compatibleNoteId,
2396
+ document: attributedNote(compatibleNoteId),
2397
+ },
2398
+ });
2399
+ const given = createRecordingVerifier();
2400
+ const object = await (await createHttpActivity(plainNoteUrl)).getObject({
2401
+ documentLoader: redirecting,
2402
+ contextLoader: mockDocumentLoader,
2403
+ verifyPortableObject: given,
2404
+ });
2405
+ assertInstanceOf(object, Note);
2406
+ deepStrictEqual(given.calls.length, 1);
2407
+ ok(getDefaultVerifier(object) === given);
2408
+
2409
+ const optedOut = await (await createHttpActivity(plainNoteUrl)).getObject(
2410
+ {
2411
+ documentLoader: redirecting,
2412
+ contextLoader: mockDocumentLoader,
2413
+ verifyPortableObject: given,
2414
+ inheritPortableObjectVerifier: false,
2415
+ },
2416
+ );
2417
+ assertInstanceOf(optedOut, Note);
2418
+ deepStrictEqual(given.calls.length, 2);
2419
+ ok(getDefaultVerifier(optedOut) == null);
2420
+ },
2421
+ );
2422
+
2423
+ await t.step("objects that were fetched without a verifier", async () => {
2424
+ const activity = await createHttpActivity(compatibleNoteId);
2425
+ const unverified = await activity.getObject(options);
2426
+ assertInstanceOf(unverified, Note);
2427
+ ok(getDefaultVerifier(unverified) == null);
2428
+ // It is not cached, so a later call verifies it and passes its verifier
2429
+ // on:
2430
+ const verifier = createRecordingVerifier();
2431
+ const verified = await activity.getObject({
2432
+ ...options,
2433
+ verifyPortableObject: verifier,
2434
+ });
2435
+ assertInstanceOf(verified, Note);
2436
+ ok(verified !== unverified);
2437
+ deepStrictEqual(verifier.calls.length, 1);
2438
+ ok(getDefaultVerifier(verified) === verifier);
2439
+ });
2440
+
2441
+ await t.step("property preprocessors", async () => {
2442
+ const iconUrl = "https://example.com/icon";
2443
+ const loader = createLoader({
2444
+ [iconUrl]: {
2445
+ "@context": "https://www.w3.org/ns/activitystreams",
2446
+ type: "Link",
2447
+ href: "https://example.com/icon.png",
2448
+ },
2449
+ });
2450
+ const createPerson = () =>
2451
+ Person.fromJsonLd({
2452
+ "@context": "https://www.w3.org/ns/activitystreams",
2453
+ id: "https://example.com/person",
2454
+ type: "Person",
2455
+ icon: iconUrl,
2456
+ }, { contextLoader: mockDocumentLoader });
2457
+ const verifier = createVerifier();
2458
+ const icon = await (await createPerson()).getIcon({
2459
+ documentLoader: loader,
2460
+ contextLoader: mockDocumentLoader,
2461
+ verifyPortableObject: verifier,
2462
+ });
2463
+ assertInstanceOf(icon, Image);
2464
+ ok(getDefaultVerifier(icon) === verifier);
2465
+ const optedOut = await (await createPerson()).getIcon({
2466
+ documentLoader: loader,
2467
+ contextLoader: mockDocumentLoader,
2468
+ verifyPortableObject: verifier,
2469
+ inheritPortableObjectVerifier: false,
2470
+ });
2471
+ assertInstanceOf(optedOut, Image);
2472
+ ok(getDefaultVerifier(optedOut) == null);
2473
+ });
2474
+
2475
+ await t.step("embedded objects keep the parent's default", async () => {
2476
+ const embedded = attributedNote(`ap://${did}/objects/2`);
2477
+ const withoutContext: Record<string, unknown> = { ...embedded };
2478
+ delete withoutContext["@context"];
2479
+ for (
2480
+ const [name, value] of [["with @context", embedded], [
2481
+ "without @context",
2482
+ withoutContext,
2483
+ ]] as const
2484
+ ) {
2485
+ for (const stored of [undefined, createRecordingVerifier()]) {
2486
+ const given = createRecordingVerifier();
2487
+ const activity = await createCompatibleActivity(
2488
+ value,
2489
+ `ap://${did}/activities/1`,
2490
+ stored == null ? {} : { verifyPortableObject: stored },
2491
+ );
2492
+ const object = await activity.getObject({
2493
+ ...options,
2494
+ verifyPortableObject: given,
2495
+ });
2496
+ assertInstanceOf(object, Note);
2497
+ // However the object is represented, it keeps the default that it
2498
+ // was parsed with along with its parent:
2499
+ ok(getDefaultVerifier(object) === stored, name);
2500
+ deepStrictEqual(given.calls.length, 0, name);
2501
+ }
2502
+ }
2503
+ });
2504
+
2505
+ await t.step("cached objects", async () => {
2506
+ const weak = createRecordingVerifier();
2507
+ const strict = createRecordingVerifier();
2508
+ for (const [first, second] of [[weak, strict], [strict, weak]]) {
2509
+ const activity = await createCompatibleActivity(compatibleNoteId);
2510
+ const object = await activity.getObject({
2511
+ ...options,
2512
+ verifyPortableObject: first,
2513
+ });
2514
+ assertInstanceOf(object, Note);
2515
+ const before = second.calls.length;
2516
+ // A cached object is returned as is, with the default it got when it
2517
+ // was fetched:
2518
+ const cached = await activity.getObject({
2519
+ ...options,
2520
+ verifyPortableObject: second,
2521
+ });
2522
+ ok(cached === object);
2523
+ ok(getDefaultVerifier(cached) === first);
2524
+ deepStrictEqual(second.calls.length, before);
2525
+ }
2526
+ });
2527
+
2528
+ await t.step("traverseCollection()", async () => {
2529
+ const collectionUrl = "https://example.com/collection";
2530
+ const pageUrl = "https://example.com/collection?page=1";
2531
+ const loader = createLoader({
2532
+ ...Object.fromEntries(
2533
+ [compatibleNoteId, actorUrl, plainNoteUrl].map((url) => [
2534
+ url,
2535
+ url === plainNoteUrl
2536
+ ? { ...attributedNote(plainNoteUrl), id: plainNoteUrl }
2537
+ : url === actorUrl
2538
+ ? actor
2539
+ : attributedNote(compatibleNoteId),
2540
+ ]),
2541
+ ),
2542
+ [pageUrl]: {
2543
+ "@context": "https://www.w3.org/ns/activitystreams",
2544
+ id: pageUrl,
2545
+ type: "OrderedCollectionPage",
2546
+ orderedItems: [
2547
+ {
2548
+ id: "https://example.com/notes/2",
2549
+ type: "Note",
2550
+ attributedTo: actorId,
2551
+ },
2552
+ plainNoteUrl,
2553
+ compatibleNoteId,
2554
+ ],
2555
+ },
2556
+ });
2557
+ const createCollection = () =>
2558
+ OrderedCollection.fromJsonLd({
2559
+ "@context": "https://www.w3.org/ns/activitystreams",
2560
+ id: collectionUrl,
2561
+ type: "OrderedCollection",
2562
+ first: pageUrl,
2563
+ }, { contextLoader: mockDocumentLoader });
2564
+ const verifier = createRecordingVerifier();
2565
+ const items = await Array.fromAsync(
2566
+ traverseCollection(await createCollection(), {
2567
+ documentLoader: loader,
2568
+ contextLoader: mockDocumentLoader,
2569
+ verifyPortableObject: verifier,
2570
+ }),
2571
+ );
2572
+ deepStrictEqual(items.length, 3);
2573
+ // Only the item at the compatible identifier is verified:
2574
+ deepStrictEqual(verifier.calls.length, 1);
2575
+ for (const item of items) {
2576
+ assertInstanceOf(item, Note);
2577
+ // Embedded in a fetched page, or fetched themselves:
2578
+ ok(getDefaultVerifier(item) === verifier);
2579
+ assertInstanceOf(
2580
+ await item.getAttribution({
2581
+ documentLoader: loader,
2582
+ contextLoader: mockDocumentLoader,
2583
+ gateways: ["https://gw.example"],
2584
+ }),
2585
+ Person,
2586
+ );
2587
+ }
2588
+ deepStrictEqual(verifier.calls.length, 4);
2589
+
2590
+ const optedOut = await Array.fromAsync(
2591
+ traverseCollection(await createCollection(), {
2592
+ documentLoader: loader,
2593
+ contextLoader: mockDocumentLoader,
2594
+ verifyPortableObject: verifier,
2595
+ inheritPortableObjectVerifier: false,
2596
+ }),
2192
2597
  );
2598
+ deepStrictEqual(optedOut.length, 3);
2599
+ for (const item of optedOut) ok(getDefaultVerifier(item) == null);
2193
2600
  });
2194
2601
  });
package/src/lookup.ts CHANGED
@@ -737,10 +737,24 @@ export interface TraverseCollectionOptions {
737
737
  /**
738
738
  * The policy to apply to portable pages and items fetched through
739
739
  * gateways, typically `verifyPortableObject()` from `@fedify/fedify`.
740
- * Portable pages and items are not fetched without it.
740
+ * Portable pages and items are not fetched without it. Pages and items
741
+ * that are fetched use it as their default verifier, unless
742
+ * {@link TraverseCollectionOptions.inheritPortableObjectVerifier} is
743
+ * `false`.
741
744
  * @since 2.4.0
742
745
  */
743
746
  verifyPortableObject?: PortableObjectVerifier;
747
+
748
+ /**
749
+ * Whether pages and items that are newly fetched use
750
+ * {@link TraverseCollectionOptions.verifyPortableObject} as their default
751
+ * verifier for their property accessors. Defaults to `true`. If `false`,
752
+ * they use the default verifier of the object they are obtained from, if
753
+ * any, instead. See the `inheritPortableObjectVerifier` option of property
754
+ * accessors such as `Collection.getItems()`.
755
+ * @since 2.4.0
756
+ */
757
+ inheritPortableObjectVerifier?: boolean;
744
758
  }
745
759
 
746
760
  /**