@fedify/vocab-runtime 2.4.0-dev.1570 → 2.4.0-dev.1571

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 (48) hide show
  1. package/deno.json +2 -1
  2. package/dist/docloader-DnUMWHaJ.d.cts +202 -0
  3. package/dist/docloader-xRGn1azD.d.ts +202 -0
  4. package/dist/internal/jsonld-cache.cjs +279 -0
  5. package/dist/internal/jsonld-cache.d.cts +48 -0
  6. package/dist/internal/jsonld-cache.d.ts +48 -0
  7. package/dist/internal/jsonld-cache.js +275 -0
  8. package/dist/mod.cjs +178 -190
  9. package/dist/mod.d.cts +59 -190
  10. package/dist/mod.d.ts +59 -190
  11. package/dist/mod.js +165 -184
  12. package/dist/tests/decimal.test.cjs +3 -3
  13. package/dist/tests/decimal.test.mjs +3 -3
  14. package/dist/tests/{docloader-C76ldE5C.mjs → docloader-DHSAmRW2.mjs} +97 -10
  15. package/dist/tests/{docloader-mvgIWKI7.cjs → docloader-DkiPIIAk.cjs} +102 -9
  16. package/dist/tests/docloader.test.cjs +58 -6
  17. package/dist/tests/docloader.test.mjs +58 -6
  18. package/dist/tests/jsonld-cache.test.cjs +652 -0
  19. package/dist/tests/jsonld-cache.test.d.cts +1 -0
  20. package/dist/tests/jsonld-cache.test.d.mts +1 -0
  21. package/dist/tests/jsonld-cache.test.mjs +651 -0
  22. package/dist/tests/{key-CrrK9mYh.mjs → key-CDGDH_vC.mjs} +69 -1
  23. package/dist/tests/{key-pMmqUKuo.cjs → key-_wXwomh_.cjs} +86 -0
  24. package/dist/tests/key.test.cjs +48 -1
  25. package/dist/tests/key.test.mjs +48 -1
  26. package/dist/tests/{request-BEXkv1ul.mjs → request-CYFeP37O.mjs} +1 -1
  27. package/dist/tests/{request-SworbvLc.cjs → request-D3H8nWtX.cjs} +1 -1
  28. package/dist/tests/request.test.cjs +1 -1
  29. package/dist/tests/request.test.mjs +1 -1
  30. package/dist/tests/{url-C20FhC7p.cjs → url-2XwVbUS_.cjs} +108 -0
  31. package/dist/tests/{url-m9Qzxy-Y.mjs → url-YWJbnRlf.mjs} +85 -1
  32. package/dist/tests/url.test.cjs +104 -1
  33. package/dist/tests/url.test.mjs +105 -2
  34. package/dist/url-BAdyyqAa.cjs +315 -0
  35. package/dist/url-BuxPHxK2.js +261 -0
  36. package/package.json +11 -1
  37. package/src/contexts/fep-7aa9.json +24 -0
  38. package/src/contexts.ts +2 -0
  39. package/src/docloader.test.ts +102 -6
  40. package/src/docloader.ts +76 -8
  41. package/src/internal/jsonld-cache.ts +538 -0
  42. package/src/jsonld-cache.test.ts +554 -0
  43. package/src/key.test.ts +86 -0
  44. package/src/key.ts +125 -0
  45. package/src/mod.ts +8 -0
  46. package/src/url.test.ts +252 -1
  47. package/src/url.ts +141 -0
  48. package/tsdown.config.ts +6 -1
