@fedify/vocab 2.5.0-dev.2339 → 2.5.0-dev.2355

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 (38) hide show
  1. package/deno.json +1 -1
  2. package/dist/mod.cjs +851 -2
  3. package/dist/mod.d.cts +227 -0
  4. package/dist/mod.d.ts +227 -0
  5. package/dist/mod.js +851 -2
  6. package/dist-tests/{actor-qViYned_.mjs → actor-BbIjMjNa.mjs} +2 -2
  7. package/dist-tests/actor.test.mjs +3 -3
  8. package/dist-tests/application.yaml +15 -0
  9. package/dist-tests/{esm-Cncdk-Cm.mjs → esm-rg6f9oLR.mjs} +1 -1
  10. package/dist-tests/gateway.test.mjs +3 -3
  11. package/dist-tests/group.yaml +15 -0
  12. package/dist-tests/license.test.mjs +538 -0
  13. package/dist-tests/link.yaml +25 -0
  14. package/dist-tests/{lookup-DHfL6oZO.mjs → lookup-DKLMyPMJ.mjs} +2 -2
  15. package/dist-tests/lookup.test.mjs +3 -3
  16. package/dist-tests/object.yaml +30 -0
  17. package/dist-tests/organization.yaml +15 -0
  18. package/dist-tests/person.yaml +15 -0
  19. package/dist-tests/portable-lookup.test.mjs +4 -4
  20. package/dist-tests/service.yaml +15 -0
  21. package/dist-tests/signed-representation.test.mjs +1 -1
  22. package/dist-tests/src-DVk5F9Ik.mjs +9 -0
  23. package/dist-tests/translation.test.mjs +3 -6
  24. package/dist-tests/type.test.mjs +1 -1
  25. package/dist-tests/{vocab-EFWWufqL.mjs → vocab-Cg2PG-xH.mjs} +851 -2
  26. package/dist-tests/vocab.test.mjs +9 -3
  27. package/package.json +4 -4
  28. package/src/__snapshots__/vocab.test.ts.snap +480 -33
  29. package/src/application.yaml +15 -0
  30. package/src/group.yaml +15 -0
  31. package/src/license.test.ts +588 -0
  32. package/src/link.yaml +25 -0
  33. package/src/object.yaml +30 -0
  34. package/src/organization.yaml +15 -0
  35. package/src/person.yaml +15 -0
  36. package/src/service.yaml +15 -0
  37. package/src/vocab.test.ts +18 -6
  38. package/LICENSE +0 -20
