diff --git a/CHANGES.md b/CHANGES.md index d180feb33..51abfe65f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,12 +10,18 @@ To be released. ### @fedify/testing + - Fixed `createFederation()` ignoring `allowPrivateAddress: true`. Received + activities and federation-created contexts now use document loaders that + allow local servers, so calls such as `follow.getActor()` can resolve + localhost actors in inbox listeners. [[#1302], [#1306]] - Fixed missing runtime dependencies in the npm package. `@fedify/vocab` and `@js-temporal/polyfill` are now installed automatically so importing `@fedify/testing` works even when dependency hoisting is disabled on Node.js and Bun. [[#1305], [#1307]] +[#1302]: https://github.com/fedify-dev/fedify/issues/1302 [#1305]: https://github.com/fedify-dev/fedify/issues/1305 +[#1306]: https://github.com/fedify-dev/fedify/pull/1306 [#1307]: https://github.com/fedify-dev/fedify/pull/1307 diff --git a/changes.d/testing/allow-private-address.md b/changes.d/testing/allow-private-address.md new file mode 100644 index 000000000..00d4a942f --- /dev/null +++ b/changes.d/testing/allow-private-address.md @@ -0,0 +1,9 @@ +--- +links: + '#1302': https://github.com/fedify-dev/fedify/issues/1302 + '#1306': https://github.com/fedify-dev/fedify/pull/1306 +--- + - Fixed `createFederation()` ignoring `allowPrivateAddress: true`. Received + activities and federation-created contexts now use document loaders that + allow local servers, so calls such as `follow.getActor()` can resolve + localhost actors in inbox listeners. [[#1302], [#1306]] diff --git a/docs/manual/test.md b/docs/manual/test.md index 22d0ef06e..2e1d7a1da 100644 --- a/docs/manual/test.md +++ b/docs/manual/test.md @@ -240,6 +240,40 @@ await federation.receiveActivity(activity); console.log("Sent activities:", federation.sentActivities); ~~~~ +To load actors or other referenced objects from a local HTTP server, pass +`allowPrivateAddress: true`. This enables HTTP document loading for received +activities and contexts created by the mock federation, including addresses +such as `localhost`: + +~~~~ typescript twoslash +import { createFederation } from "@fedify/testing"; +import { Follow, Undo } from "@fedify/vocab"; + +const federation = createFederation({ + contextData: {}, + allowPrivateAddress: true +}); + +federation + .setInboxListeners("/users/{identifier}/inbox") + .on(Follow, async (ctx, follow) => { + const actor = await follow.getActor(); + }) + .on(Undo, async (ctx, undo) => { + const object = await undo.getObject(); + if (object instanceof Follow) { + const actor = await object.getActor(ctx); + } + }); +~~~~ + +*The `allowPrivateAddress` option is available since Fedify 2.0.35.* + +Received activities are cloned with the configured loaders. Embedded objects +remain shared, so pass the listener context to their getters, as in +`object.getActor(ctx)` above. If the option is `false` or omitted, contexts +keep their no-op mock loaders and received activities keep their own loaders. + ### Creating mock contexts You can create mock contexts using the `createContext()` method, diff --git a/packages/testing/package.json b/packages/testing/package.json index 7410a778c..4bd9ae195 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -54,12 +54,13 @@ }, "dependencies": { "@fedify/vocab": "workspace:^", + "@fedify/vocab-runtime": "workspace:^", "@js-temporal/polyfill": "catalog:", + "@opentelemetry/api": "catalog:", "es-toolkit": "catalog:" }, "devDependencies": { "@fedify/fixture": "workspace:^", - "@fedify/vocab-runtime": "workspace:^", "@std/assert": "catalog:", "@std/async": "catalog:", "tsdown": "catalog:", diff --git a/packages/testing/src/mock.test.ts b/packages/testing/src/mock.test.ts index 5914f5584..20a3aa3eb 100644 --- a/packages/testing/src/mock.test.ts +++ b/packages/testing/src/mock.test.ts @@ -1,7 +1,14 @@ import type { InboxContext } from "@fedify/fedify/federation"; import { test } from "@fedify/fixture"; -import { Create, Note, Person } from "@fedify/vocab"; -import { assertEquals, assertRejects, assertStrictEquals } from "@std/assert"; +import { Create, Follow, Note, Person, Undo } from "@fedify/vocab"; +import { getDocumentLoader, UrlError } from "@fedify/vocab-runtime"; +import { + assertEquals, + assertInstanceOf, + assertNotStrictEquals, + assertRejects, + assertStrictEquals, +} from "@std/assert"; import { createFederation } from "./mock.ts"; test("MockFederation actor setters support chaining in any order", () => { @@ -599,3 +606,252 @@ test("MockContext.getActorKeyPairs() returns empty array when no dispatcher regi const keyPairs = await context.getActorKeyPairs("alice"); assertEquals(keyPairs, []); }); + +const localActorUrl = "http://localhost:9999/users/alicia"; + +function createNetworkFederation() { + const options = { contextData: {}, allowPrivateAddress: true }; + return createFederation(options); +} + +function createLocalFollow(): Follow { + return new Follow({ + id: new URL("http://localhost:9999/activities/follow"), + actor: new URL(localActorUrl), + object: new URL("https://example.com/users/alice"), + }); +} + +async function withActorFetch(fn: () => Promise): Promise { + const originalFetch = globalThis.fetch; + const requests: string[] = []; + globalThis.fetch = (input) => { + const url = input instanceof Request ? input.url : String(input); + requests.push(url); + return Promise.resolve( + new Response( + JSON.stringify({ + "@context": "https://www.w3.org/ns/activitystreams", + id: url, + type: "Person", + preferredUsername: "alicia", + }), + { headers: { "Content-Type": "application/activity+json" } }, + ), + ); + }; + try { + await fn(); + } finally { + globalThis.fetch = originalFetch; + } + return requests; +} + +for (const passContext of [false, true]) { + test(`receiveActivity allows private actor lookup (pass context: ${passContext})`, async () => { + const federation = createNetworkFederation(); + const activity = createLocalFollow(); + const received: Follow[] = []; + let actor: unknown; + federation.setInboxListeners("/users/{identifier}/inbox").on( + Follow, + async (ctx: InboxContext>, follow: Follow) => { + received.push(follow); + actor = await follow.getActor(passContext ? ctx : {}); + }, + ).on( + Follow, + (_ctx: InboxContext>, follow: Follow) => { + received.push(follow); + return Promise.resolve(); + }, + ); + const requests = await withActorFetch(() => + federation.receiveActivity(activity) + ); + assertEquals(requests, [localActorUrl]); + assertInstanceOf(actor, Person); + assertEquals(actor.id?.href, localActorUrl); + assertInstanceOf(received[0], Follow); + assertNotStrictEquals(received[0], activity); + assertStrictEquals(received[0], received[1]); + assertEquals(activity.actorId?.href, localActorUrl); + // The listener's lazy lookup must not populate the caller's actor cache. + await assertRejects(() => activity.getActor(), UrlError, "Localhost"); + }); +} + +for (const allowPrivateAddress of [undefined, false]) { + test(`receiveActivity retains offline defaults (allowPrivateAddress: ${allowPrivateAddress})`, async () => { + const options = { contextData: {}, allowPrivateAddress }; + const federation = createFederation(options); + const activity = createLocalFollow(); + let received: Follow | undefined; + let document: unknown; + let contextDocument: unknown; + federation.setInboxListeners("/users/{identifier}/inbox").on( + Follow, + async (ctx: InboxContext>, follow: Follow) => { + received = follow; + document = (await ctx.documentLoader(localActorUrl)).document; + contextDocument = (await ctx.contextLoader(localActorUrl)).document; + await assertRejects(() => follow.getActor(), UrlError, "Localhost"); + }, + ); + const requests = await withActorFetch(async () => { + await federation.receiveActivity(activity); + const ctx = federation.createContext(new URL("https://example.com"), {}); + await ctx.documentLoader(localActorUrl); + await ctx.contextLoader(localActorUrl); + }); + assertEquals(requests, []); + assertStrictEquals(received, activity); + assertEquals(document, {}); + assertEquals(contextDocument, {}); + }); +} + +test("createContext allows private actor lookup", async () => { + const federation = createNetworkFederation(); + const ctx = federation.createContext(new URL("https://example.com"), {}); + const activity = createLocalFollow(); + let actor: unknown; + const requests = await withActorFetch(async () => { + actor = await activity.getActor(ctx); + }); + assertEquals(requests, [localActorUrl]); + assertInstanceOf(actor, Person); + assertEquals(actor.id?.href, localActorUrl); +}); + +test("receiveActivity preserves embedded objects and supports lookup with context", async () => { + const federation = createNetworkFederation(); + const follow = createLocalFollow(); + // A different origin must not cause a trusted embedded object to be fetched. + const activity = new Undo({ + id: new URL("https://example.com/activities/undo"), + object: follow, + }); + let embedded: unknown; + let actor: unknown; + federation.setInboxListeners("/users/{identifier}/inbox").on( + Undo, + async (ctx: InboxContext>, undo: Undo) => { + const object = await undo.getObject(); + embedded = object; + if (object instanceof Follow) actor = await object.getActor(ctx); + }, + ); + const requests = await withActorFetch(() => + federation.receiveActivity(activity) + ); + assertEquals(requests, [localActorUrl]); + assertStrictEquals(embedded, follow); + assertInstanceOf(actor, Person); + assertEquals(actor.id?.href, localActorUrl); +}); + +test("receiveActivity respects an explicit getter document loader", async () => { + const federation = createNetworkFederation(); + let actor: unknown; + const loaderRequests: string[] = []; + federation.setInboxListeners("/users/{identifier}/inbox").on( + Follow, + async (_ctx: InboxContext>, follow: Follow) => { + actor = await follow.getActor({ + documentLoader: (url) => { + loaderRequests.push(url); + return Promise.resolve({ + contextUrl: null, + documentUrl: url, + document: { + "@context": "https://www.w3.org/ns/activitystreams", + id: url, + type: "Person", + }, + }); + }, + }); + }, + ); + const requests = await withActorFetch(() => + federation.receiveActivity(createLocalFollow()) + ); + assertEquals(requests, []); + assertEquals(loaderRequests, [localActorUrl]); + assertInstanceOf(actor, Person); + assertEquals(actor.id?.href, localActorUrl); +}); + +test("receiveActivity preserves an unconfigured activity's own loaders", async () => { + const federation = createFederation({ contextData: {} }); + const loader = getDocumentLoader({ allowPrivateAddress: true }); + const activity = createLocalFollow().clone({}, { + documentLoader: loader, + contextLoader: loader, + }); + let actor: unknown; + federation.setInboxListeners("/users/{identifier}/inbox").on( + Follow, + async (_ctx: InboxContext>, follow: Follow) => { + actor = await follow.getActor(); + }, + ); + const requests = await withActorFetch(() => + federation.receiveActivity(activity) + ); + assertEquals(requests, [localActorUrl]); + assertInstanceOf(actor, Person); +}); + +test("receiveActivity does not clone without listeners or context data", async () => { + const options = { contextData: undefined, allowPrivateAddress: true }; + const federation = createFederation(options); + const activity = createLocalFollow(); + let clones = 0; + activity.clone = () => { + clones++; + return activity; + }; + const requests = await withActorFetch(async () => { + await federation.receiveActivity(activity); + federation.setInboxListeners("/users/{identifier}/inbox").on( + Follow, + () => Promise.resolve(), + ); + await assertRejects( + () => federation.receiveActivity(activity), + Error, + "contextData", + ); + }); + assertEquals(requests, []); + assertEquals(clones, 0); +}); + +test("receiveActivity preserves parsed JSON-LD extension properties", async () => { + const federation = createNetworkFederation(); + const json = { + "@context": "https://www.w3.org/ns/activitystreams", + id: "http://localhost:9999/activities/follow", + type: "Follow", + actor: localActorUrl, + object: "https://example.com/users/alice", + "https://example.com/ns#custom": "preserved", + }; + const activity = await Follow.fromJsonLd(json); + let receivedJson: unknown; + federation.setInboxListeners("/users/{identifier}/inbox").on( + Follow, + async (_ctx: InboxContext>, follow: Follow) => { + receivedJson = await follow.toJsonLd(); + }, + ); + const requests = await withActorFetch(() => + federation.receiveActivity(activity) + ); + assertEquals(requests, []); + assertEquals(receivedJson, json); + assertEquals(await activity.toJsonLd(), json); +}); diff --git a/packages/testing/src/mock.ts b/packages/testing/src/mock.ts index f18d8d968..383b6f14a 100644 --- a/packages/testing/src/mock.ts +++ b/packages/testing/src/mock.ts @@ -18,7 +18,8 @@ import type { Object, TraverseCollectionOptions, } from "@fedify/vocab"; -import type { DocumentLoader } from "@fedify/vocab-runtime"; +import { type DocumentLoader, getDocumentLoader } from "@fedify/vocab-runtime"; +import { trace } from "@opentelemetry/api"; import { createContext, createInboxContext, @@ -203,15 +204,20 @@ class MockFederation implements Federation { private inboxListeners: Map = new Map(); private contextData?: TContextData; private receivedActivities: Activity[] = []; + private readonly documentLoader?: DocumentLoader; constructor( private options: { contextData?: TContextData; origin?: string; + allowPrivateAddress?: boolean; tracerProvider?: any; } = {}, ) { this.contextData = options.contextData; + if (options.allowPrivateAddress === true) { + this.documentLoader = getDocumentLoader({ allowPrivateAddress: true }); + } } setNodeInfoDispatcher(path: string, dispatcher: any): void { @@ -408,6 +414,11 @@ class MockFederation implements Federation { url, data: contextData, federation: mockFederation as any, + documentLoader: this.documentLoader, + contextLoader: this.documentLoader, + tracerProvider: this.documentLoader == null + ? undefined + : this.options.tracerProvider ?? trace.getTracerProvider(), }); } @@ -446,12 +457,33 @@ class MockFederation implements Federation { ); } + const receivedActivity = listeners.length > 0 && this.documentLoader != null + ? activity.clone({}, { + documentLoader: this.documentLoader, + contextLoader: this.documentLoader, + }) + : activity; + if (receivedActivity !== activity) { + // _cachedJsonLd is an internal cross-package contract documented in + // @fedify/vocab-tools. clone() drops this cache, so preserve it to keep + // unknown extensions when listeners serialize the received document. + Reflect.set( + receivedActivity, + "_cachedJsonLd", + Reflect.get(activity, "_cachedJsonLd"), + ); + } for (const listener of listeners) { const context = createInboxContext({ data: this.contextData as TContextData, federation: this as any, + documentLoader: this.documentLoader, + contextLoader: this.documentLoader, + tracerProvider: this.documentLoader == null + ? undefined + : this.options.tracerProvider ?? trace.getTracerProvider(), }); - await listener(context, activity); + await listener(context, receivedActivity); } } @@ -545,6 +577,14 @@ export function createFederation( options: { contextData?: TContextData; origin?: string; + /** + * Use HTTP document loading for received activities and contexts created by + * this federation, allowing private addresses such as localhost. + * If false or omitted, contexts retain their no-op mock loaders and + * received activities retain their own loaders. + * @since 2.0.35 + */ + allowPrivateAddress?: boolean; tracerProvider?: any; } = {}, ): TestFederation { diff --git a/packages/vocab-tools/src/__snapshots__/class.test.ts.deno.snap b/packages/vocab-tools/src/__snapshots__/class.test.ts.deno.snap index daed8cde1..10dbde171 100644 --- a/packages/vocab-tools/src/__snapshots__/class.test.ts.deno.snap +++ b/packages/vocab-tools/src/__snapshots__/class.test.ts.deno.snap @@ -86,6 +86,11 @@ export class Object { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -16504,6 +16509,11 @@ export class PropertyValue { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -17149,6 +17159,11 @@ export class DidService { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -17980,6 +17995,11 @@ export class DataIntegrityProof { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -19067,6 +19087,11 @@ export class CryptographicKey { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -19983,6 +20008,11 @@ export class Multikey { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -38808,6 +38838,11 @@ export class Endpoints { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -47969,6 +48004,11 @@ export class Link { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -82276,6 +82316,11 @@ export class Source { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } diff --git a/packages/vocab-tools/src/__snapshots__/class.test.ts.node.snap b/packages/vocab-tools/src/__snapshots__/class.test.ts.node.snap index 9bf7bbff7..c08c0e0e9 100644 --- a/packages/vocab-tools/src/__snapshots__/class.test.ts.node.snap +++ b/packages/vocab-tools/src/__snapshots__/class.test.ts.node.snap @@ -84,6 +84,11 @@ export class Object { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -16502,6 +16507,11 @@ export class PropertyValue { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -17147,6 +17157,11 @@ export class DidService { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -17978,6 +17993,11 @@ export class DataIntegrityProof { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -19065,6 +19085,11 @@ export class CryptographicKey { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -19981,6 +20006,11 @@ export class Multikey { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -38806,6 +38836,11 @@ export class Endpoints { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -47967,6 +48002,11 @@ export class Link { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -82274,6 +82314,11 @@ export class Source { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } diff --git a/packages/vocab-tools/src/__snapshots__/class.test.ts.snap b/packages/vocab-tools/src/__snapshots__/class.test.ts.snap index ba7de4a93..8cd0661a6 100644 --- a/packages/vocab-tools/src/__snapshots__/class.test.ts.snap +++ b/packages/vocab-tools/src/__snapshots__/class.test.ts.snap @@ -86,6 +86,11 @@ export class Object { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -16504,6 +16509,11 @@ export class PropertyValue { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -17149,6 +17159,11 @@ export class DidService { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -17980,6 +17995,11 @@ export class DataIntegrityProof { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -19067,6 +19087,11 @@ export class CryptographicKey { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -19983,6 +20008,11 @@ export class Multikey { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -38808,6 +38838,11 @@ export class Endpoints { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -47969,6 +48004,11 @@ export class Link { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } @@ -82276,6 +82316,11 @@ export class Source { return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } diff --git a/packages/vocab-tools/src/class.ts b/packages/vocab-tools/src/class.ts index 4acdcf001..7715087a4 100644 --- a/packages/vocab-tools/src/class.ts +++ b/packages/vocab-tools/src/class.ts @@ -80,6 +80,11 @@ async function* generateClass( return this.#warning; } + /** + * Internal cross-package contract with @fedify/testing, which copies this + * cache when attaching document loaders to an activity clone. Keep this + * accessor name and its getter/setter semantics stable. + */ protected get _cachedJsonLd(): unknown | undefined { return this.#cachedJsonLd; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c23b428d7..573724fb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1468,9 +1468,15 @@ importers: '@fedify/vocab': specifier: workspace:^ version: link:../vocab + '@fedify/vocab-runtime': + specifier: workspace:^ + version: link:../vocab-runtime '@js-temporal/polyfill': specifier: 'catalog:' version: 0.5.1 + '@opentelemetry/api': + specifier: 'catalog:' + version: 1.9.0 es-toolkit: specifier: 'catalog:' version: 1.43.0 @@ -1478,9 +1484,6 @@ importers: '@fedify/fixture': specifier: workspace:^ version: link:../fixture - '@fedify/vocab-runtime': - specifier: workspace:^ - version: link:../vocab-runtime '@std/assert': specifier: 'catalog:' version: '@jsr/std__assert@1.0.13'