@fedify/vocab-runtime 2.4.0-pr.936.41 → 2.4.1

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 (152) hide show
  1. package/deno.json +4 -2
  2. package/dist/{tests/docloader-Ck8bcBir.mjs → contexts-B09jb9Pi.cjs} +173 -274
  3. package/dist/{tests/docloader-S_gQZ2tt.cjs → contexts-DWrj4AGR.js} +169 -293
  4. package/dist/{docloader-C_dir7Xb.d.ts → docloader-BMHljyYb.d.cts} +68 -2
  5. package/dist/{docloader-D2DTRiyA.d.cts → docloader-BMHljyYb.d.ts} +68 -2
  6. package/dist/internal/jsonld-cache.cjs +70 -1
  7. package/dist/internal/jsonld-cache.d.cts +35 -2
  8. package/dist/internal/jsonld-cache.d.ts +35 -2
  9. package/dist/internal/jsonld-cache.js +67 -2
  10. package/dist/internal/portable-dereference.cjs +721 -0
  11. package/dist/internal/portable-dereference.d.cts +349 -0
  12. package/dist/internal/portable-dereference.d.ts +349 -0
  13. package/dist/internal/portable-dereference.js +702 -0
  14. package/dist/internal/signed-representation.cjs +378 -0
  15. package/dist/internal/signed-representation.d.cts +144 -0
  16. package/dist/internal/signed-representation.d.ts +144 -0
  17. package/dist/internal/signed-representation.js +371 -0
  18. package/dist/jsonld.cjs +2 -2
  19. package/dist/mod.cjs +762 -4529
  20. package/dist/mod.d.cts +427 -9
  21. package/dist/mod.d.ts +427 -10
  22. package/dist/mod.js +742 -4524
  23. package/dist/portable-6Aj-7dZJ.d.cts +160 -0
  24. package/dist/portable-nkTiqH0w.d.ts +160 -0
  25. package/dist/tests/body-DpC9p-iG.mjs +130 -0
  26. package/dist/tests/body-cKLBprYm.cjs +153 -0
  27. package/dist/tests/body.test.cjs +89 -0
  28. package/dist/tests/body.test.d.cts +1 -0
  29. package/dist/tests/body.test.d.mts +1 -0
  30. package/dist/tests/body.test.mjs +90 -0
  31. package/dist/tests/contexts-C9Lj11SC.cjs +4533 -0
  32. package/dist/tests/contexts-duRA_dDC.mjs +4522 -0
  33. package/dist/tests/decimal.test.cjs +9 -7
  34. package/dist/tests/decimal.test.mjs +9 -6
  35. package/dist/tests/digest-3FeH2Y-Q.cjs +176 -0
  36. package/dist/tests/digest-COC7xDiQ.mjs +141 -0
  37. package/dist/tests/digest.test.cjs +102 -0
  38. package/dist/tests/digest.test.d.cts +1 -0
  39. package/dist/tests/digest.test.d.mts +1 -0
  40. package/dist/tests/digest.test.mjs +103 -0
  41. package/dist/tests/docloader-DyZ1JGIl.cjs +398 -0
  42. package/dist/tests/docloader-tUmMeXjU.mjs +375 -0
  43. package/dist/tests/docloader.test.cjs +816 -39
  44. package/dist/tests/docloader.test.mjs +810 -34
  45. package/dist/tests/internal/multicodec.test.cjs +2 -3
  46. package/dist/tests/internal/multicodec.test.mjs +2 -2
  47. package/dist/tests/internal/portable-dereference.test.cjs +172 -0
  48. package/dist/tests/internal/portable-dereference.test.d.cts +1 -0
  49. package/dist/tests/internal/portable-dereference.test.d.mts +1 -0
  50. package/dist/tests/internal/portable-dereference.test.mjs +173 -0
  51. package/dist/tests/jsonld-CmDhHNTx.cjs +12 -0
  52. package/dist/tests/jsonld-DweIJ07b.mjs +5 -0
  53. package/dist/tests/jsonld-cache-BEXiiM-V.cjs +386 -0
  54. package/dist/tests/jsonld-cache-DFnWsLuZ.mjs +339 -0
  55. package/dist/tests/jsonld-cache.test.cjs +102 -298
  56. package/dist/tests/jsonld-cache.test.mjs +95 -289
  57. package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
  58. package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
  59. package/dist/tests/key.test.cjs +6 -5
  60. package/dist/tests/key.test.mjs +6 -4
  61. package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-DYRmJqFn.mjs} +5 -2
  62. package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-_PqsL1ae.cjs} +5 -2
  63. package/dist/tests/langstr.test.cjs +11 -4
  64. package/dist/tests/langstr.test.mjs +9 -2
  65. package/dist/tests/langtag-CUppHs_s.mjs +45 -0
  66. package/dist/tests/langtag-Czjvxfzx.cjs +50 -0
  67. package/dist/tests/langtag.test.cjs +45 -0
  68. package/dist/tests/langtag.test.d.cts +1 -0
  69. package/dist/tests/langtag.test.d.mts +1 -0
  70. package/dist/tests/langtag.test.mjs +46 -0
  71. package/dist/tests/link.test.cjs +2 -3
  72. package/dist/tests/link.test.mjs +2 -2
  73. package/dist/tests/multibase/multibase.test.cjs +8 -9
  74. package/dist/tests/multibase/multibase.test.mjs +6 -6
  75. package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
  76. package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
  77. package/dist/tests/portable-dereference-DmTYDy2p.mjs +250 -0
  78. package/dist/tests/portable-dereference-qTLMqe2_.cjs +279 -0
  79. package/dist/tests/portable-media-5p8azal0.cjs +153 -0
  80. package/dist/tests/portable-media-B_lt9MMA.mjs +148 -0
  81. package/dist/tests/portable-media.test.cjs +222 -0
  82. package/dist/tests/portable-media.test.d.cts +1 -0
  83. package/dist/tests/portable-media.test.d.mts +1 -0
  84. package/dist/tests/portable-media.test.mjs +223 -0
  85. package/dist/tests/portable-workers.test.cjs +36 -0
  86. package/dist/tests/portable-workers.test.d.cts +2 -0
  87. package/dist/tests/portable-workers.test.d.mts +2 -0
  88. package/dist/tests/portable-workers.test.mjs +35 -0
  89. package/dist/tests/{request-uk51rkhO.cjs → request-BgjA9wyl.cjs} +10 -4
  90. package/dist/tests/{request-C8CaGwtt.mjs → request-DodZNukP.mjs} +8 -2
  91. package/dist/tests/request.test.cjs +7 -4
  92. package/dist/tests/request.test.mjs +5 -2
  93. package/dist/tests/signed-representation.test.cjs +588 -0
  94. package/dist/tests/signed-representation.test.d.cts +1 -0
  95. package/dist/tests/signed-representation.test.d.mts +1 -0
  96. package/dist/tests/signed-representation.test.mjs +589 -0
  97. package/dist/tests/temporal.test.cjs +1 -2
  98. package/dist/tests/temporal.test.mjs +1 -1
  99. package/dist/tests/url-BNakuZ8k.cjs +998 -0
  100. package/dist/tests/url-DMxmp7ZG.mjs +859 -0
  101. package/dist/tests/url.test.cjs +489 -6
  102. package/dist/tests/url.test.mjs +489 -5
  103. package/dist/url-DrGTR8yv.cjs +993 -0
  104. package/dist/url-Dzyp-NsC.js +860 -0
  105. package/package.json +27 -4
  106. package/scripts/test-bun.mjs +17 -0
  107. package/src/body.test.ts +125 -0
  108. package/src/body.ts +152 -0
  109. package/src/contexts/cid-v1.json +114 -0
  110. package/src/contexts/fep-22cd.json +21 -0
  111. package/src/contexts/fep-6757.json +12 -0
  112. package/src/contexts/joinmastodon.json +1 -1
  113. package/src/contexts/miscellany.json +17 -0
  114. package/src/contexts.ts +47 -1
  115. package/src/digest.test.ts +220 -0
  116. package/src/digest.ts +229 -0
  117. package/src/docloader.test.ts +1066 -27
  118. package/src/docloader.ts +322 -105
  119. package/src/internal/jsonld-cache.ts +97 -1
  120. package/src/internal/portable-dereference.test.ts +254 -0
  121. package/src/internal/portable-dereference.ts +1000 -0
  122. package/src/internal/signed-representation.ts +565 -0
  123. package/src/jsonld-cache.test.ts +89 -0
  124. package/src/key.test.ts +9 -0
  125. package/src/key.ts +11 -1
  126. package/src/langstr.test.ts +18 -1
  127. package/src/langstr.ts +6 -2
  128. package/src/langtag.test.ts +53 -0
  129. package/src/langtag.ts +76 -0
  130. package/src/mod.ts +29 -0
  131. package/src/multibase/multibase.test.ts +5 -5
  132. package/src/portable-media.test.ts +293 -0
  133. package/src/portable-media.ts +264 -0
  134. package/src/portable-workers.test.ts +81 -0
  135. package/src/portable.ts +180 -0
  136. package/src/preprocessor.ts +7 -0
  137. package/src/request.test.ts +10 -1
  138. package/src/request.ts +9 -1
  139. package/src/signed-representation.test.ts +338 -0
  140. package/src/url.test.ts +823 -3
  141. package/src/url.ts +700 -23
  142. package/tsdown.config.ts +2 -0
  143. package/dist/tests/url-CsOV_B_P.cjs +0 -482
  144. package/dist/tests/url-Du7RQQgP.mjs +0 -392
  145. package/dist/url-DD4F0ULf.cjs +0 -483
  146. package/dist/url-DGVbSVVi.js +0 -393
  147. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  148. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  149. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  150. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  151. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  152. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -0,0 +1,378 @@