@@ -0,0 +1,554 @@
1
+ import { deepStrictEqual, ok, strictEqual } from "node:assert";
2
+ import { test } from "node:test";
3
+ import {
4
+ compactJsonLdCache,
5
+ getJsonLdContext,
6
+ isTrustedIriOrigin,
7
+ normalizeJsonLdIris,
8
+ } from "./internal/jsonld-cache.ts";
9
+ import jsonld from "./jsonld.ts";
10
+ import { parseIri } from "./url.ts";
11
+
12
+ test("isTrustedIriOrigin() trusts same portable IRI origins", () => {
13
+ ok(isTrustedIriOrigin(
14
+ {},
15
+ parseIri("ap://did:key:z6Mkabc/actor"),
16
+ parseIri("ap+ef61://did:key:z6Mkabc/outbox"),
17
+ ));
18
+ ok(
19
+ !isTrustedIriOrigin(
20
+ {},
21
+ parseIri("ap://did:key:z6Mkabc/actor"),
22
+ parseIri("ap://did:key:z6Mkdef/outbox"),
23
+ ),
24
+ );
25
+ ok(isTrustedIriOrigin(
26
+ { crossOrigin: "trust" },
27
+ parseIri("ap://did:key:z6Mkabc/actor"),
28
+ parseIri("ap://did:key:z6Mkdef/outbox"),
29
+ ));
30
+ });
31
+
32
+ test("normalizeJsonLdIris() normalizes selected JSON-LD IRI positions", () => {
33
+ const iriKeys = new Set(["@id", "https://example.com/ns#ref"]);
34
+ const value = {
35
+ "@id": "ap://did:key:z6Mkabc/object",
36
+ "https://example.com/ns#ref": [
37
+ { "@value": "ap://did:key:z6Mkabc/ref" },
38
+ ],
39
+ "https://example.com/ns#text": [
40
+ { "@value": "ap://did:key:z6Mkabc/not-an-iri-position" },
41
+ ],
42
+ };
43
+
44
+ deepStrictEqual(normalizeJsonLdIris(value, iriKeys), {
45
+ "@id": "ap+ef61://did:key:z6Mkabc/object",
46
+ "https://example.com/ns#ref": [
47
+ { "@value": "ap+ef61://did:key:z6Mkabc/ref" },
48
+ ],
49
+ "https://example.com/ns#text": [
50
+ { "@value": "ap://did:key:z6Mkabc/not-an-iri-position" },
51
+ ],
52
+ });
53
+ });
54
+
55
+ test("normalizeJsonLdIris() preserves IRI context in list/set wrappers", () => {
56
+ const iriKeys = new Set(["https://example.com/ns#ref"]);
57
+ const value = {
58
+ "https://example.com/ns#ref": [
59
+ {
60
+ "@list": [
61
+ { "@value": "ap://did:key:z6Mkabc/listed" },
62
+ ],
63
+ },
64
+ {
65
+ "@set": [
66
+ { "@value": "ap://did:key:z6Mkabc/set" },
67
+ ],
68
+ },
69
+ ],
70
+ };
71
+
72
+ deepStrictEqual(normalizeJsonLdIris(value, iriKeys), {
73
+ "https://example.com/ns#ref": [
74
+ {
75
+ "@list": [
76
+ { "@value": "ap+ef61://did:key:z6Mkabc/listed" },
77
+ ],
78
+ },
79
+ {
80
+ "@set": [
81
+ { "@value": "ap+ef61://did:key:z6Mkabc/set" },
82
+ ],
83
+ },
84
+ ],
85
+ });
86
+ });
87
+
88
+ test("normalizeJsonLdIris() defines prototype-like keys safely", () => {
89
+ const iriKeys = new Set(["__proto__"]);
90
+ const value: Record<string, unknown> = {};
91
+ globalThis.Object.defineProperty(value, "__proto__", {
92
+ value: "ap://did:key:z6Mkabc/object",
93
+ enumerable: true,
94
+ configurable: true,
95
+ writable: true,
96
+ });
97
+
98
+ const normalized = normalizeJsonLdIris(value, iriKeys) as Record<
99
+ string,
100
+ unknown
101
+ >;
102
+
103
+ strictEqual(
104
+ globalThis.Object.getOwnPropertyDescriptor(normalized, "__proto__")?.value,
105
+ "ap+ef61://did:key:z6Mkabc/object",
106
+ );
107
+ strictEqual(globalThis.Object.getPrototypeOf(normalized), Object.prototype);
108
+ });
109
+
110
+ test("getJsonLdContext() finds nested contexts", () => {
111
+ const context = { name: "https://example.com/ns#name" };
112
+ deepStrictEqual(
113
+ getJsonLdContext([{ type: "Note" }, { "@context": context }]),
114
+ context,
115
+ );
116
+ });
117
+
118
+ test("compactJsonLdCache() preserves no-context object shape", async () => {
119
+ const original = {
120
+ "@id": "ap://did:key:z6Mkabc/objects/1",
121
+ "@type": ["https://www.w3.org/ns/activitystreams#Note"],
122
+ "https://www.w3.org/ns/activitystreams#attributedTo": [
123
+ { "@id": "ap://did:key:z6Mkabc/actor" },
124
+ ],
125
+ "https://www.w3.org/ns/activitystreams#content": [
126
+ { "@value": "No-context object shape should stay cached." },
127
+ ],
128
+ };
129
+ const expanded = await jsonld.expand(original);
130
+ const normalized = normalizeJsonLdIris(
131
+ expanded,
132
+ new Set([
133
+ "@id",
134
+ "https://www.w3.org/ns/activitystreams#attributedTo",
135
+ ]),
136
+ );
137
+
138
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
139
+ "@id": "ap+ef61://did:key:z6Mkabc/objects/1",
140
+ "@type": ["https://www.w3.org/ns/activitystreams#Note"],
141
+ "https://www.w3.org/ns/activitystreams#attributedTo": [
142
+ { "@id": "ap+ef61://did:key:z6Mkabc/actor" },
143
+ ],
144
+ "https://www.w3.org/ns/activitystreams#content": [
145
+ { "@value": "No-context object shape should stay cached." },
146
+ ],
147
+ });
148
+ });
149
+
150
+ test("compactJsonLdCache() ignores nested contexts for no-context parents", async () => {
151
+ const nestedContext = {
152
+ type: "@type",
153
+ name: "https://www.w3.org/ns/activitystreams#name",
154
+ };
155
+ const original = {
156
+ "@id": "ap://did:key:z6Mkabc/objects/1",
157
+ "@type": ["https://www.w3.org/ns/activitystreams#Note"],
158
+ "https://www.w3.org/ns/activitystreams#attachment": [
159
+ {
160
+ "@context": nestedContext,
161
+ type: "https://www.w3.org/ns/activitystreams#Object",
162
+ name: "Nested object",
163
+ },
164
+ ],
165
+ };
166
+ const expanded = await jsonld.expand(original);
167
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
168
+
169
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
170
+ "@id": "ap+ef61://did:key:z6Mkabc/objects/1",
171
+ "@type": ["https://www.w3.org/ns/activitystreams#Note"],
172
+ "https://www.w3.org/ns/activitystreams#attachment": [
173
+ {
174
+ "@context": nestedContext,
175
+ type: "https://www.w3.org/ns/activitystreams#Object",
176
+ name: "Nested object",
177
+ },
178
+ ],
179
+ });
180
+ });
181
+
182
+ test("compactJsonLdCache() defines no-context prototype-like keys safely", async () => {
183
+ const original: Record<string, unknown> = {
184
+ "@id": "ap://did:key:z6Mkabc/objects/1",
185
+ };
186
+ globalThis.Object.defineProperty(original, "__proto__", {
187
+ value: "ap://did:key:z6Mkabc/proto",
188
+ enumerable: true,
189
+ configurable: true,
190
+ writable: true,
191
+ });
192
+ const normalized: Record<string, unknown> = {
193
+ "@id": "ap+ef61://did:key:z6Mkabc/objects/1",
194
+ };
195
+ globalThis.Object.defineProperty(normalized, "__proto__", {
196
+ value: "ap+ef61://did:key:z6Mkabc/proto",
197
+ enumerable: true,
198
+ configurable: true,
199
+ writable: true,
200
+ });
201
+
202
+ const compacted = await compactJsonLdCache([normalized], original) as Record<
203
+ string,
204
+ unknown
205
+ >;
206
+
207
+ strictEqual(
208
+ globalThis.Object.getOwnPropertyDescriptor(compacted, "__proto__")?.value,
209
+ "ap+ef61://did:key:z6Mkabc/proto",
210
+ );
211
+ strictEqual(globalThis.Object.getPrototypeOf(compacted), Object.prototype);
212
+ });
213
+
214
+ test("compactJsonLdCache() preserves nested unmapped terms", async () => {
215
+ const context = {
216
+ as: "https://www.w3.org/ns/activitystreams#",
217
+ id: "@id",
218
+ type: "@type",
219
+ attachment: { "@id": "as:attachment" },
220
+ name: "as:name",
221
+ };
222
+ const original = {
223
+ "@context": context,
224
+ type: "as:Note",
225
+ id: "ap://did:key:z6Mkabc/objects/1",
226
+ attachment: {
227
+ type: "as:Object",
228
+ name: "Attachment with an unmapped extension.",
229
+ extra: "This nested unmapped property should stay cached.",
230
+ },
231
+ };
232
+ const expanded = await jsonld.expand(original);
233
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
234
+
235
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
236
+ "@context": context,
237
+ type: "as:Note",
238
+ id: "ap+ef61://did:key:z6Mkabc/objects/1",
239
+ attachment: {
240
+ type: "as:Object",
241
+ name: "Attachment with an unmapped extension.",
242
+ extra: "This nested unmapped property should stay cached.",
243
+ },
244
+ });
245
+ });
246
+
247
+ test("compactJsonLdCache() reuses unchanged unmapped values", async () => {
248
+ const context = {
249
+ as: "https://www.w3.org/ns/activitystreams#",
250
+ id: "@id",
251
+ type: "@type",
252
+ attachment: { "@id": "as:attachment" },
253
+ name: "as:name",
254
+ };
255
+ const extra = { source: "unchanged nested extension" };
256
+ const rootExtra = { source: "unchanged root extension" };
257
+ const original = {
258
+ "@context": context,
259
+ type: "as:Note",
260
+ id: "ap://did:key:z6Mkabc/objects/1",
261
+ rootExtra,
262
+ attachment: {
263
+ type: "as:Object",
264
+ name: "Attachment with an unmapped extension.",
265
+ extra,
266
+ },
267
+ };
268
+ const expanded = await jsonld.expand(original);
269
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
270
+ const compacted = await compactJsonLdCache(normalized, original) as {
271
+ rootExtra: unknown;
272
+ attachment: { extra: unknown };
273
+ };
274
+
275
+ strictEqual(compacted.rootExtra, rootExtra);
276
+ strictEqual(compacted.attachment.extra, extra);
277
+ });
278
+
279
+ test("compactJsonLdCache() defines prototype-like unmapped keys safely", async () => {
280
+ const context = {
281
+ id: "@id",
282
+ type: "@type",
283
+ };
284
+ const protoValue = { source: "own __proto__ extension" };
285
+ const original: Record<string, unknown> = {
286
+ "@context": context,
287
+ type: "https://example.com/ns#Object",
288
+ id: "ap://did:key:z6Mkabc/objects/1",
289
+ };
290
+ globalThis.Object.defineProperty(original, "__proto__", {
291
+ value: protoValue,
292
+ enumerable: true,
293
+ configurable: true,
294
+ writable: true,
295
+ });
296
+ const expanded = await jsonld.expand(original);
297
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
298
+ const compacted = await compactJsonLdCache(normalized, original) as Record<
299
+ string,
300
+ unknown
301
+ >;
302
+
303
+ strictEqual(
304
+ globalThis.Object.getOwnPropertyDescriptor(compacted, "__proto__")?.value,
305
+ protoValue,
306
+ );
307
+ strictEqual(globalThis.Object.getPrototypeOf(compacted), Object.prototype);
308
+ });
309
+
310
+ test("compactJsonLdCache() checks prototype-like aliases safely", async () => {
311
+ const context: Record<string, unknown> = {
312
+ ex: "https://example.com/ns#",
313
+ id: "@id",
314
+ represented: "ex:represented",
315
+ };
316
+ globalThis.Object.defineProperty(context, "__proto__", {
317
+ value: "ex:represented",
318
+ enumerable: true,
319
+ configurable: true,
320
+ writable: true,
321
+ });
322
+ const original: Record<string, unknown> = {
323
+ "@context": context,
324
+ id: "ap://did:key:z6Mkabc/objects/1",
325
+ represented: "Compacted term already present.",
326
+ };
327
+ globalThis.Object.defineProperty(original, "__proto__", {
328
+ value: "Alias represented by the compacted term.",
329
+ enumerable: true,
330
+ configurable: true,
331
+ writable: true,
332
+ });
333
+ const expanded = await jsonld.expand(original);
334
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
335
+ const compacted = await compactJsonLdCache(normalized, original) as Record<
336
+ string,
337
+ unknown
338
+ >;
339
+
340
+ const protoDescriptor = globalThis.Object.getOwnPropertyDescriptor(
341
+ compacted,
342
+ "__proto__",
343
+ );
344
+ ok(
345
+ protoDescriptor?.value === "Alias represented by the compacted term." ||
346
+ Array.isArray(protoDescriptor?.value) &&
347
+ protoDescriptor.value.includes(
348
+ "Alias represented by the compacted term.",
349
+ ),
350
+ );
351
+ strictEqual(protoDescriptor?.enumerable, true);
352
+ strictEqual(compacted.id, "ap+ef61://did:key:z6Mkabc/objects/1");
353
+ strictEqual(
354
+ globalThis.Object.getPrototypeOf(compacted),
355
+ Object.prototype,
356
+ );
357
+ });
358
+
359
+ test("compactJsonLdCache() preserves nested contexts", async () => {
360
+ const context = {
361
+ as: "https://www.w3.org/ns/activitystreams#",
362
+ id: "@id",
363
+ type: "@type",
364
+ attachment: { "@id": "as:attachment" },
365
+ name: "as:name",
366
+ };
367
+ const nestedContext = {
368
+ id: "@id",
369
+ type: "@type",
370
+ title: "https://example.com/ns#title",
371
+ };
372
+ const original = {
373
+ "@context": context,
374
+ type: "as:Note",
375
+ id: "ap://did:key:z6Mkabc/objects/1",
376
+ attachment: {
377
+ "@context": nestedContext,
378
+ type: "as:Object",
379
+ title: "Nested title.",
380
+ },
381
+ };
382
+ const expanded = await jsonld.expand(original);
383
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
384
+
385
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
386
+ "@context": context,
387
+ type: "as:Note",
388
+ id: "ap+ef61://did:key:z6Mkabc/objects/1",
389
+ attachment: {
390
+ "@context": nestedContext,
391
+ type: "as:Object",
392
+ "https://example.com/ns#title": "Nested title.",
393
+ },
394
+ });
395
+ });
396
+
397
+ test("compactJsonLdCache() preserves nested array contexts", async () => {
398
+ const context = {
399
+ as: "https://www.w3.org/ns/activitystreams#",
400
+ id: "@id",
401
+ type: "@type",
402
+ attachment: { "@id": "as:attachment" },
403
+ };
404
+ const nestedContext = [
405
+ {
406
+ id: "@id",
407
+ type: "@type",
408
+ },
409
+ {
410
+ title: "https://example.com/ns#title",
411
+ },
412
+ ];
413
+ const original = {
414
+ "@context": context,
415
+ type: "as:Note",
416
+ id: "ap://did:key:z6Mkabc/objects/1",
417
+ attachment: {
418
+ "@context": nestedContext,
419
+ type: "as:Object",
420
+ title: "Nested title.",
421
+ },
422
+ };
423
+ const expanded = await jsonld.expand(original);
424
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
425
+
426
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
427
+ "@context": context,
428
+ type: "as:Note",
429
+ id: "ap+ef61://did:key:z6Mkabc/objects/1",
430
+ attachment: {
431
+ "@context": nestedContext,
432
+ type: "as:Object",
433
+ "https://example.com/ns#title": "Nested title.",
434
+ },
435
+ });
436
+ });
437
+
438
+ test("compactJsonLdCache() preserves nested null contexts", async () => {
439
+ const context = {
440
+ as: "https://www.w3.org/ns/activitystreams#",
441
+ id: "@id",
442
+ type: "@type",
443
+ attachment: { "@id": "as:attachment" },
444
+ title: "https://example.com/ns#title",
445
+ };
446
+ const original = {
447
+ "@context": context,
448
+ type: "as:Note",
449
+ id: "ap://did:key:z6Mkabc/objects/1",
450
+ attachment: {
451
+ "@context": null,
452
+ "https://example.com/ns#title": "Absolute title.",
453
+ },
454
+ };
455
+ const expanded = await jsonld.expand(original);
456
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
457
+
458
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
459
+ "@context": context,
460
+ type: "as:Note",
461
+ id: "ap+ef61://did:key:z6Mkabc/objects/1",
462
+ attachment: {
463
+ "@context": null,
464
+ "https://example.com/ns#title": "Absolute title.",
465
+ },
466
+ });
467
+ });
468
+
469
+ test("compactJsonLdCache() does not re-add represented nested aliases", async () => {
470
+ const context = {
471
+ as: "https://www.w3.org/ns/activitystreams#",
472
+ id: "@id",
473
+ type: "@type",
474
+ attachment: { "@id": "as:attachment" },
475
+ actor: { "@id": "as:actor", "@type": "@id" },
476
+ };
477
+ const nestedContext = {
478
+ id: "@id",
479
+ type: "@type",
480
+ actorRef: { "@id": "as:actor", "@type": "@id" },
481
+ };
482
+ const original = {
483
+ "@context": context,
484
+ type: "as:Note",
485
+ id: "ap://did:key:z6Mkabc/objects/1",
486
+ attachment: {
487
+ "@context": nestedContext,
488
+ type: "as:Object",
489
+ actorRef: "ap://did:key:z6Mkabc/actor",
490
+ },
491
+ };
492
+ const expanded = await jsonld.expand(original);
493
+ const normalized = normalizeJsonLdIris(
494
+ expanded,
495
+ new Set(["@id", "https://www.w3.org/ns/activitystreams#actor"]),
496
+ );
497
+
498
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
499
+ "@context": context,
500
+ type: "as:Note",
501
+ id: "ap+ef61://did:key:z6Mkabc/objects/1",
502
+ attachment: {
503
+ "@context": nestedContext,
504
+ type: "as:Object",
505
+ actor: "ap+ef61://did:key:z6Mkabc/actor",
506
+ },
507
+ });
508
+ });
509
+
510
+ test("compactJsonLdCache() does not confuse dummy marker prefixes", async () => {
511
+ const context = {
512
+ ex: "https://example.com/ns#",
513
+ id: "@id",
514
+ represented: "ex:represented",
515
+ key10: "ex:represented",
516
+ };
517
+ const original = {
518
+ "@context": context,
519
+ id: "ap://did:key:z6Mkabc/objects/1",
520
+ represented: "Compacted term already present.",
521
+ key0: "Extension 0",
522
+ key1: "Extension 1",
523
+ key2: "Extension 2",
524
+ key3: "Extension 3",
525
+ key4: "Extension 4",
526
+ key5: "Extension 5",
527
+ key6: "Extension 6",
528
+ key7: "Extension 7",
529
+ key8: "Extension 8",
530
+ key9: "Extension 9",
531
+ key10: "Alias represented by the compacted term.",
532
+ };
533
+ const expanded = await jsonld.expand(original);
534
+ const normalized = normalizeJsonLdIris(expanded, new Set(["@id"]));
535
+
536
+ deepStrictEqual(await compactJsonLdCache(normalized, original), {
537
+ "@context": context,
538
+ id: "ap+ef61://did:key:z6Mkabc/objects/1",
539
+ key0: "Extension 0",
540
+ key1: "Extension 1",
541
+ key2: "Extension 2",
542
+ key3: "Extension 3",
543
+ key4: "Extension 4",
544
+ key5: "Extension 5",
545
+ key6: "Extension 6",
546
+ key7: "Extension 7",
547
+ key8: "Extension 8",
548
+ key9: "Extension 9",
549
+ key10: [
550
+ "Alias represented by the compacted term.",
551
+ "Compacted term already present.",
552
+ ],
553
+ });
554
+ });
package/src/key.test.ts CHANGED
@@ -2,13 +2,17 @@ import { deepStrictEqual, rejects } from "node:assert";
2
2
  import { test } from "node:test";
