@fedify/vocab 2.4.0-pr.936.41 → 2.4.0
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/deno.json +1 -1
- package/dist/mod.cjs +31328 -8723
- package/dist/mod.d.cts +4599 -6
- package/dist/mod.d.ts +4599 -6
- package/dist/mod.js +31221 -8623
- package/dist-tests/actor-D7_iX4I4.mjs +276 -0
- package/dist-tests/actor.test.mjs +55 -49
- package/dist-tests/{actor-MBpzF3-D.mjs → esm-DP2Spt0u.mjs} +15 -211
- package/dist-tests/gateway.test.mjs +1906 -0
- package/dist-tests/lookup-IiNFWuXL.mjs +480 -0
- package/dist-tests/lookup.test.mjs +10 -268
- package/dist-tests/object.yaml +15 -0
- package/dist-tests/portable-lookup.test.mjs +653 -0
- package/dist-tests/signed-representation.test.mjs +189 -0
- package/dist-tests/translation.test.mjs +404 -0
- package/dist-tests/translation.yaml +91 -0
- package/dist-tests/type.test.mjs +2 -2
- package/dist-tests/{vocab-Bjwctrxn.mjs → vocab-COZcxf00.mjs} +30831 -8515
- package/dist-tests/vocab.test.mjs +677 -10
- package/package.json +5 -5
- package/src/__snapshots__/vocab.test.ts.snap +291 -15
- package/src/actor.test.ts +41 -40
- package/src/actor.ts +120 -1
- package/src/gateway.test.ts +2637 -0
- package/src/lookup.test.ts +6 -5
- package/src/lookup.ts +460 -33
- package/src/mod.ts +9 -0
- package/src/object.yaml +15 -0
- package/src/portable-lookup.test.ts +889 -0
- package/src/preprocessors.ts +1 -1
- package/src/signed-representation.test.ts +277 -0
- package/src/translation.test.ts +518 -0
- package/src/translation.yaml +91 -0
- package/src/vocab.test.ts +914 -5
- /package/dist-tests/{type-_BlsfT3S.mjs → type-BMdjm4Eg.mjs} +0 -0
- /package/dist-tests/{utils-CQjrpUkt.mjs → utils-Df2odC27.mjs} +0 -0
package/src/preprocessors.ts
CHANGED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
2
|
+
import {
|
|
3
|
+
isMarkerSafeContext,
|
|
4
|
+
retainSignedRepresentation,
|
|
5
|
+
} from "@fedify/vocab-runtime/internal/signed-representation";
|
|
6
|
+
import { deepStrictEqual, ok } from "node:assert/strict";
|
|
7
|
+
import * as vocab from "./vocab.ts";
|
|
8
|
+
import {
|
|
9
|
+
Announce,
|
|
10
|
+
Create,
|
|
11
|
+
Note,
|
|
12
|
+
OrderedCollection,
|
|
13
|
+
Question,
|
|
14
|
+
} from "./vocab.ts";
|
|
15
|
+
|
|
16
|
+
const securedNote = {
|
|
17
|
+
"@context": [
|
|
18
|
+
"https://www.w3.org/ns/activitystreams",
|
|
19
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
20
|
+
{ ex: "https://example.com/ns#" },
|
|
21
|
+
],
|
|
22
|
+
id: "https://example.com/notes/1",
|
|
23
|
+
type: "Note",
|
|
24
|
+
content: "Hello",
|
|
25
|
+
proof: {
|
|
26
|
+
"@context": [
|
|
27
|
+
"https://www.w3.org/ns/activitystreams",
|
|
28
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
29
|
+
{ ex: "https://example.com/ns#" },
|
|
30
|
+
],
|
|
31
|
+
type: "DataIntegrityProof",
|
|
32
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
33
|
+
created: "2023-02-24T23:36:38Z",
|
|
34
|
+
verificationMethod: "https://example.com/person#ed25519-key",
|
|
35
|
+
proofPurpose: "assertionMethod",
|
|
36
|
+
proofValue: "z3FXQ",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const options = { contextLoader: mockDocumentLoader };
|
|
41
|
+
|
|
42
|
+
function retainedNote(): Note {
|
|
43
|
+
const note = new Note({
|
|
44
|
+
id: new URL("https://example.com/notes/1"),
|
|
45
|
+
content: "Hello",
|
|
46
|
+
});
|
|
47
|
+
retainSignedRepresentation(note, structuredClone(securedNote));
|
|
48
|
+
return note;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test("nested serialization embeds a retained signed representation", async () => {
|
|
52
|
+
const create = new Create({
|
|
53
|
+
id: new URL("https://example.com/activities/1"),
|
|
54
|
+
actor: new URL("https://example.com/person"),
|
|
55
|
+
object: retainedNote(),
|
|
56
|
+
});
|
|
57
|
+
const compact = await create.toJsonLd({
|
|
58
|
+
format: "compact",
|
|
59
|
+
...options,
|
|
60
|
+
}) as Record<string, unknown>;
|
|
61
|
+
|
|
62
|
+
// `Create` is not compactable, so this goes through the expand-then-compact
|
|
63
|
+
// path, which would otherwise dissolve the child's own contexts.
|
|
64
|
+
deepStrictEqual(compact.object, securedNote);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("nested serialization embeds a retained representation through a compactable parent", async () => {
|
|
68
|
+
const wrapper = new Note({
|
|
69
|
+
id: new URL("https://example.com/notes/wrapper"),
|
|
70
|
+
attachments: [retainedNote()],
|
|
71
|
+
});
|
|
72
|
+
const compact = await wrapper.toJsonLd(options) as Record<string, unknown>;
|
|
73
|
+
deepStrictEqual(compact.attachment, securedNote);
|
|
74
|
+
|
|
75
|
+
// … and still when a non-compactable ancestor compacts the whole tree.
|
|
76
|
+
const question = new Question({
|
|
77
|
+
id: new URL("https://example.com/questions/1"),
|
|
78
|
+
exclusiveOptions: [wrapper],
|
|
79
|
+
});
|
|
80
|
+
const compacted = await question.toJsonLd(options) as Record<string, unknown>;
|
|
81
|
+
const embeddedWrapper = compacted.oneOf as Record<string, unknown>;
|
|
82
|
+
deepStrictEqual(embeddedWrapper.attachment, securedNote);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("nested serialization embeds one retained representation many times", async () => {
|
|
86
|
+
const note = retainedNote();
|
|
87
|
+
const create = new Create({
|
|
88
|
+
id: new URL("https://example.com/activities/1"),
|
|
89
|
+
actor: new URL("https://example.com/person"),
|
|
90
|
+
objects: [note, note],
|
|
91
|
+
tags: [note],
|
|
92
|
+
});
|
|
93
|
+
const compact = await create.toJsonLd({
|
|
94
|
+
format: "compact",
|
|
95
|
+
...options,
|
|
96
|
+
}) as Record<string, unknown>;
|
|
97
|
+
const objects = compact.object as Record<string, unknown>[];
|
|
98
|
+
|
|
99
|
+
deepStrictEqual(objects.length, 2);
|
|
100
|
+
deepStrictEqual(objects[0], securedNote);
|
|
101
|
+
deepStrictEqual(objects[1], securedNote);
|
|
102
|
+
deepStrictEqual(compact.tag, securedNote);
|
|
103
|
+
// Each site gets its own copy rather than an alias of one object.
|
|
104
|
+
ok(objects[0] !== objects[1]);
|
|
105
|
+
ok(objects[0] !== compact.tag);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("nested serialization embeds a retained representation at any depth", async () => {
|
|
109
|
+
const announce = new Announce({
|
|
110
|
+
id: new URL("https://example.com/activities/2"),
|
|
111
|
+
actor: new URL("https://example.com/person"),
|
|
112
|
+
object: new Create({
|
|
113
|
+
id: new URL("https://example.com/activities/1"),
|
|
114
|
+
actor: new URL("https://example.com/person"),
|
|
115
|
+
object: retainedNote(),
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
const compact = await announce.toJsonLd({
|
|
119
|
+
format: "compact",
|
|
120
|
+
...options,
|
|
121
|
+
}) as Record<string, unknown>;
|
|
122
|
+
const create = compact.object as Record<string, unknown>;
|
|
123
|
+
|
|
124
|
+
deepStrictEqual(create.object, securedNote);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("expanded serialization ignores a retained signed representation", async () => {
|
|
128
|
+
const create = new Create({
|
|
129
|
+
id: new URL("https://example.com/activities/1"),
|
|
130
|
+
actor: new URL("https://example.com/person"),
|
|
131
|
+
object: retainedNote(),
|
|
132
|
+
});
|
|
133
|
+
const expanded = await create.toJsonLd({
|
|
134
|
+
format: "expand",
|
|
135
|
+
...options,
|
|
136
|
+
}) as Record<string, unknown>[];
|
|
137
|
+
const objects = expanded[0][
|
|
138
|
+
"https://www.w3.org/ns/activitystreams#object"
|
|
139
|
+
] as Record<string, unknown>[];
|
|
140
|
+
|
|
141
|
+
// An expanded document has no compact representation to preserve, so the
|
|
142
|
+
// child is expanded like any other object.
|
|
143
|
+
deepStrictEqual(
|
|
144
|
+
objects[0]["@id"],
|
|
145
|
+
"https://example.com/notes/1",
|
|
146
|
+
);
|
|
147
|
+
deepStrictEqual(
|
|
148
|
+
objects[0]["@type"],
|
|
149
|
+
["https://www.w3.org/ns/activitystreams#Note"],
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("a context that could hide a placeholder turns retention off", async () => {
|
|
154
|
+
const create = new Create({
|
|
155
|
+
id: new URL("https://example.com/activities/1"),
|
|
156
|
+
actor: new URL("https://example.com/person"),
|
|
157
|
+
object: retainedNote(),
|
|
158
|
+
});
|
|
159
|
+
const hostileContext = [
|
|
160
|
+
"https://www.w3.org/ns/activitystreams",
|
|
161
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
162
|
+
// An `@id` alias other than ActivityStreams' own `id` would make the
|
|
163
|
+
// placeholder indistinguishable from an ordinary property value.
|
|
164
|
+
{ identifier: "@id" },
|
|
165
|
+
];
|
|
166
|
+
const compact = await create.toJsonLd({
|
|
167
|
+
format: "compact",
|
|
168
|
+
context: hostileContext as unknown as (string | Record<string, string>)[],
|
|
169
|
+
...options,
|
|
170
|
+
}) as Record<string, unknown>;
|
|
171
|
+
const embedded = compact.object as Record<string, unknown>;
|
|
172
|
+
|
|
173
|
+
// Rather than risk corrupting the document, Fedify falls back to ordinary
|
|
174
|
+
// serialization, which reconstructs the child under the parent's context.
|
|
175
|
+
deepStrictEqual(embedded["@context"], undefined);
|
|
176
|
+
deepStrictEqual(embedded.proof, undefined);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a clone does not inherit a retained signed representation", async () => {
|
|
180
|
+
const note = retainedNote();
|
|
181
|
+
const create = new Create({
|
|
182
|
+
id: new URL("https://example.com/activities/1"),
|
|
183
|
+
actor: new URL("https://example.com/person"),
|
|
184
|
+
object: note.clone({ content: "Changed" }),
|
|
185
|
+
});
|
|
186
|
+
const compact = await create.toJsonLd({
|
|
187
|
+
format: "compact",
|
|
188
|
+
...options,
|
|
189
|
+
}) as Record<string, unknown>;
|
|
190
|
+
const embedded = compact.object as Record<string, unknown>;
|
|
191
|
+
|
|
192
|
+
deepStrictEqual(embedded["@context"], undefined);
|
|
193
|
+
deepStrictEqual(embedded.content, "Changed");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("every generated default context keeps placeholders recoverable", async () => {
|
|
197
|
+
interface VocabClass {
|
|
198
|
+
new (values: { id?: URL | null }): { toJsonLd(): Promise<unknown> };
|
|
199
|
+
readonly typeId: URL;
|
|
200
|
+
}
|
|
201
|
+
const classes = Object.values(vocab).filter((value) =>
|
|
202
|
+
typeof value === "function" &&
|
|
203
|
+
(value as { typeId?: unknown }).typeId instanceof URL
|
|
204
|
+
) as unknown as VocabClass[];
|
|
205
|
+
ok(classes.length > 0);
|
|
206
|
+
for (const cls of classes) {
|
|
207
|
+
const serialized = await new cls({
|
|
208
|
+
id: new URL("https://example.com/objects/1"),
|
|
209
|
+
}).toJsonLd() as Record<string, unknown>;
|
|
210
|
+
ok(
|
|
211
|
+
isMarkerSafeContext(serialized["@context"]),
|
|
212
|
+
`${cls.typeId.href} has a default context that could hide a placeholder`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("a subtype that redefines an inherited property still embeds a retained representation", async () => {
|
|
218
|
+
// `OrderedCollection` redefines `Collection.items` as `orderedItems`, so
|
|
219
|
+
// the inherited encoder runs first and its output is then dropped and
|
|
220
|
+
// re-encoded under the new key.
|
|
221
|
+
const collection = new OrderedCollection({
|
|
222
|
+
id: new URL("https://example.com/collections/1"),
|
|
223
|
+
items: [retainedNote()],
|
|
224
|
+
});
|
|
225
|
+
const compact = await collection.toJsonLd(options) as Record<string, unknown>;
|
|
226
|
+
|
|
227
|
+
deepStrictEqual(compact.items, undefined);
|
|
228
|
+
deepStrictEqual(compact.orderedItems, [securedNote]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("a context that shadows the placeholder scheme turns retention off", async () => {
|
|
232
|
+
const create = new Create({
|
|
233
|
+
id: new URL("https://example.com/activities/1"),
|
|
234
|
+
actor: new URL("https://example.com/person"),
|
|
235
|
+
object: retainedNote(),
|
|
236
|
+
});
|
|
237
|
+
const shadowingContext = [
|
|
238
|
+
"https://www.w3.org/ns/activitystreams",
|
|
239
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
240
|
+
// A term named `urn` makes a JSON-LD processor read the placeholder as a
|
|
241
|
+
// compact IRI and reject it in safe mode.
|
|
242
|
+
{ urn: "https://example.com/ns#" },
|
|
243
|
+
];
|
|
244
|
+
const compact = await create.toJsonLd({
|
|
245
|
+
format: "compact",
|
|
246
|
+
context: shadowingContext as unknown as (string | Record<string, string>)[],
|
|
247
|
+
...options,
|
|
248
|
+
}) as Record<string, unknown>;
|
|
249
|
+
const embedded = compact.object as Record<string, unknown>;
|
|
250
|
+
|
|
251
|
+
deepStrictEqual(embedded["@context"], undefined);
|
|
252
|
+
deepStrictEqual(embedded.proof, undefined);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("a context that aliases `@id` indirectly turns retention off", async () => {
|
|
256
|
+
const create = new Create({
|
|
257
|
+
id: new URL("https://example.com/activities/1"),
|
|
258
|
+
actor: new URL("https://example.com/person"),
|
|
259
|
+
object: retainedNote(),
|
|
260
|
+
});
|
|
261
|
+
const indirectContext = [
|
|
262
|
+
"https://www.w3.org/ns/activitystreams",
|
|
263
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
264
|
+
// `i` resolves through ActivityStreams' own `id`, so it aliases `@id`
|
|
265
|
+
// without naming the keyword.
|
|
266
|
+
{ i: "id", object: "as:object" },
|
|
267
|
+
];
|
|
268
|
+
const compact = await create.toJsonLd({
|
|
269
|
+
format: "compact",
|
|
270
|
+
context: indirectContext as unknown as (string | Record<string, string>)[],
|
|
271
|
+
...options,
|
|
272
|
+
}) as Record<string, unknown>;
|
|
273
|
+
const embedded = compact.object as Record<string, unknown>;
|
|
274
|
+
|
|
275
|
+
deepStrictEqual(embedded["@context"], undefined);
|
|
276
|
+
deepStrictEqual(embedded.proof, undefined);
|
|
277
|
+
});
|