1
+
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_contexts = require("../contexts-B09jb9Pi.cjs");
4
+ //#region src/internal/signed-representation.ts
5
+ const RETAINED_SIGNED_REPRESENTATION = Symbol.for("@fedify/vocab-runtime.signedRepresentation.v1");
6
+ const SIGNED_VALUE_SCOPE = Symbol.for("@fedify/vocab-runtime.signedValueScope.v1");
7
+ /**
8
+ * The IRI namespace of the placeholder node references that stand in for a
9
+ * retained signed representation until the owning `toJsonLd()` frame puts the
10
+ * representation back.
11
+ */
12
+ const MARKER_NAMESPACE = "urn:x-fedify-signed-value:";
13
+ /** Bounds applied when validating a retained signed representation. */
14
+ const MAX_VALIDATION_DEPTH = 64;
15
+ const MAX_VALIDATION_NODES = 1e5;
16
+ /**
17
+ * The depth to which placeholders are put back. It has to exceed
18
+ * {@link MAX_VALIDATION_DEPTH}, because a restored document is itself nested
19
+ * inside the document that embeds it. Running out of depth is an error
20
+ * rather than a stopping point: a placeholder left in an unvisited subtree
21
+ * would reach the wire in place of the secured child.
22
+ */
23
+ const MAX_RESTORATION_DEPTH = 256;
24
+ /**
25
+ * JSON-LD keywords that may appear at the top level of a context definition
26
+ * without endangering placeholder recovery. `@vocab` and `@base` are checked
27
+ * separately because they can rewrite the placeholder IRI.
28
+ */
29
+ const HARMLESS_CONTEXT_KEYWORDS = /* @__PURE__ */ new Set([
30
+ "@protected",
31
+ "@version",
32
+ "@propagate"
33
+ ]);
34
+ /**
35
+ * Keyword aliases that are safe to leave in place because the placeholder
36
+ * recovery walker understands them.
37
+ */
38
+ const ALLOWED_KEYWORD_ALIASES = /* @__PURE__ */ new Map([["id", "@id"], ["type", "@type"]]);
39
+ function isJsonMap(value) {
40
+ if (value == null || typeof value !== "object" || Array.isArray(value)) return false;
41
+ const prototype = Object.getPrototypeOf(value);
42
+ return prototype === Object.prototype || prototype === null;
43
+ }
44
+ /**
45
+ * Checks that a value is a plain JSON tree that is safe to embed verbatim in
46
+ * a serialized document: plain object and array prototypes only, string keys
47
+ * only, own enumerable data properties only, finite numbers, and no
48
+ * `undefined`.
49
+ *
50
+ * @internal Technically exported for generated vocabulary classes, but not
51
+ * part of the public API contract. This is not considered public API for
52
+ * Semantic Versioning decisions.
53
+ */
54
+ function isPlainJsonTree(value) {
55
+ let nodes = 0;
56
+ return check(value, 0);
57
+ function check(current, depth) {
58
+ if (depth > MAX_VALIDATION_DEPTH) return false;
59
+ if (++nodes > MAX_VALIDATION_NODES) return false;
60
+ if (current === null) return true;
61
+ switch (typeof current) {
62
+ case "string":
63
+ case "boolean": return true;
64
+ case "number": return Number.isFinite(current);
65
+ case "object": break;
66
+ default: return false;
67
+ }
68
+ if (Array.isArray(current)) {
69
+ if (Object.getPrototypeOf(current) !== Array.prototype) return false;
70
+ if (Object.getOwnPropertySymbols(current).length > 0) return false;
71
+ for (let i = 0; i < current.length; i++) {
72
+ const descriptor = Object.getOwnPropertyDescriptor(current, i);
73
+ if (descriptor == null || !("value" in descriptor)) return false;
74
+ if (!check(descriptor.value, depth + 1)) return false;
75
+ }
76
+ return true;
77
+ }
78
+ if (!isJsonMap(current)) return false;
79
+ if (Object.getOwnPropertySymbols(current).length > 0) return false;
80
+ for (const key of Object.getOwnPropertyNames(current)) {
81
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
82
+ if (descriptor == null || !("value" in descriptor)) return false;
83
+ if (!descriptor.enumerable) return false;
84
+ if (!check(descriptor.value, depth + 1)) return false;
85
+ }
86
+ return true;
87
+ }
88
+ }
89
+ function deepFreeze(value) {
90
+ if (value == null || typeof value !== "object") return value;
91
+ if (Object.isFrozen(value)) return value;
92
+ Object.freeze(value);
93
+ for (const child of Object.values(value)) deepFreeze(child);
94
+ return value;
95
+ }
96
+ /**
97
+ * Attaches the secured JSON document that a signer captured for `target`.
98
+ *
99
+ * The document is the exact JSON value that the object's own Object Integrity
100
+ * Proof covers. Nested serialization embeds it verbatim instead of
101
+ * reconstructing the object under the parent's JSON-LD context. The value is
102
+ * deep-frozen, so it is independent of anything done to `target` afterwards.
103
+ *
104
+ * `clone()` never carries the attachment, because a clone is a fresh instance.
105
+ *
106
+ * @internal Technically exported for `@fedify/fedify` and generated
107
+ * vocabulary classes, but not part of the public API contract. This is not
108
+ * considered public API for Semantic Versioning decisions.
109
+ */
110
+ function retainSignedRepresentation(target, securedDocument) {
111
+ if (!isJsonMap(securedDocument) || !isPlainJsonTree(securedDocument)) throw new TypeError(`The secured document must be a plain JSON map within the validation bounds (at most ${MAX_VALIDATION_DEPTH} levels deep and ${MAX_VALIDATION_NODES} nodes).`);
112
+ Object.defineProperty(target, RETAINED_SIGNED_REPRESENTATION, {
113
+ value: deepFreeze(securedDocument),
114
+ enumerable: false,
115
+ writable: false,
116
+ configurable: true
117
+ });
118
+ }
119
+ /**
120
+ * Returns the secured JSON document retained for `target`, if any.
121
+ *
122
+ * @internal Technically exported for generated vocabulary classes, but not
123
+ * part of the public API contract. This is not considered public API for
124
+ * Semantic Versioning decisions.
125
+ */
126
+ function getRetainedSignedRepresentation(target) {
127
+ if (target == null || typeof target !== "object") return void 0;
128
+ const retained = target[RETAINED_SIGNED_REPRESENTATION];
129
+ return isJsonMap(retained) ? retained : void 0;
130
+ }
131
+ function resolveContextEntry(entry) {
132
+ if (typeof entry !== "string") return entry;
133
+ if (!Object.hasOwn(require_contexts.preloadedContexts, entry)) return void 0;
134
+ const document = require_contexts.preloadedContexts[entry];
135
+ if (!isJsonMap(document)) return void 0;
136
+ return document["@context"];
137
+ }
138
+ /**
139
+ * Checks whether a JSON-LD context leaves placeholder node references
140
+ * recoverable after compaction.
141
+ *
142
+ * Placeholder recovery replaces a bare IRI string, `{"@id": …}`, or
143
+ * `{"id": …}`. A context that aliases `@id` under a different term, declares
144
+ * `@nest`, uses an `@id` or `@type` container, rebases or re-vocabularies the
145
+ * placeholder namespace, or defines a prefix that shortens the placeholder
146
+ * IRI could hide the placeholder from that walker or make the walker rewrite
147
+ * the wrong node. A context that cannot be resolved offline cannot be vetted
148
+ * at all. Any of those makes the context unsafe, and retention is then not
149
+ * used.
150
+ *
151
+ * @internal Technically exported for generated vocabulary classes, but not
152
+ * part of the public API contract. This is not considered public API for
153
+ * Semantic Versioning decisions.
154
+ */
155
+ function isMarkerSafeContext(context) {
156
+ return check(context, 0);
157
+ function check(current, depth) {
158
+ if (depth > 8) return false;
159
+ if (Array.isArray(current)) return current.every((entry) => check(entry, depth + 1));
160
+ if (typeof current === "string") {
161
+ const resolved = resolveContextEntry(current);
162
+ if (resolved === void 0) return false;
163
+ return check(resolved, depth + 1);
164
+ }
165
+ if (current == null) return true;
166
+ if (!isJsonMap(current)) return false;
167
+ for (const [term, definition] of Object.entries(current)) {
168
+ if (term === "@vocab" || term === "@base") {
169
+ if (typeof definition !== "string") return false;
170
+ if (sharesMarkerNamespace(definition)) return false;
171
+ continue;
172
+ }
173
+ if (term.startsWith("@")) {
174
+ if (!HARMLESS_CONTEXT_KEYWORDS.has(term)) return false;
175
+ continue;
176
+ }
177
+ if (MARKER_NAMESPACE.startsWith(`${term}:`)) return false;
178
+ if (!checkTerm(term, definition, depth)) return false;
179
+ }
180
+ return true;
181
+ }
182
+ function checkTerm(term, definition, depth) {
183
+ if (definition == null) return true;
184
+ if (typeof definition === "string") return checkTermTarget(term, definition);
185
+ if (!isJsonMap(definition)) return false;
186
+ const target = definition["@id"];
187
+ if (target != null) {
188
+ if (typeof target !== "string") return false;
189
+ if (!checkTermTarget(term, target)) return false;
190
+ }
191
+ if ("@nest" in definition) return false;
192
+ if ("@reverse" in definition) return false;
193
+ const container = definition["@container"];
194
+ const containers = Array.isArray(container) ? container : container == null ? [] : [container];
195
+ for (const entry of containers) if (entry === "@id" || entry === "@type") return false;
196
+ if ("@context" in definition) {
197
+ if (!check(definition["@context"], depth + 1)) return false;
198
+ }
199
+ return true;
200
+ }
201
+ function checkTermTarget(term, target) {
202
+ if (target.startsWith("@")) return ALLOWED_KEYWORD_ALIASES.get(term) === target;
203
+ if (!target.includes(":")) return false;
204
+ return !sharesMarkerNamespace(target);
205
+ }
206
+ }
207
+ /**
208
+ * Reports whether an IRI prefix could shorten, relativize or otherwise hide a
209
+ * placeholder IRI. Both directions matter: a prefix shorter than the
210
+ * namespace covers every marker, and a longer one covers the markers whose
211
+ * random suffix happens to start with it.
212
+ */
213
+ function sharesMarkerNamespace(prefix) {
214
+ return MARKER_NAMESPACE.startsWith(prefix) || prefix.startsWith(MARKER_NAMESPACE);
215
+ }
216
+ /**
217
+ * Joins the signed-value scope of the enclosing `toJsonLd()` frame, or starts
218
+ * one when this frame is the outermost.
219
+ *
220
+ * `options` must already be the internal copy that the generated encoder
221
+ * makes, never the object a caller passed in: the scope is attached to it as
222
+ * an enumerable symbol-keyed property so that every `{...options}` spread in
223
+ * the generated code propagates it to nested and inherited frames.
224
+ *
225
+ * @internal Technically exported for generated vocabulary classes, but not
226
+ * part of the public API contract. This is not considered public API for
227
+ * Semantic Versioning decisions.
228
+ */
229
+ function enterSignedValueScope(options) {
230
+ const existing = options[SIGNED_VALUE_SCOPE];
231
+ if (existing != null) return {
232
+ scope: existing,
233
+ owner: false
234
+ };
235
+ const scope = {
236
+ enabled: options.format !== "expand" && (options.context == null || isMarkerSafeContext(options.context)),
237
+ markers: /* @__PURE__ */ new Map(),
238
+ documents: /* @__PURE__ */ new Map()
239
+ };
240
+ Object.defineProperty(options, SIGNED_VALUE_SCOPE, {
241
+ value: scope,
242
+ enumerable: true,
243
+ writable: false,
244
+ configurable: true
245
+ });
246
+ return {
247
+ scope,
248
+ owner: true
249
+ };
250
+ }
251
+ /**
252
+ * Emits a placeholder node reference for a value that carries a retained
253
+ * signed representation, or `undefined` when it does not carry one.
254
+ *
255
+ * Both encoder paths use placeholders, never the retained document itself:
256
+ * an ancestor frame may still expand or compact the value, which would
257
+ * destroy an embedded document but leaves a node reference recoverable.
258
+ *
259
+ * @internal Technically exported for generated vocabulary classes, but not
260
+ * part of the public API contract. This is not considered public API for
261
+ * Semantic Versioning decisions.
262
+ */
263
+ function retainedSignedValueRef(value, scope) {
264
+ if (!scope.enabled) return void 0;
265
+ if (value == null || typeof value !== "object") return void 0;
266
+ const retained = getRetainedSignedRepresentation(value);
267
+ if (retained == null) return void 0;
268
+ let marker = scope.markers.get(value);
269
+ if (marker == null) {
270
+ if (!isPlainJsonTree(retained)) return void 0;
271
+ marker = `${MARKER_NAMESPACE}${crypto.randomUUID()}`;
272
+ scope.markers.set(value, marker);
273
+ scope.documents.set(marker, retained);
274
+ }
275
+ return { "@id": marker };
276
+ }
277
+ function markerOf(value, scope) {
278
+ return typeof value === "string" && scope.documents.has(value) ? value : void 0;
279
+ }
280
+ /**
281
+ * Puts every retained signed representation back in place of its placeholder
282
+ * node reference.
283
+ *
284
+ * Only the frame that owns the scope calls this, and only after compaction
285
+ * and context embedding are complete.
286
+ *
287
+ * @throws {TypeError} If a retained document was not put back anywhere, or if
288
+ * any trace of a placeholder survives. Both mean the serialized
289
+ * document does not contain the secured child it was told to embed,
290
+ * and emitting it would produce a signed child that silently fails
291
+ * verification.
292
+ *
293
+ * @internal Technically exported for generated vocabulary classes, but not
294
+ * part of the public API contract. This is not considered public API for
295
+ * Semantic Versioning decisions.
296
+ */
297
+ function resolveSignedValues(document, scope) {
298
+ if (scope.documents.size < 1) return document;
299
+ const restored = /* @__PURE__ */ new Set();
300
+ const resolved = restore(document, 0);
301
+ for (const marker of scope.documents.keys()) {
302
+ if (restored.has(marker)) continue;
303
+ throw new TypeError(`Failed to preserve a signed child representation: the placeholder ${marker} is not in the serialized document.`);
304
+ }
305
+ const trace = findMarkerTrace(resolved, scope);
306
+ if (trace != null) throw new TypeError(`Failed to preserve a signed child representation: a trace of the placeholder ${trace} survived serialization.`);
307
+ return resolved;
308
+ function restore(value, depth) {
309
+ if (depth > MAX_RESTORATION_DEPTH) throw new TypeError(`Failed to preserve a signed child representation: the serialized document is nested deeper than ${MAX_RESTORATION_DEPTH} levels, so a placeholder could be left unresolved.`);
310
+ const direct = markerOf(value, scope);
311
+ if (direct != null) return take(direct);
312
+ if (Array.isArray(value)) {
313
+ let changed = false;
314
+ const mapped = value.map((item) => {
315
+ const next = restore(item, depth + 1);
316
+ if (next !== item) changed = true;
317
+ return next;
318
+ });
319
+ return changed ? mapped : value;
320
+ }
321
+ if (!isJsonMap(value)) return value;
322
+ const keys = Object.keys(value);
323
+ if (keys.length === 1 && (keys[0] === "@id" || keys[0] === "id")) {
324
+ const marker = markerOf(value[keys[0]], scope);
325
+ if (marker != null) return take(marker);
326
+ }
327
+ let changed = false;
328
+ const mapped = Object.create(null);
329
+ for (const key of keys) {
330
+ const next = key === "@context" ? value[key] : restore(value[key], depth + 1);
331
+ if (next !== value[key]) changed = true;
332
+ mapped[key] = next;
333
+ }
334
+ if (!changed) return value;
335
+ return Object.setPrototypeOf(mapped, Object.prototype);
336
+ }
337
+ function take(marker) {
338
+ restored.add(marker);
339
+ return structuredClone(scope.documents.get(marker));
340
+ }
341
+ }
342
+ /**
343
+ * Finds any surviving trace of a placeholder IRI in a resolved document,
344
+ * matching the random suffix rather than the whole IRI so that a shortened,
345
+ * relativized or otherwise rewritten marker is caught too.
346
+ */
347
+ function findMarkerTrace(document, scope) {
348
+ const suffixes = /* @__PURE__ */ new Map();
349
+ for (const marker of scope.documents.keys()) suffixes.set(marker.slice(26), marker);
350
+ return search(document, 0);
351
+ function matched(value) {
352
+ for (const [suffix, marker] of suffixes) if (value.includes(suffix)) return marker;
353
+ }
354
+ function search(value, depth) {
355
+ if (depth > MAX_RESTORATION_DEPTH) throw new TypeError(`Failed to preserve a signed child representation: the serialized document is nested deeper than ${MAX_RESTORATION_DEPTH} levels, so a surviving placeholder could go unnoticed.`);
356
+ if (typeof value === "string") return matched(value);
357
+ if (Array.isArray(value)) {
358
+ for (const item of value) {
359
+ const found = search(item, depth + 1);
360
+ if (found != null) return found;
361
+ }
362
+ return;
363
+ }
364
+ if (value == null || typeof value !== "object") return void 0;
365
+ for (const [key, child] of Object.entries(value)) {
366
+ const found = matched(key) ?? search(child, depth + 1);
367
+ if (found != null) return found;
368
+ }
369
+ }
370
+ }
371
+ //#endregion
372
+ exports.enterSignedValueScope = enterSignedValueScope;
373
+ exports.getRetainedSignedRepresentation = getRetainedSignedRepresentation;
374
+ exports.isMarkerSafeContext = isMarkerSafeContext;
375
+ exports.isPlainJsonTree = isPlainJsonTree;
376
+ exports.resolveSignedValues = resolveSignedValues;
377
+ exports.retainSignedRepresentation = retainSignedRepresentation;
378
+ exports.retainedSignedValueRef = retainedSignedValueRef;
@@ -0,0 +1,144 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ //#region src/internal/signed-representation.d.ts
3
+ /**
4
+ * A JSON map. Retained signed representations are always plain JSON maps.
5
+ *
6
+ * @internal Technically exported for generated vocabulary classes, but not
7
+ * part of the public API contract. This is not considered public API for
8
+ * Semantic Versioning decisions.
9
+ */
10
+ type SignedRepresentationJsonMap = Record<string, unknown>;
11
+ /**
12
+ * Checks that a value is a plain JSON tree that is safe to embed verbatim in
13
+ * a serialized document: plain object and array prototypes only, string keys
14
+ * only, own enumerable data properties only, finite numbers, and no
15
+ * `undefined`.
16
+ *
17
+ * @internal Technically exported for generated vocabulary classes, but not
18
+ * part of the public API contract. This is not considered public API for
19
+ * Semantic Versioning decisions.
20
+ */
21
+ declare function isPlainJsonTree(value: unknown): boolean;
22
+ /**
23
+ * Attaches the secured JSON document that a signer captured for `target`.
24
+ *
25
+ * The document is the exact JSON value that the object's own Object Integrity
26
+ * Proof covers. Nested serialization embeds it verbatim instead of
27
+ * reconstructing the object under the parent's JSON-LD context. The value is
28
+ * deep-frozen, so it is independent of anything done to `target` afterwards.
29
+ *
30
+ * `clone()` never carries the attachment, because a clone is a fresh instance.
31
+ *
32
+ * @internal Technically exported for `@fedify/fedify` and generated
33
+ * vocabulary classes, but not part of the public API contract. This is not
34
+ * considered public API for Semantic Versioning decisions.
35
+ */
36
+ declare function retainSignedRepresentation(target: object, securedDocument: SignedRepresentationJsonMap): void;
37
+ /**
38
+ * Returns the secured JSON document retained for `target`, if any.
39
+ *
40
+ * @internal Technically exported for generated vocabulary classes, but not
41
+ * part of the public API contract. This is not considered public API for
42
+ * Semantic Versioning decisions.
43
+ */
44
+ declare function getRetainedSignedRepresentation(target: unknown): SignedRepresentationJsonMap | undefined;
45
+ /**
46
+ * Checks whether a JSON-LD context leaves placeholder node references
47
+ * recoverable after compaction.
48
+ *
49
+ * Placeholder recovery replaces a bare IRI string, `{"@id": …}`, or
50
+ * `{"id": …}`. A context that aliases `@id` under a different term, declares
51
+ * `@nest`, uses an `@id` or `@type` container, rebases or re-vocabularies the
52
+ * placeholder namespace, or defines a prefix that shortens the placeholder
53
+ * IRI could hide the placeholder from that walker or make the walker rewrite
54
+ * the wrong node. A context that cannot be resolved offline cannot be vetted
55
+ * at all. Any of those makes the context unsafe, and retention is then not
56
+ * used.
57
+ *
58
+ * @internal Technically exported for generated vocabulary classes, but not
59
+ * part of the public API contract. This is not considered public API for
60
+ * Semantic Versioning decisions.
61
+ */
62
+ declare function isMarkerSafeContext(context: unknown): boolean;
63
+ /**
64
+ * The per-`toJsonLd()`-call state that tracks retained children.
65
+ *
66
+ * @internal Technically exported for generated vocabulary classes, but not
67
+ * part of the public API contract. This is not considered public API for
68
+ * Semantic Versioning decisions.
69
+ */
70
+ interface SignedValueScope {
71
+ /** Whether retained representations may be embedded at all. */
72
+ readonly enabled: boolean;
73
+ /** Marker IRI for each retained instance seen in this call. */
74
+ readonly markers: Map<object, string>;
75
+ /** Retained document for each marker IRI. */
76
+ readonly documents: Map<string, SignedRepresentationJsonMap>;
77
+ }
78
+ /**
79
+ * The result of entering a signed-value scope.
80
+ *
81
+ * @internal Technically exported for generated vocabulary classes, but not
82
+ * part of the public API contract. This is not considered public API for
83
+ * Semantic Versioning decisions.
84
+ */
85
+ interface SignedValueScopeEntry {
86
+ readonly scope: SignedValueScope;
87
+ /**
88
+ * Whether this frame created the scope, and therefore has to put the
89
+ * retained documents back before returning.
90
+ */
91
+ readonly owner: boolean;
92
+ }
93
+ /**
94
+ * Joins the signed-value scope of the enclosing `toJsonLd()` frame, or starts
95
+ * one when this frame is the outermost.
96
+ *
97
+ * `options` must already be the internal copy that the generated encoder
98
+ * makes, never the object a caller passed in: the scope is attached to it as
99
+ * an enumerable symbol-keyed property so that every `{...options}` spread in
100
+ * the generated code propagates it to nested and inherited frames.
101
+ *
102
+ * @internal Technically exported for generated vocabulary classes, but not
103
+ * part of the public API contract. This is not considered public API for
104
+ * Semantic Versioning decisions.
105
+ */
106
+ declare function enterSignedValueScope(options: {
107
+ format?: "compact" | "expand";
108
+ context?: string | Record<string, string> | (string | Record<string, string>)[];
109
+ }): SignedValueScopeEntry;
110
+ /**
111
+ * Emits a placeholder node reference for a value that carries a retained
112
+ * signed representation, or `undefined` when it does not carry one.
113
+ *
114
+ * Both encoder paths use placeholders, never the retained document itself:
115
+ * an ancestor frame may still expand or compact the value, which would
116
+ * destroy an embedded document but leaves a node reference recoverable.
117
+ *
118
+ * @internal Technically exported for generated vocabulary classes, but not
119
+ * part of the public API contract. This is not considered public API for
120
+ * Semantic Versioning decisions.
121
+ */
122
+ declare function retainedSignedValueRef(value: unknown, scope: SignedValueScope): {
123
+ "@id": string;
124
+ } | undefined;
125
+ /**
126
+ * Puts every retained signed representation back in place of its placeholder
127
+ * node reference.
128
+ *
129
+ * Only the frame that owns the scope calls this, and only after compaction
130
+ * and context embedding are complete.
131
+ *
132
+ * @throws {TypeError} If a retained document was not put back anywhere, or if
133
+ * any trace of a placeholder survives. Both mean the serialized
134
+ * document does not contain the secured child it was told to embed,
135
+ * and emitting it would produce a signed child that silently fails
136
+ * verification.
137
+ *
138
+ * @internal Technically exported for generated vocabulary classes, but not
139
+ * part of the public API contract. This is not considered public API for
140
+ * Semantic Versioning decisions.
141
+ */
142
+ declare function resolveSignedValues(document: unknown, scope: SignedValueScope): unknown;
143
+ //#endregion
144
+ export { SignedRepresentationJsonMap, SignedValueScope, SignedValueScopeEntry, enterSignedValueScope, getRetainedSignedRepresentation, isMarkerSafeContext, isPlainJsonTree, resolveSignedValues, retainSignedRepresentation, retainedSignedValueRef };
@@ -0,0 +1,144 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ //#region src/internal/signed-representation.d.ts
3
+ /**
4
+ * A JSON map. Retained signed representations are always plain JSON maps.
5
+ *
6
+ * @internal Technically exported for generated vocabulary classes, but not
7
+ * part of the public API contract. This is not considered public API for
8
+ * Semantic Versioning decisions.
9
+ */
10
+ type SignedRepresentationJsonMap = Record<string, unknown>;
11
+ /**
12
+ * Checks that a value is a plain JSON tree that is safe to embed verbatim in
13
+ * a serialized document: plain object and array prototypes only, string keys
14
+ * only, own enumerable data properties only, finite numbers, and no
15
+ * `undefined`.
16
+ *
17
+ * @internal Technically exported for generated vocabulary classes, but not
18
+ * part of the public API contract. This is not considered public API for
19
+ * Semantic Versioning decisions.
20
+ */
21
+ declare function isPlainJsonTree(value: unknown): boolean;
22
+ /**
23
+ * Attaches the secured JSON document that a signer captured for `target`.
24
+ *
25
+ * The document is the exact JSON value that the object's own Object Integrity
26
+ * Proof covers. Nested serialization embeds it verbatim instead of
27
+ * reconstructing the object under the parent's JSON-LD context. The value is
28
+ * deep-frozen, so it is independent of anything done to `target` afterwards.
29
+ *
30
+ * `clone()` never carries the attachment, because a clone is a fresh instance.
31
+ *
32
+ * @internal Technically exported for `@fedify/fedify` and generated
33
+ * vocabulary classes, but not part of the public API contract. This is not
34
+ * considered public API for Semantic Versioning decisions.
35
+ */
36
+ declare function retainSignedRepresentation(target: object, securedDocument: SignedRepresentationJsonMap): void;
37
+ /**
38
+ * Returns the secured JSON document retained for `target`, if any.
39
+ *
40
+ * @internal Technically exported for generated vocabulary classes, but not
41
+ * part of the public API contract. This is not considered public API for
42
+ * Semantic Versioning decisions.
43
+ */
44
+ declare function getRetainedSignedRepresentation(target: unknown): SignedRepresentationJsonMap | undefined;
45
+ /**
46
+ * Checks whether a JSON-LD context leaves placeholder node references
47
+ * recoverable after compaction.
48
+ *
49
+ * Placeholder recovery replaces a bare IRI string, `{"@id": …}`, or
50
+ * `{"id": …}`. A context that aliases `@id` under a different term, declares
51
+ * `@nest`, uses an `@id` or `@type` container, rebases or re-vocabularies the
52
+ * placeholder namespace, or defines a prefix that shortens the placeholder
53
+ * IRI could hide the placeholder from that walker or make the walker rewrite
54
+ * the wrong node. A context that cannot be resolved offline cannot be vetted
55
+ * at all. Any of those makes the context unsafe, and retention is then not
56
+ * used.
57
+ *
58
+ * @internal Technically exported for generated vocabulary classes, but not
59
+ * part of the public API contract. This is not considered public API for
60
+ * Semantic Versioning decisions.
61
+ */
62
+ declare function isMarkerSafeContext(context: unknown): boolean;
63
+ /**
64
+ * The per-`toJsonLd()`-call state that tracks retained children.
65
+ *
66
+ * @internal Technically exported for generated vocabulary classes, but not
67
+ * part of the public API contract. This is not considered public API for
68
+ * Semantic Versioning decisions.
69
+ */
70
+ interface SignedValueScope {
71
+ /** Whether retained representations may be embedded at all. */
72
+ readonly enabled: boolean;
73
+ /** Marker IRI for each retained instance seen in this call. */
74
+ readonly markers: Map<object, string>;
75
+ /** Retained document for each marker IRI. */
76
+ readonly documents: Map<string, SignedRepresentationJsonMap>;
77
+ }
78
+ /**
79
+ * The result of entering a signed-value scope.
80
+ *
81
+ * @internal Technically exported for generated vocabulary classes, but not
82
+ * part of the public API contract. This is not considered public API for
83
+ * Semantic Versioning decisions.
84
+ */
85
+ interface SignedValueScopeEntry {
86
+ readonly scope: SignedValueScope;
87
+ /**
88
+ * Whether this frame created the scope, and therefore has to put the
89
+ * retained documents back before returning.
90
+ */
91
+ readonly owner: boolean;
92
+ }
93
+ /**
94
+ * Joins the signed-value scope of the enclosing `toJsonLd()` frame, or starts
95
+ * one when this frame is the outermost.
96
+ *
97
+ * `options` must already be the internal copy that the generated encoder
98
+ * makes, never the object a caller passed in: the scope is attached to it as
99
+ * an enumerable symbol-keyed property so that every `{...options}` spread in
100
+ * the generated code propagates it to nested and inherited frames.
101
+ *
102
+ * @internal Technically exported for generated vocabulary classes, but not
103
+ * part of the public API contract. This is not considered public API for
104
+ * Semantic Versioning decisions.
105
+ */
106
+ declare function enterSignedValueScope(options: {
107
+ format?: "compact" | "expand";
108
+ context?: string | Record<string, string> | (string | Record<string, string>)[];
109
+ }): SignedValueScopeEntry;
110
+ /**
111
+ * Emits a placeholder node reference for a value that carries a retained
112
+ * signed representation, or `undefined` when it does not carry one.
113
+ *
114
+ * Both encoder paths use placeholders, never the retained document itself:
115
+ * an ancestor frame may still expand or compact the value, which would
116
+ * destroy an embedded document but leaves a node reference recoverable.
117
+ *
118
+ * @internal Technically exported for generated vocabulary classes, but not
119
+ * part of the public API contract. This is not considered public API for
120
+ * Semantic Versioning decisions.
121
+ */
122
+ declare function retainedSignedValueRef(value: unknown, scope: SignedValueScope): {
123
+ "@id": string;
124
+ } | undefined;
125
+ /**
126
+ * Puts every retained signed representation back in place of its placeholder
127
+ * node reference.
128
+ *
129
+ * Only the frame that owns the scope calls this, and only after compaction
130
+ * and context embedding are complete.
131
+ *
132
+ * @throws {TypeError} If a retained document was not put back anywhere, or if
133
+ * any trace of a placeholder survives. Both mean the serialized
134
+ * document does not contain the secured child it was told to embed,
135
+ * and emitting it would produce a signed child that silently fails
136
+ * verification.
137
+ *
138
+ * @internal Technically exported for generated vocabulary classes, but not
139
+ * part of the public API contract. This is not considered public API for
140
+ * Semantic Versioning decisions.
141
+ */
142
+ declare function resolveSignedValues(document: unknown, scope: SignedValueScope): unknown;
143
+ //#endregion
144
+ export { SignedRepresentationJsonMap, SignedValueScope, SignedValueScopeEntry, enterSignedValueScope, getRetainedSignedRepresentation, isMarkerSafeContext, isPlainJsonTree, resolveSignedValues, retainSignedRepresentation, retainedSignedValueRef };