@@ -1,6 +1,6 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-EFWWufqL.mjs";
3
+ import { F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-Cg2PG-xH.mjs";
4
4
  import { t as getTypeId } from "./type-BMdjm4Eg.mjs";
5
5
  import { canonicalizePortableUri, formatIri, fromCompatibleEf61Id, isGatewayUrl } from "@fedify/vocab-runtime";
6
6
  import { lookupWebFinger } from "@fedify/webfinger";
@@ -8,7 +8,7 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { domainToASCII, domainToUnicode } from "node:url";
9
9
  //#region deno.json
10
10
  var name = "@fedify/vocab";
11
- var version = "2.5.0-dev.2339+5e786861";
11
+ var version = "2.5.0-dev.2355+c92a1cb1";
12
12
  //#endregion
13
13
  //#region src/actor.ts
14
14
  const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { F as Person, P as Organization, Q as __exportAll, U as Service, a as Application, b as Group } from "./vocab-EFWWufqL.mjs";
4
- import { t as esm_default } from "./esm-Cncdk-Cm.mjs";
5
- import { a as normalizeActorHandle, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-qViYned_.mjs";
3
+ import { $ as __exportAll, F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-Cg2PG-xH.mjs";
4
+ import { t as esm_default } from "./esm-rg6f9oLR.mjs";
5
+ import { a as normalizeActorHandle, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-BbIjMjNa.mjs";
6
6
  import { createTestMeterProvider, test } from "@fedify/fixture";
7
7
  import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
8
8
  //#region ../../node_modules/.pnpm/fast-check@3.23.2/node_modules/fast-check/lib/esm/check/precondition/PreconditionFailure.js
@@ -351,3 +351,18 @@ properties:
351
351
  attached in some clients.
352
352
  range:
353
353
  - "http://www.w3.org/2001/XMLSchema#boolean"
354
+
355
+ - singularName: preferredLicense
356
+ functional: true
357
+ compactName: preferredLicense
358
+ uri: "https://w3id.org/fep/6757#preferredLicense"
359
+ extraContext: "https://w3id.org/fep/6757"
360
+ description: |
361
+ The URI of the license this actor prefers for the new objects it creates,
362
+ as defined by [FEP-6757](https://w3id.org/fep/6757). A client can use it
363
+ as the default `license` of a new object and let the user choose another.
364
+
365
+ This is not a fallback license: an object without its own `license` is not
366
+ licensed under its author's preferred license.
367
+ range:
368
+ - "fedify:absoluteIri"
@@ -1,6 +1,6 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { $ as __toESM, Z as __commonJSMin } from "./vocab-EFWWufqL.mjs";
3
+ import { Q as __commonJSMin, et as __toESM } from "./vocab-Cg2PG-xH.mjs";
4
4
  //#endregion
5
5
  //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
6
6
  var import_glob_to_regexp = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { F as Person, M as OrderedCollection, S as Image, c as CollectionPage, k as Note, s as Collection, u as Create } from "./vocab-EFWWufqL.mjs";
4
- import { t as esm_default } from "./esm-Cncdk-Cm.mjs";
5
- import { n as traverseCollection, t as lookupObject } from "./lookup-DHfL6oZO.mjs";
3
+ import { F as Person, M as OrderedCollection, S as Image, c as CollectionPage, k as Note, s as Collection, u as Create } from "./vocab-Cg2PG-xH.mjs";
4
+ import { t as esm_default } from "./esm-rg6f9oLR.mjs";
5
+ import { n as traverseCollection, t as lookupObject } from "./lookup-DKLMyPMJ.mjs";
6
6
  import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
7
7
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
8
8
  import { deepStrictEqual, ok, rejects } from "node:assert/strict";
@@ -351,3 +351,18 @@ properties:
351
351
  attached in some clients.
352
352
  range:
353
353
  - "http://www.w3.org/2001/XMLSchema#boolean"
354
+
355
+ - singularName: preferredLicense
356
+ functional: true
357
+ compactName: preferredLicense
358
+ uri: "https://w3id.org/fep/6757#preferredLicense"
359
+ extraContext: "https://w3id.org/fep/6757"
360
+ description: |
361
+ The URI of the license this actor prefers for the new objects it creates,
362
+ as defined by [FEP-6757](https://w3id.org/fep/6757). A client can use it
363
+ as the default `license` of a new object and let the user choose another.
364
+
365
+ This is not a fallback license: an object without its own `license` is not
366
+ licensed under its author's preferred license.
367
+ range:
368
+ - "fedify:absoluteIri"
@@ -0,0 +1,538 @@
1
+ import "temporal-polyfill";
2
+ globalThis.addEventListener = () => {};
3
+ import { E as Link, F as Person, J as Video, P as Organization, S as Image, U as Service, a as Application, b as Group, k as Note, s as Collection, u as Create, z as Question } from "./vocab-Cg2PG-xH.mjs";
4
+ import "./src-DVk5F9Ik.mjs";
5
+ import { test } from "@fedify/fixture";
6
+ import { deepStrictEqual, ok } from "node:assert/strict";
7
+ import { getDocumentLoader, preloadedContexts } from "@fedify/vocab-runtime";
8
+ import jsonld from "@fedify/vocab-runtime/jsonld";
9
+ //#region src/license.test.ts
10
+ const AS = "https://www.w3.org/ns/activitystreams";
11
+ const FEP = "https://w3id.org/fep/6757";
12
+ const DC_LICENSE = "http://purl.org/dc/terms/license";
13
+ const PREFERRED_LICENSE = `${FEP}#preferredLicense`;
14
+ const SCHEMA_LICENSE = "https://schema.org/license";
15
+ const HTTP_SCHEMA_LICENSE = "http://schema.org/license";
16
+ const CC_LICENSE = "http://creativecommons.org/ns#license";
17
+ const BY = "https://creativecommons.org/licenses/by/4.0/";
18
+ const BY_NC = "https://creativecommons.org/licenses/by-nc/4.0/";
19
+ const BY_NC_ND = "https://creativecommons.org/licenses/by-nc-nd/4.0/";
20
+ const CC0 = "https://creativecommons.org/publicdomain/zero/1.0/";
21
+ const PD_MARK = "https://creativecommons.org/publicdomain/mark/1.0/";
22
+ const noteExample = {
23
+ "@context": [
24
+ AS,
25
+ FEP,
26
+ { schema: "https://schema.org/" }
27
+ ],
28
+ id: "https://social.example/note/857",
29
+ type: "Note",
30
+ content: "Hello, world!",
31
+ license: BY,
32
+ "schema:license": BY,
33
+ attachment: {
34
+ type: "Link",
35
+ href: "https://media.social.example/uploads/file339.jpg",
36
+ mediaType: "image/jpeg",
37
+ license: PD_MARK
38
+ }
39
+ };
40
+ const personExample = {
41
+ "@context": [AS, FEP],
42
+ id: "https://social.example/user/1753",
43
+ type: "Person",
44
+ name: "Example User",
45
+ inbox: "https://social.example/collection/9257",
46
+ outbox: "https://social.example/collection/10772",
47
+ summary: "An example user who is very illustrative of important features",
48
+ license: BY_NC_ND,
49
+ icon: {
50
+ type: "Image",
51
+ license: BY_NC,
52
+ url: {
53
+ type: "Link",
54
+ mediaType: "image/jpeg",
55
+ href: "https://media.social.example/upload/7272.jpg"
56
+ }
57
+ },
58
+ preferredLicense: BY_NC
59
+ };
60
+ function record(value) {
61
+ ok(value != null && typeof value === "object" && !Array.isArray(value));
62
+ return value;
63
+ }
64
+ const noFetch = (url) => {
65
+ throw new Error(`Unexpected fetch: ${url}`);
66
+ };
67
+ /**
68
+ * A context loader that serves only preloaded contexts and records them.
69
+ */
70
+ function offlineContextLoader() {
71
+ const loaded = [];
72
+ const loader = getDocumentLoader();
73
+ const contextLoader = (url, options) => {
74
+ if (!(url in preloadedContexts)) throw new Error(`Unexpected context fetch: ${url}`);
75
+ loaded.push(url);
76
+ return loader(url, options);
77
+ };
78
+ return Object.assign(contextLoader, { loaded });
79
+ }
80
+ async function expand(json) {
81
+ return record((await jsonld.expand(json, { documentLoader: getDocumentLoader() }))[0]);
82
+ }
83
+ function contexts(json) {
84
+ const context = record(json)["@context"];
85
+ return Array.isArray(context) ? context : [context];
86
+ }
87
+ function note(licenses = [new URL(BY)]) {
88
+ return new Note({
89
+ id: new URL("https://social.example/note/857"),
90
+ content: "Hello, world!",
91
+ licenses
92
+ });
93
+ }
94
+ test("FEP-6757 context is preloaded", async () => {
95
+ ok(FEP in preloadedContexts);
96
+ deepStrictEqual(record((await getDocumentLoader()(FEP)).document)["@context"], {
97
+ license: {
98
+ "@id": DC_LICENSE,
99
+ "@type": "@id"
100
+ },
101
+ preferredLicense: {
102
+ "@id": PREFERRED_LICENSE,
103
+ "@type": "@id"
104
+ }
105
+ });
106
+ });
107
+ test("FEP-6757 note example is parsed offline", async () => {
108
+ const contextLoader = offlineContextLoader();
109
+ const parsed = await Note.fromJsonLd(noteExample, {
110
+ documentLoader: noFetch,
111
+ contextLoader
112
+ });
113
+ deepStrictEqual(parsed.licenses, [new URL(BY)]);
114
+ const attachments = await Array.fromAsync(parsed.getAttachments({
115
+ documentLoader: noFetch,
116
+ contextLoader
117
+ }));
118
+ deepStrictEqual(attachments.length, 1);
119
+ ok(attachments[0] instanceof Link);
120
+ deepStrictEqual(attachments[0].licenses, [new URL(PD_MARK)]);
121
+ ok(contextLoader.loaded.includes(FEP));
122
+ deepStrictEqual(await parsed.toJsonLd(), noteExample);
123
+ });
124
+ test("FEP-6757 person example is parsed offline", async () => {
125
+ const contextLoader = offlineContextLoader();
126
+ const person = await Person.fromJsonLd(personExample, {
127
+ documentLoader: noFetch,
128
+ contextLoader
129
+ });
130
+ deepStrictEqual(person.licenses, [new URL(BY_NC_ND)]);
131
+ deepStrictEqual(person.preferredLicense, new URL(BY_NC));
132
+ const icon = await person.getIcon({
133
+ documentLoader: noFetch,
134
+ contextLoader
135
+ });
136
+ ok(icon instanceof Image);
137
+ deepStrictEqual(icon.licenses, [new URL(BY_NC)]);
138
+ const url = icon.url;
139
+ ok(url instanceof Link);
140
+ deepStrictEqual(url.licenses, []);
141
+ deepStrictEqual(await person.toJsonLd(), personExample);
142
+ });
143
+ test("FEP-6757 license is written only as the canonical IRI", async () => {
144
+ for (const format of [void 0, "compact"]) {
145
+ const json = record(await note().toJsonLd({ format }));
146
+ deepStrictEqual(contexts(json).at(-1), FEP);
147
+ deepStrictEqual(json.license, BY);
148
+ const expanded = await expand(json);
149
+ deepStrictEqual(expanded[DC_LICENSE], [{ "@id": BY }]);
150
+ for (const synonym of [
151
+ SCHEMA_LICENSE,
152
+ HTTP_SCHEMA_LICENSE,
153
+ CC_LICENSE
154
+ ]) ok(!(synonym in expanded));
155
+ }
156
+ deepStrictEqual(record((await note().toJsonLd({ format: "expand" }))[0])[DC_LICENSE], [{ "@id": BY }]);
157
+ });
158
+ test("FEP-6757 multiple licenses are alternatives in observed order", async () => {
159
+ const licenses = [new URL(BY), new URL(CC0)];
160
+ const json = record(await note(licenses).toJsonLd());
161
+ deepStrictEqual(json.license, [BY, CC0]);
162
+ deepStrictEqual((await Note.fromJsonLd(json, { documentLoader: noFetch })).licenses, licenses);
163
+ });
164
+ test("FEP-6757 objects without license metadata serialize as before", async () => {
165
+ const fields = {
166
+ id: new URL("https://social.example/note/1"),
167
+ content: "No license"
168
+ };
169
+ for (const format of [void 0, "compact"]) {
170
+ const json = record(await new Note(fields).toJsonLd({ format }));
171
+ ok(!JSON.stringify(json["@context"]).includes(FEP));
172
+ ok(!("license" in json));
173
+ const person = record(await new Person({ id: new URL("https://social.example/user/1") }).toJsonLd({ format }));
174
+ ok(!JSON.stringify(person["@context"]).includes(FEP));
175
+ ok(!("preferredLicense" in person));
176
+ }
177
+ deepStrictEqual(await new Note(fields).toJsonLd(), {
178
+ "@context": [
179
+ AS,
180
+ "https://w3id.org/security/data-integrity/v1",
181
+ "https://gotosocial.org/ns",
182
+ {
183
+ toot: "http://joinmastodon.org/ns#",
184
+ misskey: "https://misskey-hub.net/ns#",
185
+ fedibird: "http://fedibird.com/ns#",
186
+ sensitive: "as:sensitive",
187
+ Emoji: "toot:Emoji",
188
+ Hashtag: "as:Hashtag",
189
+ quote: {
190
+ "@id": "https://w3id.org/fep/044f#quote",
191
+ "@type": "@id"
192
+ },
193
+ quoteUrl: "as:quoteUrl",
194
+ _misskey_quote: "misskey:_misskey_quote",
195
+ quoteUri: "fedibird:quoteUri",
196
+ QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
197
+ quoteAuthorization: {
198
+ "@id": "https://w3id.org/fep/044f#quoteAuthorization",
199
+ "@type": "@id"
200
+ },
201
+ emojiReactions: {
202
+ "@id": "fedibird:emojiReactions",
203
+ "@type": "@id"
204
+ }
205
+ }
206
+ ],
207
+ id: "https://social.example/note/1",
208
+ type: "Note",
209
+ content: "No license"
210
+ });
211
+ });
212
+ test("FEP-6757 preferredLicense on every actor type", async () => {
213
+ for (const Actor of [
214
+ Application,
215
+ Group,
216
+ Organization,
217
+ Person,
218
+ Service
219
+ ]) {
220
+ const actor = new Actor({
221
+ id: new URL("https://social.example/actor"),
222
+ preferredLicense: new URL(BY_NC)
223
+ });
224
+ for (const format of [void 0, "compact"]) {
225
+ const json = record(await actor.toJsonLd({ format }));
226
+ deepStrictEqual(contexts(json).at(-1), FEP);
227
+ deepStrictEqual(json.preferredLicense, BY_NC);
228
+ ok(!("license" in json));
229
+ deepStrictEqual((await expand(json))[PREFERRED_LICENSE], [{ "@id": BY_NC }]);
230
+ const parsed = await Actor.fromJsonLd(json, { documentLoader: noFetch });
231
+ deepStrictEqual(parsed.preferredLicense, new URL(BY_NC));
232
+ deepStrictEqual(parsed.licenses, []);
233
+ }
234
+ }
235
+ });
236
+ test("FEP-6757 licenses in activities compact to the license term", async () => {
237
+ const create = new Create({
238
+ id: new URL("https://social.example/create/1"),
239
+ actor: new URL("https://social.example/user/1753"),
240
+ object: note()
241
+ });
242
+ for (const format of [void 0, "compact"]) {
243
+ const json = record(await create.toJsonLd({ format }));
244
+ deepStrictEqual(contexts(json).at(-1), FEP);
245
+ deepStrictEqual(record(json.object).license, BY);
246
+ ok(!("license" in json));
247
+ ok(!JSON.stringify(json).includes("dc:license"));
248
+ }
249
+ const parsedNote = await Note.fromJsonLd(noteExample, { documentLoader: noFetch });
250
+ const forwarded = record(await new Create({
251
+ actor: new URL("https://social.example/user/1753"),
252
+ object: parsedNote
253
+ }).toJsonLd());
254
+ deepStrictEqual(contexts(forwarded).at(-1), FEP);
255
+ deepStrictEqual(record(forwarded.object).license, BY);
256
+ const question = record(await new Question({
257
+ id: new URL("https://social.example/question/1"),
258
+ licenses: [new URL(BY)]
259
+ }).toJsonLd());
260
+ deepStrictEqual(question.license, BY);
261
+ ok(!JSON.stringify(question).includes("dc:license"));
262
+ const inline = await Note.fromJsonLd({
263
+ "@context": [AS, { license: {
264
+ "@id": DC_LICENSE,
265
+ "@type": "@id"
266
+ } }],
267
+ id: "https://social.example/note/inline",
268
+ type: "Note",
269
+ license: BY
270
+ }, { documentLoader: noFetch });
271
+ const collection = record(await new Collection({
272
+ id: new URL("https://social.example/collection/1"),
273
+ items: [inline],
274
+ summary: "Licensed items"
275
+ }).toJsonLd());
276
+ deepStrictEqual(contexts(collection).at(-1), FEP);
277
+ const items = collection.items;
278
+ deepStrictEqual(items.license, BY);
279
+ });
280
+ test("FEP-6757 licenses of nested objects only", async () => {
281
+ const json = record(await new Note({
282
+ id: new URL("https://social.example/note/2"),
283
+ attachments: [new Link({
284
+ href: new URL(noteExample.attachment.href),
285
+ licenses: []
286
+ }), new Link({
287
+ href: new URL(noteExample.attachment.href),
288
+ licenses: [new URL(PD_MARK)]
289
+ })]
290
+ }).toJsonLd());
291
+ ok(!("license" in json));
292
+ const attachments = json.attachment;
293
+ ok(!("license" in attachments[0]));
294
+ deepStrictEqual(attachments[1].license, PD_MARK);
295
+ const links = (await expand(json))["https://www.w3.org/ns/activitystreams#attachment"];
296
+ deepStrictEqual(links[1][DC_LICENSE], [{ "@id": PD_MARK }]);
297
+ const person = record(await new Person({
298
+ id: new URL("https://social.example/user/1"),
299
+ icon: new Image({ licenses: [new URL(BY_NC)] })
300
+ }).toJsonLd());
301
+ ok(!("license" in person));
302
+ deepStrictEqual(record(person.icon).license, BY_NC);
303
+ const collection = await Collection.fromJsonLd({
304
+ "@context": [AS, FEP],
305
+ type: "Collection",
306
+ license: CC0,
307
+ items: [{
308
+ type: "Note",
309
+ content: "Unlicensed item"
310
+ }]
311
+ }, { documentLoader: noFetch });
312
+ deepStrictEqual(collection.licenses, [new URL(CC0)]);
313
+ const items = await Array.fromAsync(collection.getItems({ documentLoader: noFetch }));
314
+ ok(items[0] instanceof Note);
315
+ deepStrictEqual(items[0].licenses, []);
316
+ });
317
+ test("FEP-6757 modified clones write the canonical property", async () => {
318
+ const parsed = await Note.fromJsonLd({
319
+ "@context": [AS, { schema: "https://schema.org/" }],
320
+ id: "https://social.example/note/3",
321
+ type: "Note",
322
+ "schema:license": BY
323
+ }, { documentLoader: noFetch });
324
+ deepStrictEqual(parsed.licenses, [new URL(BY)]);
325
+ for (const clone of [parsed.clone({ content: "Edited" }), parsed.clone({ licenses: [new URL(CC0)] })]) {
326
+ const json = record(await clone.toJsonLd());
327
+ ok(!("schema:license" in json));
328
+ const expanded = await expand(json);
329
+ ok(!(SCHEMA_LICENSE in expanded));
330
+ deepStrictEqual(expanded[DC_LICENSE], [{ "@id": clone.licenses[0].href }]);
331
+ }
332
+ const compact = await expand(await parsed.toJsonLd({ format: "compact" }));
333
+ deepStrictEqual(compact[DC_LICENSE], [{ "@id": BY }]);
334
+ ok(!(SCHEMA_LICENSE in compact));
335
+ });
336
+ test("FEP-6757 caller-provided contexts", async () => {
337
+ for (const context of [
338
+ AS,
339
+ [AS, FEP],
340
+ [AS, "http://schema.org/"],
341
+ [
342
+ AS,
343
+ FEP,
344
+ "http://schema.org/"
345
+ ],
346
+ [
347
+ AS,
348
+ "http://schema.org/",
349
+ FEP
350
+ ]
351
+ ]) {
352
+ const json = await note().toJsonLd({
353
+ format: "compact",
354
+ context
355
+ });
356
+ deepStrictEqual(record(json)["@context"], context);
357
+ deepStrictEqual((await expand(json))[DC_LICENSE], [{ "@id": BY }]);
358
+ deepStrictEqual((await Note.fromJsonLd(json, { documentLoader: noFetch })).licenses, [new URL(BY)]);
359
+ }
360
+ });
361
+ test("FEP-6757 synonym precedence", async () => {
362
+ const id = "https://social.example/note/4";
363
+ const cases = [
364
+ [{
365
+ [DC_LICENSE]: [{ "@id": BY }],
366
+ [SCHEMA_LICENSE]: [{ "@id": CC0 }]
367
+ }, [BY]],
368
+ [{
369
+ [SCHEMA_LICENSE]: [{ "@id": CC0 }],
370
+ [CC_LICENSE]: [{ "@id": BY }]
371
+ }, [CC0]],
372
+ [{ [HTTP_SCHEMA_LICENSE]: [{ "@id": BY }] }, []],
373
+ [{ [CC_LICENSE]: [{ "@id": CC0 }, { "@value": BY }] }, [CC0, BY]],
374
+ [{
375
+ [DC_LICENSE]: [],
376
+ [SCHEMA_LICENSE]: [{ "@value": BY }]
377
+ }, [BY]],
378
+ [{
379
+ [DC_LICENSE]: [{ "@id": "CC-BY-4.0" }],
380
+ [SCHEMA_LICENSE]: [{ "@id": BY }]
381
+ }, []],
382
+ [{}, []]
383
+ ];
384
+ for (const [properties, expected] of cases) deepStrictEqual((await Note.fromJsonLd({
385
+ "@id": id,
386
+ "@type": [`${AS}#Note`],
387
+ ...properties
388
+ }, { documentLoader: noFetch })).licenses, expected.map((l) => new URL(l)));
389
+ });
390
+ test("FEP-6757 schema.org context order", async () => {
391
+ for (const [context, predicate, expected] of [[
392
+ [
393
+ AS,
394
+ FEP,
395
+ "http://schema.org/"
396
+ ],
397
+ HTTP_SCHEMA_LICENSE,
398
+ []
399
+ ], [
400
+ [
401
+ AS,
402
+ "http://schema.org/",
403
+ FEP
404
+ ],
405
+ DC_LICENSE,
406
+ [new URL(BY)]
407
+ ]]) {
408
+ const json = {
409
+ "@context": context,
410
+ id: "https://social.example/note/5",
411
+ type: "Note",
412
+ license: BY
413
+ };
414
+ deepStrictEqual(Object.keys(await expand(json)).includes(predicate), true);
415
+ deepStrictEqual((await Note.fromJsonLd(json, {
416
+ documentLoader: noFetch,
417
+ contextLoader: offlineContextLoader()
418
+ })).licenses, expected);
419
+ }
420
+ for (const extra of [{}, { "@language": "und" }]) deepStrictEqual((await Note.fromJsonLd({
421
+ "@context": [AS, {
422
+ schema: "https://schema.org/",
423
+ ...extra
424
+ }],
425
+ type: "Note",
426
+ "schema:license": BY
427
+ }, { documentLoader: noFetch })).licenses, [new URL(BY)]);
428
+ });
429
+ test("FEP-6757 PeerTube licences round-trip unchanged", async () => {
430
+ const video = {
431
+ "@context": [AS, {
432
+ sc: "http://schema.org/",
433
+ licence: "sc:license",
434
+ uuid: "sc:identifier"
435
+ }],
436
+ type: "Video",
437
+ id: "https://peertube.example/videos/watch/1",
438
+ name: "A video",
439
+ uuid: "e5a2d4b0-0000-4000-8000-000000000000",
440
+ licence: {
441
+ identifier: "1",
442
+ name: "Attribution"
443
+ }
444
+ };
445
+ const parsed = await Video.fromJsonLd(video, { documentLoader: noFetch });
446
+ deepStrictEqual(parsed.licenses, []);
447
+ deepStrictEqual(await parsed.toJsonLd(), video);
448
+ const create = {
449
+ "@context": video["@context"],
450
+ type: "Create",
451
+ id: "https://peertube.example/videos/watch/1/activity",
452
+ actor: "https://peertube.example/accounts/alice",
453
+ object: {
454
+ ...video,
455
+ "@context": void 0
456
+ }
457
+ };
458
+ delete create.object["@context"];
459
+ deepStrictEqual(await (await Create.fromJsonLd(create, { documentLoader: noFetch })).toJsonLd(), create);
460
+ });
461
+ test("FEP-6757 rejects license names and relative identifiers", async () => {
462
+ const base = {
463
+ "@context": [AS, FEP],
464
+ id: "https://social.example/note/6",
465
+ type: "Note"
466
+ };
467
+ for (const license of [
468
+ "CC-BY-4.0",
469
+ "Creative Commons Attribution 4.0 International",
470
+ "licenses/by/4.0/",
471
+ "_:license"
472
+ ]) {
473
+ const parsed = await Note.fromJsonLd({
474
+ ...base,
475
+ license
476
+ }, { documentLoader: noFetch });
477
+ deepStrictEqual(parsed.licenses, []);
478
+ ok(!("license" in record(await parsed.toJsonLd())));
479
+ }
480
+ const mixed = await Note.fromJsonLd({
481
+ ...base,
482
+ license: [
483
+ "CC-BY-4.0",
484
+ BY,
485
+ "urn:example:license"
486
+ ]
487
+ }, { documentLoader: noFetch });
488
+ deepStrictEqual(mixed.licenses, [new URL(BY), new URL("urn:example:license")]);
489
+ deepStrictEqual(record(await mixed.toJsonLd()).license, [BY, "urn:example:license"]);
490
+ deepStrictEqual((await Note.fromJsonLd({
491
+ ...base,
492
+ "@context": [
493
+ AS,
494
+ FEP,
495
+ { "@base": "https://licenses.example/" }
496
+ ],
497
+ license: "by/4.0/"
498
+ }, { documentLoader: noFetch })).licenses, [new URL("https://licenses.example/by/4.0/")]);
499
+ for (const value of [{
500
+ "@value": BY,
501
+ "@type": "http://www.w3.org/2001/XMLSchema#date"
502
+ }, {
503
+ "@value": "CC-BY-4.0",
504
+ "@language": "en"
505
+ }]) deepStrictEqual((await Note.fromJsonLd({
506
+ "@type": [`${AS}#Note`],
507
+ [DC_LICENSE]: [value]
508
+ }, { documentLoader: noFetch })).licenses, []);
509
+ deepStrictEqual((await Note.fromJsonLd({
510
+ "@type": [`${AS}#Note`],
511
+ [DC_LICENSE]: [{
512
+ "@value": BY,
513
+ "@type": "http://www.w3.org/2001/XMLSchema#anyURI"
514
+ }]
515
+ }, { documentLoader: noFetch })).licenses, [new URL(BY)]);
516
+ });
517
+ test("FEP-6757 license IRIs are never dereferenced", async () => {
518
+ const requested = [];
519
+ const documentLoader = (url) => {
520
+ requested.push(url);
521
+ throw new Error(`Unexpected fetch: ${url}`);
522
+ };
523
+ const contextLoader = offlineContextLoader();
524
+ const parsed = await Note.fromJsonLd(noteExample, {
525
+ documentLoader,
526
+ contextLoader
527
+ });
528
+ deepStrictEqual(parsed.licenses, [new URL(BY)]);
529
+ await parsed.clone({ content: "Edited" }).toJsonLd({ contextLoader });
530
+ deepStrictEqual((await Person.fromJsonLd(personExample, {
531
+ documentLoader,
532
+ contextLoader
533
+ })).preferredLicense, new URL(BY_NC));
534
+ deepStrictEqual(requested, []);
535
+ for (const url of contextLoader.loaded) ok(url in preloadedContexts);
536
+ });
537
+ //#endregion
538
+ export {};
@@ -112,3 +112,28 @@ properties:
112
112
  range:
113
113
  - "https://www.w3.org/ns/activitystreams#Link"
114
114
  - "https://www.w3.org/ns/activitystreams#Object"
115
+
116
+ - pluralName: licenses
117
+ singularName: license
118
+ compactName: license
119
+ uri: "http://purl.org/dc/terms/license"
120
+ extraContext: "https://w3id.org/fep/6757"
121
+ redundantProperties:
122
+ - uri: "https://schema.org/license"
123
+ - uri: "http://creativecommons.org/ns#license"
124
+ redundantPropertiesWrite: canonical
125
+ description: |
126
+ The copyright licenses for the linked resource, as defined by
127
+ [FEP-6757](https://w3id.org/fep/6757). Each license is identified by its
128
+ URI, such as <https://creativecommons.org/publicdomain/mark/1.0/>; license
129
+ names and SPDX short identifiers are not license URIs. Multiple values are
130
+ alternatives that a consumer may choose from; their order has no meaning.
131
+
132
+ An empty list means the link carries no license metadata. Fedify does not
133
+ synthesize a default license for it.
134
+
135
+ The `https://schema.org/license` and
136
+ `http://creativecommons.org/ns#license` properties are read as synonyms
137
+ when this property is absent, but only this property is written.
138
+ range:
139
+ - "fedify:absoluteIri"
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Object$1 } from "./vocab-EFWWufqL.mjs";
4
- import { i as isActor, o as name, s as version } from "./actor-qViYned_.mjs";
3
+ import { A as Object$1 } from "./vocab-Cg2PG-xH.mjs";
4
+ import { i as isActor, o as name, s as version } from "./actor-BbIjMjNa.mjs";
5
5
  import { t as getTypeId } from "./type-BMdjm4Eg.mjs";
6
6
  import { canonicalizePortableUri, formatIri, getDocumentLoader, haveSameFe34Origin, haveSameIriOrigin, parseIri } from "@fedify/vocab-runtime";
7
7
  import { lookupWebFinger } from "@fedify/webfinger";
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Object$1, F as Person, k as Note, s as Collection } from "./vocab-EFWWufqL.mjs";
4
- import { t as esm_default } from "./esm-Cncdk-Cm.mjs";
5
- import { n as traverseCollection, t as lookupObject } from "./lookup-DHfL6oZO.mjs";
3
+ import { A as Object$1, F as Person, k as Note, s as Collection } from "./vocab-Cg2PG-xH.mjs";
4
+ import { t as esm_default } from "./esm-rg6f9oLR.mjs";
5
+ import { n as traverseCollection, t as lookupObject } from "./lookup-DKLMyPMJ.mjs";
6
6
  import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
7
7
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
8
8
  import { deepStrictEqual, equal, ok, rejects } from "node:assert/strict";