3
3
  import { exportJwk, importJwk } from "./jwk.ts";
4
4
  import {
5
+ exportDidKey,
5
6
  exportMultibaseKey,
6
7
  exportSpki,
8
+ importDidKey,
7
9
  importMultibaseKey,
8
10
  importPem,
9
11
  importPkcs1,
10
12
  importSpki,
13
+ parseDidKeyVerificationMethod,
11
14
  } from "./key.ts";
15
+ import { addMulticodecPrefix } from "./internal/multicodec.ts";
12
16
  import { encodeMultibase } from "./multibase/mod.ts";
13
17
 
14
18
  // cSpell: disable
@@ -198,3 +202,85 @@ test("exportMultibaseKey()", async () => {
198
202
  // cSpell: enable
199
203
  );
200
204
  });
205
+
206
+ test("exportDidKey() and importDidKey()", async () => {
207
+ const ed25519Key = await importJwk(ed25519Jwk, "public");
208
+ const did = await exportDidKey(ed25519Key);
209
+ deepStrictEqual(did, `did:key:${ed25519Multibase}`);
210
+ deepStrictEqual(await exportJwk(await importDidKey(did)), ed25519Jwk);
211
+ deepStrictEqual(
212
+ await exportJwk(await importDidKey(new URL(did))),
213
+ ed25519Jwk,
214
+ );
215
+
216
+ const rsaKey = await importJwk(rsaJwk, "public");
217
+ await rejects(
218
+ () => exportDidKey(rsaKey),
219
+ TypeError,
220
+ );
221
+ await rejects(
222
+ () => importDidKey(`did:key:${rsaMultibase}`),
223
+ new TypeError("Unsupported did:key type: 0x1205"),
224
+ );
225
+ });
226
+
227
+ test("parseDidKeyVerificationMethod()", async () => {
228
+ const verificationMethod = `did:key:${ed25519Multibase}#${ed25519Multibase}`;
229
+ const parsed = await parseDidKeyVerificationMethod(verificationMethod);
230
+ deepStrictEqual(parsed.id, new URL(verificationMethod));
231
+ deepStrictEqual(parsed.controller, new URL(`did:key:${ed25519Multibase}`));
232
+ deepStrictEqual(parsed.publicKeyMultibase, ed25519Multibase);
233
+ deepStrictEqual(await exportJwk(parsed.publicKey), ed25519Jwk);
234
+ });
235
+
236
+ test("did:key helpers reject malformed values", async () => {
237
+ const ed25519Key = await importJwk(ed25519Jwk, "public");
238
+ const raw = new Uint8Array(await crypto.subtle.exportKey("raw", ed25519Key));
239
+ const shortEd25519 = new TextDecoder().decode(
240
+ encodeMultibase(
241
+ "base58btc",
242
+ addMulticodecPrefix(0xed, raw.slice(0, 31)),
243
+ ),
244
+ );
245
+ const base64UrlEd25519 = new TextDecoder().decode(
246
+ encodeMultibase(
247
+ "base64url",
248
+ addMulticodecPrefix(0xed, raw),
249
+ ),
250
+ );
251
+ const malformedMulticodec = new TextDecoder().decode(
252
+ encodeMultibase("base58btc", Uint8Array.from([0x80])),
253
+ );
254
+
255
+ for (
256
+ const value of [
257
+ "did:web:example.com",
258
+ "did:key:",
259
+ `did:key:${ed25519Multibase}/path`,
260
+ `did:key:${ed25519Multibase}?query`,
261
+ `did:key:${ed25519Multibase}#${ed25519Multibase}`,
262
+ "did:key:z0",
263
+ `did:key:${base64UrlEd25519}`,
264
+ `did:key:${shortEd25519}`,
265
+ `did:key:${malformedMulticodec}`,
266
+ ]
267
+ ) {
268
+ await rejects(() => importDidKey(value), TypeError);
269
+ }
270
+
271
+ for (
272
+ const value of [
273
+ `did:key:${ed25519Multibase}`,
274
+ `did:key:${ed25519Multibase}#`,
275
+ `did:key:${ed25519Multibase}#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK`,
276
+ `did:key:${ed25519Multibase}#${base64UrlEd25519}`,
277
+ "did:key:z0#z0",
278
+ `did:key:${rsaMultibase}#${rsaMultibase}`,
279
+ `did:key:${shortEd25519}#${shortEd25519}`,
280
+ `did:key:${malformedMulticodec}#${malformedMulticodec}`,
281
+ "https://example.com/key",
282
+ ]
283
+ ) {
284
+ await rejects(() => parseDidKeyVerificationMethod(value), TypeError);
285
+ }
286
+ });