Skip to content

Commit 09a41ad

Browse files
authored
Merge pull request #1246 from dahlia/docs/vocab-clone
Document shallow cloning and shared hydration
2 parents 1b6133f + 453e505 commit 09a41ad

5 files changed

Lines changed: 3424 additions & 12 deletions

File tree

‎docs/manual/vocab.md‎

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,23 @@ const note = await create.getObject();
835835
const note2 = await create.getObject();
836836
~~~~
837837

838-
Hydrating the property also affects the JSON-LD representation of the object.
838+
When hydration caches a fetched object, it replaces the property's URL in the
839+
instance whose accessor was called. An unverified portable claim can be
840+
returned without replacing that URL or clearing the JSON-LD cache, so a later
841+
accessor call can fetch it again.
842+
When the result is cached, all references to that instance see the hydrated
843+
property, including references
844+
from a source and its [shallow clone](#immutability) to a shared nested object,
845+
or from an activity that embeds it. Hydrating a top-level property of a clone
846+
does not update the source's property, because their property arrays are
847+
separate.
848+
849+
Hydration can also affect `toJsonLd()` output, including output from activities
850+
that embed the hydrated object. Objects parsed with `fromJsonLd()` may retain
851+
cached JSON-LD. Caching a fetched object clears the JSON-LD cache only on the
852+
instance whose accessor fetched it; parent caches remain intact. A parent may
853+
therefore serialize differently on its own and when embedded in another
854+
activity.
839855

840856
For example, since the following code does not hydrate the `object` property,
841857
the JSON-LD representation of the `Create` object has the `object` property
@@ -904,16 +920,13 @@ attributes are simplified or omitted for readability):
904920
Immutability
905921
------------
906922

907-
Every object in the Activity Vocabulary is represented as an immutable object.
908-
This means that you cannot change the properties of the object after the object
909-
is instantiated. This is for ensuring the consistency of the objects and the
910-
safety of the objects in the concurrent environment.
923+
Activity Vocabulary objects are immutable in the sense that you cannot assign
924+
new property values after construction. Dereferencing accessors can still
925+
cache fetched objects through [property hydration](#property-hydration).
911926

912-
In order to change the properties of the object, you need to clone the object
913-
with the new properties. Fortunately, the objects have a `clone()` method that
914-
takes an object with the new properties and returns a new object with the new
915-
properties. The following shows an example of changing the `~Object.content`
916-
property of a `Note` object:
927+
To replace property values, use `clone()`. It returns a new instance with the
928+
given values and leaves the source's property values unchanged. The following
929+
example replaces the `~Object.content` property of a `Note` object:
917930

918931
~~~~ typescript{8-10} twoslash
919932
import { Note } from "@fedify/vocab";
@@ -929,8 +942,40 @@ const noteInChinese = noteInEnglish.clone({
929942
});
930943
~~~~
931944

932-
Parameters of the `clone()` method share the same type with parameters of
933-
the constructor.
945+
The `clone()` method accepts the same property values as the constructor.
946+
It makes a *shallow copy*: the clone has its own property arrays, but nested
947+
objects and URLs are shared with the source. Hydrating a shared nested object
948+
through either instance changes that nested object for both when their accessors
949+
return the shared instance. A parsed source can return a fresh object instead:
950+
if its cached JSON-LD contains an embedded object with its own `@context`, the
951+
accessor re-parses that object on each call. Hydrating that returned object
952+
does not update the nested instance stored in the source or clone.
953+
954+
If you need to preserve the received representation while resolving properties,
955+
keep the received JSON-LD document. To resolve properties in a separate object
956+
graph, serialize and re-parse the object before hydrating any properties rather
957+
than using `clone()`:
958+
959+
~~~~ typescript twoslash
960+
import { Create } from "@fedify/vocab";
961+
import type { DocumentLoader } from "@fedify/vocab-runtime";
962+
declare const original: Create;
963+
declare const documentLoader: DocumentLoader;
964+
declare const contextLoader: DocumentLoader;
965+
// ---cut-before---
966+
const options = { documentLoader, contextLoader };
967+
const copy = await Create.fromJsonLd(await original.toJsonLd(), options);
968+
// Resolve properties on copy while keeping original separate:
969+
const note = await copy.getObject(options);
970+
if (note != null) await note.getAttribution(options);
971+
~~~~
972+
973+
This round trip separates nested vocabulary objects; it does not preserve the
974+
original JSON bytes. Pass any custom loaders again, as in the example above.
975+
The copy also does not inherit trust in embedded objects. Under the
976+
[origin-based security model](#origin-based-security-model), accessors may fetch
977+
cross-origin objects again, even if the original already hydrated them. The
978+
fetched data may have changed, or the fetch may fail.
934979

935980

936981
Looking up remote objects

0 commit comments

Comments
 (0)