@fedify/backfill 2.3.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +152 -0
- package/dist/backfill.test.cjs +1563 -0
- package/dist/backfill.test.d.cts +1 -0
- package/dist/backfill.test.d.ts +1 -0
- package/dist/backfill.test.js +1541 -0
- package/dist/mod.cjs +366 -0
- package/dist/mod.d.cts +157 -0
- package/dist/mod.d.ts +157 -0
- package/dist/mod.js +364 -0
- package/package.json +67 -0
|
@@ -0,0 +1,1563 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let node_assert_strict = require("node:assert/strict");
|
|
24
|
+
let node_test = require("node:test");
|
|
25
|
+
node_test = __toESM(node_test, 1);
|
|
26
|
+
let _fedify_vocab = require("@fedify/vocab");
|
|
27
|
+
//#region src/backfill.ts
|
|
28
|
+
const defaultStrategies = ["context-auto"];
|
|
29
|
+
const DEFAULT_MAX_DEPTH = 10;
|
|
30
|
+
/**
|
|
31
|
+
* Thrown when backfill traversal exceeds the configured request budget.
|
|
32
|
+
*
|
|
33
|
+
* @since 2.3.0
|
|
34
|
+
*/
|
|
35
|
+
var MaxRequestsExceeded = class extends Error {};
|
|
36
|
+
/**
|
|
37
|
+
* Backfills post-like objects related to a seed object.
|
|
38
|
+
*
|
|
39
|
+
* The seed object is not yielded by default, but its ID is treated as already
|
|
40
|
+
* seen so it will not be yielded again if the collection contains it.
|
|
41
|
+
*
|
|
42
|
+
* @since 2.3.0
|
|
43
|
+
*/
|
|
44
|
+
async function* backfill(context, note, options = {}) {
|
|
45
|
+
if (options.maxItems != null && options.maxItems <= 0) return;
|
|
46
|
+
const strategies = normalizeStrategies(options.strategies);
|
|
47
|
+
if (strategies.length < 1) return;
|
|
48
|
+
const budget = {
|
|
49
|
+
signal: options.signal,
|
|
50
|
+
requestCount: 0,
|
|
51
|
+
documents: /* @__PURE__ */ new Map()
|
|
52
|
+
};
|
|
53
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
54
|
+
if (note.id != null) seenIds.add(note.id.href);
|
|
55
|
+
let yielded = 0;
|
|
56
|
+
try {
|
|
57
|
+
for (let i = 0; i < strategies.length; i++) {
|
|
58
|
+
const strategy = strategies[i];
|
|
59
|
+
let items;
|
|
60
|
+
if (isContextStrategy(strategy)) {
|
|
61
|
+
const contextStrategies = [strategy];
|
|
62
|
+
while (true) {
|
|
63
|
+
const nextStrategy = strategies[i + 1];
|
|
64
|
+
if (nextStrategy == null || !isContextStrategy(nextStrategy)) break;
|
|
65
|
+
contextStrategies.push(nextStrategy);
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
items = getContextStrategyItems(context, note, contextStrategies, options, budget, seenIds);
|
|
69
|
+
} else items = getStrategyItems(context, note, strategy, options, budget, seenIds);
|
|
70
|
+
for await (const item of items) {
|
|
71
|
+
const id = item.object.id ?? void 0;
|
|
72
|
+
if (id != null) {
|
|
73
|
+
if (seenIds.has(id.href)) continue;
|
|
74
|
+
seenIds.add(id.href);
|
|
75
|
+
}
|
|
76
|
+
options.signal?.throwIfAborted();
|
|
77
|
+
yield {
|
|
78
|
+
object: item.object,
|
|
79
|
+
id,
|
|
80
|
+
strategy: item.strategy,
|
|
81
|
+
origin: item.origin,
|
|
82
|
+
depth: item.depth
|
|
83
|
+
};
|
|
84
|
+
yielded++;
|
|
85
|
+
if (options.maxItems != null && yielded >= options.maxItems) return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof MaxRequestsExceeded) return;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function normalizeStrategies(strategies = defaultStrategies) {
|
|
94
|
+
const normalized = [];
|
|
95
|
+
for (const strategy of strategies) if (strategy === "context-auto") {
|
|
96
|
+
for (let i = normalized.length - 1; i >= 0 && isContextStrategy(normalized[i]); i--) normalized.splice(i, 1);
|
|
97
|
+
if (!normalized.includes(strategy)) normalized.push(strategy);
|
|
98
|
+
} else if (isContextStrategy(strategy)) {
|
|
99
|
+
if (!currentContextGroupHasAuto(normalized) && !normalized.includes(strategy)) normalized.push(strategy);
|
|
100
|
+
} else if (!normalized.includes(strategy)) normalized.push(strategy);
|
|
101
|
+
return normalized;
|
|
102
|
+
}
|
|
103
|
+
function isContextStrategy(strategy) {
|
|
104
|
+
return strategy === "context-objects" || strategy === "context-activities" || strategy === "context-auto";
|
|
105
|
+
}
|
|
106
|
+
function currentContextGroupHasAuto(strategies) {
|
|
107
|
+
for (let i = strategies.length - 1; i >= 0; i--) {
|
|
108
|
+
const strategy = strategies[i];
|
|
109
|
+
if (!isContextStrategy(strategy)) return false;
|
|
110
|
+
if (strategy === "context-auto") return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
async function* getContextStrategyItems(context, note, strategies, options, budget, seenIds) {
|
|
115
|
+
const contextId = note.contextIds[0];
|
|
116
|
+
if (contextId == null) return;
|
|
117
|
+
const collection = await loadObject(context, contextId, options, budget);
|
|
118
|
+
if (!isCollection(collection)) return;
|
|
119
|
+
for await (const object of getCollectionItems(context, collection, options, budget, seenIds)) for (const strategy of strategies) for await (const item of getContextBackfillItems(context, object, strategy, options, budget)) yield {
|
|
120
|
+
object: item.object,
|
|
121
|
+
strategy: item.strategy,
|
|
122
|
+
origin: "collection",
|
|
123
|
+
depth: 0
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function* getStrategyItems(context, note, strategy, options, budget, seenIds) {
|
|
127
|
+
if (isContextStrategy(strategy)) yield* getContextStrategyItems(context, note, [strategy], options, budget, seenIds);
|
|
128
|
+
else if (strategy === "reply-tree") yield* getReplyTreeItems(context, note, options, budget);
|
|
129
|
+
}
|
|
130
|
+
async function* getReplyTreeItems(context, note, options, budget) {
|
|
131
|
+
const visitedObjectIds = /* @__PURE__ */ new Set();
|
|
132
|
+
const visitedObjects = /* @__PURE__ */ new WeakSet();
|
|
133
|
+
const visitedCollectionIds = /* @__PURE__ */ new Set();
|
|
134
|
+
const visitedCollections = /* @__PURE__ */ new WeakSet();
|
|
135
|
+
if (note.id != null) visitedObjectIds.add(note.id.href);
|
|
136
|
+
visitedObjects.add(note);
|
|
137
|
+
const ancestors = [];
|
|
138
|
+
for await (const item of getReplyAncestors(context, note, options, budget, {
|
|
139
|
+
depth: 1,
|
|
140
|
+
visitedObjectIds,
|
|
141
|
+
visitedObjects,
|
|
142
|
+
visitedCollectionIds,
|
|
143
|
+
visitedCollections
|
|
144
|
+
})) {
|
|
145
|
+
ancestors.push({
|
|
146
|
+
object: item.object,
|
|
147
|
+
depth: item.depth
|
|
148
|
+
});
|
|
149
|
+
yield item;
|
|
150
|
+
}
|
|
151
|
+
for (const ancestor of ancestors.toReversed()) yield* getReplyDescendants(context, ancestor.object, options, budget, {
|
|
152
|
+
depth: ancestor.depth + 1,
|
|
153
|
+
visitedObjectIds,
|
|
154
|
+
visitedObjects,
|
|
155
|
+
visitedCollectionIds,
|
|
156
|
+
visitedCollections
|
|
157
|
+
});
|
|
158
|
+
yield* getReplyDescendants(context, note, options, budget, {
|
|
159
|
+
depth: 1,
|
|
160
|
+
visitedObjectIds,
|
|
161
|
+
visitedObjects,
|
|
162
|
+
visitedCollectionIds,
|
|
163
|
+
visitedCollections
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function* getReplyAncestors(context, object, options, budget, traversal) {
|
|
167
|
+
if (traversal.depth > (options.maxDepth ?? DEFAULT_MAX_DEPTH)) return;
|
|
168
|
+
for await (const target of getReplyTargets(context, object, options, budget)) {
|
|
169
|
+
if (!isContextPostObject(target)) continue;
|
|
170
|
+
if (!visitReplyTreeObject(target, traversal)) continue;
|
|
171
|
+
yield {
|
|
172
|
+
object: target,
|
|
173
|
+
strategy: "reply-tree",
|
|
174
|
+
origin: "in-reply-to",
|
|
175
|
+
depth: traversal.depth
|
|
176
|
+
};
|
|
177
|
+
yield* getReplyAncestors(context, target, options, budget, {
|
|
178
|
+
depth: traversal.depth + 1,
|
|
179
|
+
visitedObjectIds: traversal.visitedObjectIds,
|
|
180
|
+
visitedObjects: traversal.visitedObjects,
|
|
181
|
+
visitedCollectionIds: traversal.visitedCollectionIds,
|
|
182
|
+
visitedCollections: traversal.visitedCollections
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function* getReplyDescendants(context, object, options, budget, traversal) {
|
|
187
|
+
if (traversal.depth > (options.maxDepth ?? DEFAULT_MAX_DEPTH)) return;
|
|
188
|
+
const repliesId = object.repliesId;
|
|
189
|
+
if (repliesId != null && traversal.visitedCollectionIds.has(repliesId.href)) return;
|
|
190
|
+
const replies = await getRepliesCollection(context, object, options, budget);
|
|
191
|
+
if (replies == null) return;
|
|
192
|
+
const unvisited = visitReplyTreeCollection(replies, traversal);
|
|
193
|
+
if (repliesId != null) traversal.visitedCollectionIds.add(repliesId.href);
|
|
194
|
+
if (!unvisited) return;
|
|
195
|
+
for await (const reply of getCollectionItems(context, replies, options, budget, traversal.visitedObjectIds)) {
|
|
196
|
+
if (!isContextPostObject(reply)) continue;
|
|
197
|
+
if (!visitReplyTreeObject(reply, traversal)) continue;
|
|
198
|
+
yield {
|
|
199
|
+
object: reply,
|
|
200
|
+
strategy: "reply-tree",
|
|
201
|
+
origin: "replies",
|
|
202
|
+
depth: traversal.depth
|
|
203
|
+
};
|
|
204
|
+
yield* getReplyDescendants(context, reply, options, budget, {
|
|
205
|
+
depth: traversal.depth + 1,
|
|
206
|
+
visitedObjectIds: traversal.visitedObjectIds,
|
|
207
|
+
visitedObjects: traversal.visitedObjects,
|
|
208
|
+
visitedCollectionIds: traversal.visitedCollectionIds,
|
|
209
|
+
visitedCollections: traversal.visitedCollections
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function* getReplyTargets(context, object, options, budget) {
|
|
214
|
+
try {
|
|
215
|
+
yield* object.getReplyTargets({
|
|
216
|
+
documentLoader: async (url) => {
|
|
217
|
+
return await loadCollectionItemDocument(context, url, options, budget);
|
|
218
|
+
},
|
|
219
|
+
crossOrigin: "trust"
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (error instanceof MaxRequestsExceeded) throw error;
|
|
223
|
+
budget.signal?.throwIfAborted();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function getRepliesCollection(context, object, options, budget) {
|
|
227
|
+
try {
|
|
228
|
+
return await object.getReplies({
|
|
229
|
+
documentLoader: async (url) => {
|
|
230
|
+
return await loadCollectionItemDocument(context, url, options, budget);
|
|
231
|
+
},
|
|
232
|
+
crossOrigin: "trust"
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (error instanceof MaxRequestsExceeded) throw error;
|
|
236
|
+
budget.signal?.throwIfAborted();
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function visitReplyTreeObject(object, traversal) {
|
|
241
|
+
if (object.id != null) {
|
|
242
|
+
if (traversal.visitedObjectIds.has(object.id.href)) return false;
|
|
243
|
+
traversal.visitedObjectIds.add(object.id.href);
|
|
244
|
+
} else if (traversal.visitedObjects.has(object)) return false;
|
|
245
|
+
traversal.visitedObjects.add(object);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
function visitReplyTreeCollection(collection, traversal) {
|
|
249
|
+
if (collection.id != null) return visitReplyTreeCollectionId(collection.id, traversal);
|
|
250
|
+
else if (traversal.visitedCollections.has(collection)) return false;
|
|
251
|
+
traversal.visitedCollections.add(collection);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
function visitReplyTreeCollectionId(id, traversal) {
|
|
255
|
+
if (traversal.visitedCollectionIds.has(id.href)) return false;
|
|
256
|
+
traversal.visitedCollectionIds.add(id.href);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
async function* getContextBackfillItems(context, object, strategy, options, budget) {
|
|
260
|
+
if (strategy === "context-objects" && isContextPostObject(object)) yield {
|
|
261
|
+
object,
|
|
262
|
+
strategy
|
|
263
|
+
};
|
|
264
|
+
else if (strategy === "context-activities") {
|
|
265
|
+
const activityObject = await getCreateActivityObject(context, object, options, budget);
|
|
266
|
+
if (activityObject != null && isContextPostObject(activityObject)) yield {
|
|
267
|
+
object: activityObject,
|
|
268
|
+
strategy
|
|
269
|
+
};
|
|
270
|
+
} else if (strategy === "context-auto") {
|
|
271
|
+
if (object instanceof _fedify_vocab.Activity) {
|
|
272
|
+
const activityObject = await getCreateActivityObject(context, object, options, budget);
|
|
273
|
+
if (activityObject != null && isContextPostObject(activityObject)) yield {
|
|
274
|
+
object: activityObject,
|
|
275
|
+
strategy
|
|
276
|
+
};
|
|
277
|
+
} else if (isContextPostObject(object)) yield {
|
|
278
|
+
object,
|
|
279
|
+
strategy
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function* getCollectionItems(context, collection, options, budget, skipIds) {
|
|
284
|
+
yield* collection.getItems({
|
|
285
|
+
documentLoader: async (url) => {
|
|
286
|
+
return await loadCollectionItemDocument(context, url, options, budget, skipIds);
|
|
287
|
+
},
|
|
288
|
+
crossOrigin: "trust"
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async function getCreateActivityObject(context, object, options, budget) {
|
|
292
|
+
if (!(object instanceof _fedify_vocab.Create)) return null;
|
|
293
|
+
try {
|
|
294
|
+
return await object.getObject({
|
|
295
|
+
documentLoader: async (url) => {
|
|
296
|
+
return await loadCollectionItemDocument(context, url, options, budget);
|
|
297
|
+
},
|
|
298
|
+
crossOrigin: "trust"
|
|
299
|
+
});
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (error instanceof MaxRequestsExceeded) throw error;
|
|
302
|
+
budget.signal?.throwIfAborted();
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function loadCollectionItemDocument(context, url, options, budget, skipIds) {
|
|
307
|
+
let object;
|
|
308
|
+
try {
|
|
309
|
+
const iri = new URL(url);
|
|
310
|
+
if (skipIds?.has(iri.href)) return skippedCollectionItemDocument(url);
|
|
311
|
+
object = await loadObject(context, iri, options, budget, true);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (error instanceof MaxRequestsExceeded) throw error;
|
|
314
|
+
budget.signal?.throwIfAborted();
|
|
315
|
+
return skippedCollectionItemDocument(url);
|
|
316
|
+
}
|
|
317
|
+
if (object == null) return skippedCollectionItemDocument(url);
|
|
318
|
+
return {
|
|
319
|
+
contextUrl: null,
|
|
320
|
+
documentUrl: url,
|
|
321
|
+
document: await object.toJsonLd()
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function skippedCollectionItemDocument(url) {
|
|
325
|
+
return {
|
|
326
|
+
contextUrl: null,
|
|
327
|
+
documentUrl: url,
|
|
328
|
+
document: {
|
|
329
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
330
|
+
type: "Activity"
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function loadObject(context, iri, options, budget, throwOnBudgetExceeded = false) {
|
|
335
|
+
budget.signal?.throwIfAborted();
|
|
336
|
+
const cacheKey = iri.href;
|
|
337
|
+
const cached = budget.documents.get(cacheKey);
|
|
338
|
+
if (cached != null) return await cached;
|
|
339
|
+
if (options.maxRequests != null && budget.requestCount >= options.maxRequests) {
|
|
340
|
+
if (throwOnBudgetExceeded) throw new MaxRequestsExceeded();
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
await waitForInterval(options, budget);
|
|
344
|
+
budget.signal?.throwIfAborted();
|
|
345
|
+
budget.requestCount++;
|
|
346
|
+
const document = context.documentLoader(iri, { signal: budget.signal });
|
|
347
|
+
budget.documents.set(cacheKey, document);
|
|
348
|
+
try {
|
|
349
|
+
return await document;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (budget.documents.get(cacheKey) === document) budget.documents.delete(cacheKey);
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function waitForInterval(options, budget) {
|
|
356
|
+
if (options.interval == null) return;
|
|
357
|
+
const milliseconds = durationToMilliseconds(typeof options.interval === "function" ? options.interval(budget.requestCount) : options.interval);
|
|
358
|
+
if (milliseconds <= 0) return;
|
|
359
|
+
await new Promise((resolve, reject) => {
|
|
360
|
+
if (budget.signal?.aborted) {
|
|
361
|
+
reject(budget.signal.reason);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const timeout = setTimeout(() => {
|
|
365
|
+
budget.signal?.removeEventListener("abort", onAbort);
|
|
366
|
+
resolve();
|
|
367
|
+
}, milliseconds);
|
|
368
|
+
const onAbort = () => {
|
|
369
|
+
clearTimeout(timeout);
|
|
370
|
+
reject(budget.signal?.reason);
|
|
371
|
+
};
|
|
372
|
+
budget.signal?.addEventListener("abort", onAbort, { once: true });
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function durationToMilliseconds(duration) {
|
|
376
|
+
if (typeof duration === "string") {
|
|
377
|
+
if (typeof Temporal === "undefined") throw new TypeError("Temporal is not globally available; pass interval as a Temporal.DurationLike object instead of a string, or provide a Temporal polyfill.");
|
|
378
|
+
return Temporal.Duration.from(duration).total({ unit: "milliseconds" });
|
|
379
|
+
}
|
|
380
|
+
return (duration.milliseconds ?? 0) + (duration.seconds ?? 0) * 1e3 + (duration.minutes ?? 0) * 60 * 1e3 + (duration.hours ?? 0) * 60 * 60 * 1e3 + (duration.days ?? 0) * 24 * 60 * 60 * 1e3;
|
|
381
|
+
}
|
|
382
|
+
function isCollection(object) {
|
|
383
|
+
return object instanceof _fedify_vocab.Collection || object instanceof _fedify_vocab.OrderedCollection || object instanceof _fedify_vocab.CollectionPage || object instanceof _fedify_vocab.OrderedCollectionPage;
|
|
384
|
+
}
|
|
385
|
+
function isContextPostObject(object) {
|
|
386
|
+
return object instanceof _fedify_vocab.Object && !(object instanceof _fedify_vocab.Activity) && !isCollection(object);
|
|
387
|
+
}
|
|
388
|
+
//#endregion
|
|
389
|
+
//#region src/backfill.test.ts
|
|
390
|
+
async function collect(context, note, options = {}) {
|
|
391
|
+
return await Array.fromAsync(backfill(context, note, options));
|
|
392
|
+
}
|
|
393
|
+
(0, node_test.describe)("backfill", () => {
|
|
394
|
+
(0, node_test.default)("package exports backfill", () => {
|
|
395
|
+
(0, node_assert_strict.strictEqual)(typeof backfill, "function");
|
|
396
|
+
(0, node_assert_strict.strictEqual)(typeof MaxRequestsExceeded, "function");
|
|
397
|
+
});
|
|
398
|
+
(0, node_test.default)("context missing yields nothing", async () => {
|
|
399
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => {
|
|
400
|
+
throw new Error("documentLoader should not be called");
|
|
401
|
+
} }, new _fedify_vocab.Note({ id: new URL("https://example.com/notes/1") })), []);
|
|
402
|
+
});
|
|
403
|
+
(0, node_test.default)("context resolves to non-collection yields nothing", async () => {
|
|
404
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
405
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") })) }, new _fedify_vocab.Note({
|
|
406
|
+
id: new URL("https://example.com/notes/1"),
|
|
407
|
+
contexts: [contextId]
|
|
408
|
+
})), []);
|
|
409
|
+
});
|
|
410
|
+
(0, node_test.default)("context collection with embedded objects yields items", async () => {
|
|
411
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
412
|
+
const item = new _fedify_vocab.Note({
|
|
413
|
+
id: new URL("https://example.com/notes/2"),
|
|
414
|
+
content: "hello"
|
|
415
|
+
});
|
|
416
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
417
|
+
id: contextId,
|
|
418
|
+
items: [item]
|
|
419
|
+
})) }, new _fedify_vocab.Note({
|
|
420
|
+
id: new URL("https://example.com/notes/1"),
|
|
421
|
+
contexts: [contextId]
|
|
422
|
+
}));
|
|
423
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
424
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
425
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].id, item.id);
|
|
426
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-auto");
|
|
427
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "collection");
|
|
428
|
+
});
|
|
429
|
+
(0, node_test.default)("context object strategy yields embedded objects", async () => {
|
|
430
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
431
|
+
const item = new _fedify_vocab.Note({
|
|
432
|
+
id: new URL("https://example.com/notes/2"),
|
|
433
|
+
content: "hello"
|
|
434
|
+
});
|
|
435
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
436
|
+
id: contextId,
|
|
437
|
+
items: [item]
|
|
438
|
+
})) }, new _fedify_vocab.Note({
|
|
439
|
+
id: new URL("https://example.com/notes/1"),
|
|
440
|
+
contexts: [contextId]
|
|
441
|
+
}), { strategies: ["context-objects"] });
|
|
442
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
443
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
444
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-objects");
|
|
445
|
+
});
|
|
446
|
+
(0, node_test.default)("embedded object without id is yielded without id", async () => {
|
|
447
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
448
|
+
const item = new _fedify_vocab.Note({ content: "anonymous" });
|
|
449
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
450
|
+
id: contextId,
|
|
451
|
+
items: [item]
|
|
452
|
+
})) }, new _fedify_vocab.Note({
|
|
453
|
+
id: new URL("https://example.com/notes/1"),
|
|
454
|
+
contexts: [contextId]
|
|
455
|
+
}));
|
|
456
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
457
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
458
|
+
(0, node_assert_strict.strictEqual)(items[0].id, void 0);
|
|
459
|
+
});
|
|
460
|
+
(0, node_test.default)("context object strategy skips activity objects", async () => {
|
|
461
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
462
|
+
const activity = new _fedify_vocab.Create({
|
|
463
|
+
id: new URL("https://example.com/activities/1"),
|
|
464
|
+
object: new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") })
|
|
465
|
+
});
|
|
466
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
467
|
+
id: contextId,
|
|
468
|
+
items: [activity]
|
|
469
|
+
})) }, new _fedify_vocab.Note({
|
|
470
|
+
id: new URL("https://example.com/notes/1"),
|
|
471
|
+
contexts: [contextId]
|
|
472
|
+
}), { strategies: ["context-objects"] }), []);
|
|
473
|
+
});
|
|
474
|
+
(0, node_test.default)("context auto strategy yields object from embedded Create", async () => {
|
|
475
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
476
|
+
const item = new _fedify_vocab.Note({
|
|
477
|
+
id: new URL("https://example.com/notes/2"),
|
|
478
|
+
content: "hello"
|
|
479
|
+
});
|
|
480
|
+
const activity = new _fedify_vocab.Create({
|
|
481
|
+
id: new URL("https://example.com/activities/1"),
|
|
482
|
+
object: item
|
|
483
|
+
});
|
|
484
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
485
|
+
id: contextId,
|
|
486
|
+
items: [activity]
|
|
487
|
+
})) }, new _fedify_vocab.Note({
|
|
488
|
+
id: new URL("https://example.com/notes/1"),
|
|
489
|
+
contexts: [contextId]
|
|
490
|
+
}));
|
|
491
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
492
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
493
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-auto");
|
|
494
|
+
});
|
|
495
|
+
(0, node_test.default)("empty strategies yield nothing without dereferencing context", async () => {
|
|
496
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => {
|
|
497
|
+
throw new Error("documentLoader should not be called");
|
|
498
|
+
} }, new _fedify_vocab.Note({
|
|
499
|
+
id: new URL("https://example.com/notes/1"),
|
|
500
|
+
contexts: [new URL("https://example.com/contexts/1")]
|
|
501
|
+
}), { strategies: [] }), []);
|
|
502
|
+
});
|
|
503
|
+
(0, node_test.default)("reply tree strategy does not require context collection", async () => {
|
|
504
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => {
|
|
505
|
+
throw new Error("documentLoader should not be called");
|
|
506
|
+
} }, new _fedify_vocab.Note({
|
|
507
|
+
id: new URL("https://example.com/notes/1"),
|
|
508
|
+
contexts: [new URL("https://example.com/contexts/1")]
|
|
509
|
+
}), { strategies: ["reply-tree"] }), []);
|
|
510
|
+
});
|
|
511
|
+
(0, node_test.default)("reply tree yields embedded ancestor", async () => {
|
|
512
|
+
const parent = new _fedify_vocab.Note({
|
|
513
|
+
id: new URL("https://example.com/notes/1"),
|
|
514
|
+
content: "parent"
|
|
515
|
+
});
|
|
516
|
+
const items = await collect({ documentLoader: () => {
|
|
517
|
+
throw new Error("documentLoader should not be called");
|
|
518
|
+
} }, new _fedify_vocab.Note({
|
|
519
|
+
id: new URL("https://example.com/notes/2"),
|
|
520
|
+
replyTarget: parent
|
|
521
|
+
}), { strategies: ["reply-tree"] });
|
|
522
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
523
|
+
(0, node_assert_strict.strictEqual)(items[0].object, parent);
|
|
524
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].id, parent.id);
|
|
525
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "reply-tree");
|
|
526
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "in-reply-to");
|
|
527
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
528
|
+
});
|
|
529
|
+
(0, node_test.default)("reply tree dereferences ancestor URL", async () => {
|
|
530
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
531
|
+
const parent = new _fedify_vocab.Note({
|
|
532
|
+
id: parentId,
|
|
533
|
+
content: "parent"
|
|
534
|
+
});
|
|
535
|
+
const items = await collect({ documentLoader: (iri) => Promise.resolve(iri.href === parentId.href ? parent : null) }, new _fedify_vocab.Note({
|
|
536
|
+
id: new URL("https://example.com/notes/2"),
|
|
537
|
+
replyTarget: parentId
|
|
538
|
+
}), { strategies: ["reply-tree"] });
|
|
539
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
540
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].object.id, parent.id);
|
|
541
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "in-reply-to");
|
|
542
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
543
|
+
});
|
|
544
|
+
(0, node_test.default)("reply tree maxDepth limits ancestors", async () => {
|
|
545
|
+
const rootId = new URL("https://example.com/notes/1");
|
|
546
|
+
const parentId = new URL("https://example.com/notes/2");
|
|
547
|
+
const root = new _fedify_vocab.Note({
|
|
548
|
+
id: rootId,
|
|
549
|
+
content: "root"
|
|
550
|
+
});
|
|
551
|
+
const parent = new _fedify_vocab.Note({
|
|
552
|
+
id: parentId,
|
|
553
|
+
content: "parent",
|
|
554
|
+
replyTarget: rootId
|
|
555
|
+
});
|
|
556
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
557
|
+
if (iri.href === parentId.href) return Promise.resolve(parent);
|
|
558
|
+
if (iri.href === rootId.href) return Promise.resolve(root);
|
|
559
|
+
return Promise.resolve(null);
|
|
560
|
+
} }, new _fedify_vocab.Note({
|
|
561
|
+
id: new URL("https://example.com/notes/3"),
|
|
562
|
+
replyTarget: parentId
|
|
563
|
+
}), {
|
|
564
|
+
strategies: ["reply-tree"],
|
|
565
|
+
maxDepth: 1
|
|
566
|
+
});
|
|
567
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
568
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].object.id, parent.id);
|
|
569
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
570
|
+
});
|
|
571
|
+
(0, node_test.default)("reply tree defaults maxDepth to 10 for ancestors", async () => {
|
|
572
|
+
let note = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/0") });
|
|
573
|
+
for (let i = 1; i <= 12; i++) note = new _fedify_vocab.Note({
|
|
574
|
+
id: new URL(`https://example.com/notes/${i}`),
|
|
575
|
+
replyTarget: note
|
|
576
|
+
});
|
|
577
|
+
const items = await collect({ documentLoader: () => {
|
|
578
|
+
throw new Error("documentLoader should not be called");
|
|
579
|
+
} }, note, { strategies: ["reply-tree"] });
|
|
580
|
+
(0, node_assert_strict.strictEqual)(items.length, 10);
|
|
581
|
+
(0, node_assert_strict.strictEqual)(items.at(-1)?.depth, 10);
|
|
582
|
+
});
|
|
583
|
+
(0, node_test.default)("maxRequests limits reply tree ancestor dereferencing", async () => {
|
|
584
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
585
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => {
|
|
586
|
+
throw new Error("documentLoader should not be called");
|
|
587
|
+
} }, new _fedify_vocab.Note({
|
|
588
|
+
id: new URL("https://example.com/notes/2"),
|
|
589
|
+
replyTarget: parentId
|
|
590
|
+
}), {
|
|
591
|
+
strategies: ["reply-tree"],
|
|
592
|
+
maxRequests: 0
|
|
593
|
+
}), []);
|
|
594
|
+
});
|
|
595
|
+
(0, node_test.default)("reply tree avoids ancestor cycles", async () => {
|
|
596
|
+
const seedId = new URL("https://example.com/notes/1");
|
|
597
|
+
const parentId = new URL("https://example.com/notes/2");
|
|
598
|
+
const note = new _fedify_vocab.Note({
|
|
599
|
+
id: seedId,
|
|
600
|
+
replyTarget: parentId
|
|
601
|
+
});
|
|
602
|
+
const parent = new _fedify_vocab.Note({
|
|
603
|
+
id: parentId,
|
|
604
|
+
replyTarget: seedId
|
|
605
|
+
});
|
|
606
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
607
|
+
if (iri.href === seedId.href) return Promise.resolve(note);
|
|
608
|
+
if (iri.href === parentId.href) return Promise.resolve(parent);
|
|
609
|
+
return Promise.resolve(null);
|
|
610
|
+
} }, note, { strategies: ["reply-tree"] });
|
|
611
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
612
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].object.id, parent.id);
|
|
613
|
+
});
|
|
614
|
+
(0, node_test.default)("reply tree deduplicates ancestors from context collection", async () => {
|
|
615
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
616
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
617
|
+
const parent = new _fedify_vocab.Note({
|
|
618
|
+
id: parentId,
|
|
619
|
+
content: "parent"
|
|
620
|
+
});
|
|
621
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
622
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
623
|
+
id: contextId,
|
|
624
|
+
items: [parent]
|
|
625
|
+
}));
|
|
626
|
+
if (iri.href === parentId.href) return Promise.resolve(parent);
|
|
627
|
+
return Promise.resolve(null);
|
|
628
|
+
} }, new _fedify_vocab.Note({
|
|
629
|
+
id: new URL("https://example.com/notes/2"),
|
|
630
|
+
contexts: [contextId],
|
|
631
|
+
replyTarget: parentId
|
|
632
|
+
}), { strategies: ["context-auto", "reply-tree"] });
|
|
633
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
634
|
+
(0, node_assert_strict.strictEqual)(items[0].object, parent);
|
|
635
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-auto");
|
|
636
|
+
});
|
|
637
|
+
(0, node_test.default)("document cache avoids duplicate dereferences across strategies", async () => {
|
|
638
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
639
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
640
|
+
const parent = new _fedify_vocab.Note({
|
|
641
|
+
id: parentId,
|
|
642
|
+
content: "parent"
|
|
643
|
+
});
|
|
644
|
+
const note = new _fedify_vocab.Note({
|
|
645
|
+
id: new URL("https://example.com/notes/2"),
|
|
646
|
+
contexts: [contextId],
|
|
647
|
+
replyTarget: parentId
|
|
648
|
+
});
|
|
649
|
+
const requests = [];
|
|
650
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
651
|
+
requests.push(iri);
|
|
652
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
653
|
+
id: contextId,
|
|
654
|
+
items: [parentId]
|
|
655
|
+
}));
|
|
656
|
+
if (iri.href === parentId.href) return Promise.resolve(parent);
|
|
657
|
+
return Promise.resolve(null);
|
|
658
|
+
} }, note, { strategies: ["context-auto", "reply-tree"] });
|
|
659
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
660
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, parentId.href);
|
|
661
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [contextId.href, parentId.href]);
|
|
662
|
+
});
|
|
663
|
+
(0, node_test.default)("document cache does not keep failed dereferences", async () => {
|
|
664
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
665
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
666
|
+
const parent = new _fedify_vocab.Note({
|
|
667
|
+
id: parentId,
|
|
668
|
+
content: "parent"
|
|
669
|
+
});
|
|
670
|
+
const note = new _fedify_vocab.Note({
|
|
671
|
+
id: new URL("https://example.com/notes/2"),
|
|
672
|
+
contexts: [contextId],
|
|
673
|
+
replyTarget: parentId
|
|
674
|
+
});
|
|
675
|
+
const requests = [];
|
|
676
|
+
let parentRequests = 0;
|
|
677
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
678
|
+
requests.push(iri);
|
|
679
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
680
|
+
id: contextId,
|
|
681
|
+
items: [parentId]
|
|
682
|
+
}));
|
|
683
|
+
if (iri.href === parentId.href) {
|
|
684
|
+
parentRequests++;
|
|
685
|
+
if (parentRequests === 1) throw new Error("temporary failure");
|
|
686
|
+
return Promise.resolve(parent);
|
|
687
|
+
}
|
|
688
|
+
return Promise.resolve(null);
|
|
689
|
+
} }, note, { strategies: ["context-auto", "reply-tree"] });
|
|
690
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
691
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, parentId.href);
|
|
692
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [
|
|
693
|
+
contextId.href,
|
|
694
|
+
parentId.href,
|
|
695
|
+
parentId.href
|
|
696
|
+
]);
|
|
697
|
+
});
|
|
698
|
+
(0, node_test.default)("strategy order controls deduplicated item metadata", async () => {
|
|
699
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
700
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
701
|
+
const parent = new _fedify_vocab.Note({
|
|
702
|
+
id: parentId,
|
|
703
|
+
content: "parent"
|
|
704
|
+
});
|
|
705
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
706
|
+
if (iri.href === parentId.href) return Promise.resolve(parent);
|
|
707
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
708
|
+
id: contextId,
|
|
709
|
+
items: [parent]
|
|
710
|
+
}));
|
|
711
|
+
return Promise.resolve(null);
|
|
712
|
+
} }, new _fedify_vocab.Note({
|
|
713
|
+
id: new URL("https://example.com/notes/2"),
|
|
714
|
+
contexts: [contextId],
|
|
715
|
+
replyTarget: parentId
|
|
716
|
+
}), { strategies: ["reply-tree", "context-auto"] });
|
|
717
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
718
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, parentId.href);
|
|
719
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "reply-tree");
|
|
720
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "in-reply-to");
|
|
721
|
+
});
|
|
722
|
+
(0, node_test.default)("context auto preserves strategy order across reply tree", async () => {
|
|
723
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
724
|
+
const parentId = new URL("https://example.com/notes/1");
|
|
725
|
+
const parent = new _fedify_vocab.Note({
|
|
726
|
+
id: parentId,
|
|
727
|
+
content: "parent"
|
|
728
|
+
});
|
|
729
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
730
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
731
|
+
id: contextId,
|
|
732
|
+
items: [parent]
|
|
733
|
+
}));
|
|
734
|
+
if (iri.href === parentId.href) return Promise.resolve(parent);
|
|
735
|
+
return Promise.resolve(null);
|
|
736
|
+
} }, new _fedify_vocab.Note({
|
|
737
|
+
id: new URL("https://example.com/notes/2"),
|
|
738
|
+
contexts: [contextId],
|
|
739
|
+
replyTarget: parentId
|
|
740
|
+
}), { strategies: [
|
|
741
|
+
"context-objects",
|
|
742
|
+
"reply-tree",
|
|
743
|
+
"context-auto"
|
|
744
|
+
] });
|
|
745
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
746
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, parentId.href);
|
|
747
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-objects");
|
|
748
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "collection");
|
|
749
|
+
});
|
|
750
|
+
(0, node_test.default)("reply tree yields embedded descendants", async () => {
|
|
751
|
+
const reply = new _fedify_vocab.Note({
|
|
752
|
+
id: new URL("https://example.com/notes/2"),
|
|
753
|
+
content: "reply"
|
|
754
|
+
});
|
|
755
|
+
const items = await collect({ documentLoader: () => {
|
|
756
|
+
throw new Error("documentLoader should not be called");
|
|
757
|
+
} }, new _fedify_vocab.Note({
|
|
758
|
+
id: new URL("https://example.com/notes/1"),
|
|
759
|
+
replies: new _fedify_vocab.Collection({
|
|
760
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
761
|
+
items: [reply]
|
|
762
|
+
})
|
|
763
|
+
}), { strategies: ["reply-tree"] });
|
|
764
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
765
|
+
(0, node_assert_strict.strictEqual)(items[0].object, reply);
|
|
766
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].id, reply.id);
|
|
767
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "reply-tree");
|
|
768
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "replies");
|
|
769
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
770
|
+
});
|
|
771
|
+
(0, node_test.default)("reply tree walks sibling descendants from discovered ancestor", async () => {
|
|
772
|
+
const seedId = new URL("https://example.com/notes/2");
|
|
773
|
+
const sibling = new _fedify_vocab.Note({
|
|
774
|
+
id: new URL("https://example.com/notes/3"),
|
|
775
|
+
content: "sibling"
|
|
776
|
+
});
|
|
777
|
+
const parent = new _fedify_vocab.Note({
|
|
778
|
+
id: new URL("https://example.com/notes/1"),
|
|
779
|
+
content: "parent",
|
|
780
|
+
replies: new _fedify_vocab.Collection({
|
|
781
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
782
|
+
items: [seedId, sibling]
|
|
783
|
+
})
|
|
784
|
+
});
|
|
785
|
+
const items = await collect({ documentLoader: () => {
|
|
786
|
+
throw new Error("documentLoader should not be called");
|
|
787
|
+
} }, new _fedify_vocab.Note({
|
|
788
|
+
id: seedId,
|
|
789
|
+
replyTarget: parent
|
|
790
|
+
}), { strategies: ["reply-tree"] });
|
|
791
|
+
(0, node_assert_strict.strictEqual)(items.length, 2);
|
|
792
|
+
(0, node_assert_strict.strictEqual)(items[0].object, parent);
|
|
793
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "in-reply-to");
|
|
794
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
795
|
+
(0, node_assert_strict.strictEqual)(items[1].object, sibling);
|
|
796
|
+
(0, node_assert_strict.strictEqual)(items[1].origin, "replies");
|
|
797
|
+
(0, node_assert_strict.strictEqual)(items[1].depth, 2);
|
|
798
|
+
});
|
|
799
|
+
(0, node_test.default)("reply tree maxDepth applies from seed through ancestors", async () => {
|
|
800
|
+
const seedId = new URL("https://example.com/notes/2");
|
|
801
|
+
const sibling = new _fedify_vocab.Note({
|
|
802
|
+
id: new URL("https://example.com/notes/3"),
|
|
803
|
+
content: "sibling"
|
|
804
|
+
});
|
|
805
|
+
const parent = new _fedify_vocab.Note({
|
|
806
|
+
id: new URL("https://example.com/notes/1"),
|
|
807
|
+
content: "parent",
|
|
808
|
+
replies: new _fedify_vocab.Collection({
|
|
809
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
810
|
+
items: [seedId, sibling]
|
|
811
|
+
})
|
|
812
|
+
});
|
|
813
|
+
const items = await collect({ documentLoader: () => {
|
|
814
|
+
throw new Error("documentLoader should not be called");
|
|
815
|
+
} }, new _fedify_vocab.Note({
|
|
816
|
+
id: seedId,
|
|
817
|
+
replyTarget: parent
|
|
818
|
+
}), {
|
|
819
|
+
strategies: ["reply-tree"],
|
|
820
|
+
maxDepth: 1
|
|
821
|
+
});
|
|
822
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
823
|
+
(0, node_assert_strict.strictEqual)(items[0].object, parent);
|
|
824
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
825
|
+
});
|
|
826
|
+
(0, node_test.default)("reply tree dereferences replies collection URL", async () => {
|
|
827
|
+
const repliesId = new URL("https://example.com/notes/1/replies");
|
|
828
|
+
const reply = new _fedify_vocab.Note({
|
|
829
|
+
id: new URL("https://example.com/notes/2"),
|
|
830
|
+
content: "reply"
|
|
831
|
+
});
|
|
832
|
+
const items = await collect({ documentLoader: (iri) => Promise.resolve(iri.href === repliesId.href ? new _fedify_vocab.Collection({
|
|
833
|
+
id: repliesId,
|
|
834
|
+
items: [reply]
|
|
835
|
+
}) : null) }, new _fedify_vocab.Note({
|
|
836
|
+
id: new URL("https://example.com/notes/1"),
|
|
837
|
+
replies: repliesId
|
|
838
|
+
}), { strategies: ["reply-tree"] });
|
|
839
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
840
|
+
(0, node_assert_strict.deepStrictEqual)(items[0].object.id, reply.id);
|
|
841
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "replies");
|
|
842
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
843
|
+
});
|
|
844
|
+
(0, node_test.default)("reply tree maxDepth limits descendants", async () => {
|
|
845
|
+
const grandchild = new _fedify_vocab.Note({
|
|
846
|
+
id: new URL("https://example.com/notes/3"),
|
|
847
|
+
content: "grandchild"
|
|
848
|
+
});
|
|
849
|
+
const reply = new _fedify_vocab.Note({
|
|
850
|
+
id: new URL("https://example.com/notes/2"),
|
|
851
|
+
content: "reply",
|
|
852
|
+
replies: new _fedify_vocab.Collection({
|
|
853
|
+
id: new URL("https://example.com/notes/2/replies"),
|
|
854
|
+
items: [grandchild]
|
|
855
|
+
})
|
|
856
|
+
});
|
|
857
|
+
const items = await collect({ documentLoader: () => {
|
|
858
|
+
throw new Error("documentLoader should not be called");
|
|
859
|
+
} }, new _fedify_vocab.Note({
|
|
860
|
+
id: new URL("https://example.com/notes/1"),
|
|
861
|
+
replies: new _fedify_vocab.Collection({
|
|
862
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
863
|
+
items: [reply]
|
|
864
|
+
})
|
|
865
|
+
}), {
|
|
866
|
+
strategies: ["reply-tree"],
|
|
867
|
+
maxDepth: 1
|
|
868
|
+
});
|
|
869
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
870
|
+
(0, node_assert_strict.strictEqual)(items[0].object, reply);
|
|
871
|
+
(0, node_assert_strict.strictEqual)(items[0].depth, 1);
|
|
872
|
+
});
|
|
873
|
+
(0, node_test.default)("reply tree defaults maxDepth to 10 for descendants", async () => {
|
|
874
|
+
let note = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/12") });
|
|
875
|
+
for (let i = 11; i >= 0; i--) note = new _fedify_vocab.Note({
|
|
876
|
+
id: new URL(`https://example.com/notes/${i}`),
|
|
877
|
+
replies: new _fedify_vocab.Collection({
|
|
878
|
+
id: new URL(`https://example.com/notes/${i}/replies`),
|
|
879
|
+
items: [note]
|
|
880
|
+
})
|
|
881
|
+
});
|
|
882
|
+
const items = await collect({ documentLoader: () => {
|
|
883
|
+
throw new Error("documentLoader should not be called");
|
|
884
|
+
} }, note, { strategies: ["reply-tree"] });
|
|
885
|
+
(0, node_assert_strict.strictEqual)(items.length, 10);
|
|
886
|
+
(0, node_assert_strict.strictEqual)(items.at(-1)?.depth, 10);
|
|
887
|
+
});
|
|
888
|
+
(0, node_test.default)("maxRequests limits reply tree replies dereferencing", async () => {
|
|
889
|
+
const repliesId = new URL("https://example.com/notes/1/replies");
|
|
890
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => {
|
|
891
|
+
throw new Error("documentLoader should not be called");
|
|
892
|
+
} }, new _fedify_vocab.Note({
|
|
893
|
+
id: new URL("https://example.com/notes/1"),
|
|
894
|
+
replies: repliesId
|
|
895
|
+
}), {
|
|
896
|
+
strategies: ["reply-tree"],
|
|
897
|
+
maxRequests: 0
|
|
898
|
+
}), []);
|
|
899
|
+
});
|
|
900
|
+
(0, node_test.default)("reply tree does not reload visited replies collection URL", async () => {
|
|
901
|
+
const repliesId = new URL("https://example.com/notes/1/replies");
|
|
902
|
+
const reply = new _fedify_vocab.Note({
|
|
903
|
+
id: new URL("https://example.com/notes/2"),
|
|
904
|
+
content: "reply",
|
|
905
|
+
replies: repliesId
|
|
906
|
+
});
|
|
907
|
+
const note = new _fedify_vocab.Note({
|
|
908
|
+
id: new URL("https://example.com/notes/1"),
|
|
909
|
+
replies: repliesId
|
|
910
|
+
});
|
|
911
|
+
let requests = 0;
|
|
912
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
913
|
+
requests++;
|
|
914
|
+
(0, node_assert_strict.strictEqual)(iri.href, repliesId.href);
|
|
915
|
+
return Promise.resolve(new _fedify_vocab.Collection({
|
|
916
|
+
id: repliesId,
|
|
917
|
+
items: [reply]
|
|
918
|
+
}));
|
|
919
|
+
} }, note, { strategies: ["reply-tree"] });
|
|
920
|
+
(0, node_assert_strict.strictEqual)(requests, 1);
|
|
921
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
922
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, reply.id?.href);
|
|
923
|
+
});
|
|
924
|
+
(0, node_test.default)("reply tree retries a replies collection after load failure", async () => {
|
|
925
|
+
const repliesId = new URL("https://example.com/replies/shared");
|
|
926
|
+
const grandchild = new _fedify_vocab.Note({
|
|
927
|
+
id: new URL("https://example.com/notes/4"),
|
|
928
|
+
content: "grandchild"
|
|
929
|
+
});
|
|
930
|
+
const first = new _fedify_vocab.Note({
|
|
931
|
+
id: new URL("https://example.com/notes/2"),
|
|
932
|
+
replies: repliesId
|
|
933
|
+
});
|
|
934
|
+
const second = new _fedify_vocab.Note({
|
|
935
|
+
id: new URL("https://example.com/notes/3"),
|
|
936
|
+
replies: repliesId
|
|
937
|
+
});
|
|
938
|
+
const note = new _fedify_vocab.Note({
|
|
939
|
+
id: new URL("https://example.com/notes/1"),
|
|
940
|
+
replies: new _fedify_vocab.Collection({
|
|
941
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
942
|
+
items: [first, second]
|
|
943
|
+
})
|
|
944
|
+
});
|
|
945
|
+
let requests = 0;
|
|
946
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
947
|
+
(0, node_assert_strict.strictEqual)(iri.href, repliesId.href);
|
|
948
|
+
requests++;
|
|
949
|
+
if (requests === 1) throw new Error("temporary failure");
|
|
950
|
+
return Promise.resolve(new _fedify_vocab.Collection({
|
|
951
|
+
id: repliesId,
|
|
952
|
+
items: [grandchild]
|
|
953
|
+
}));
|
|
954
|
+
} }, note, { strategies: ["reply-tree"] });
|
|
955
|
+
(0, node_assert_strict.strictEqual)(requests, 2);
|
|
956
|
+
(0, node_assert_strict.deepStrictEqual)(items.map((item) => item.object.id?.href), [
|
|
957
|
+
first.id?.href,
|
|
958
|
+
second.id?.href,
|
|
959
|
+
grandchild.id?.href
|
|
960
|
+
]);
|
|
961
|
+
(0, node_assert_strict.strictEqual)(items[2].depth, 2);
|
|
962
|
+
});
|
|
963
|
+
(0, node_test.default)("reply tree skips visited reply IRIs before dereferencing", async () => {
|
|
964
|
+
const seedId = new URL("https://example.com/notes/1");
|
|
965
|
+
const siblingId = new URL("https://example.com/notes/2");
|
|
966
|
+
const sibling = new _fedify_vocab.Note({
|
|
967
|
+
id: siblingId,
|
|
968
|
+
content: "sibling"
|
|
969
|
+
});
|
|
970
|
+
const note = new _fedify_vocab.Note({
|
|
971
|
+
id: seedId,
|
|
972
|
+
replies: new _fedify_vocab.Collection({
|
|
973
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
974
|
+
items: [seedId, siblingId]
|
|
975
|
+
})
|
|
976
|
+
});
|
|
977
|
+
const requests = [];
|
|
978
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
979
|
+
requests.push(iri.href);
|
|
980
|
+
if (iri.href === siblingId.href) return Promise.resolve(sibling);
|
|
981
|
+
if (iri.href === seedId.href) throw new Error("seed should have been skipped");
|
|
982
|
+
return Promise.resolve(null);
|
|
983
|
+
} }, note, {
|
|
984
|
+
strategies: ["reply-tree"],
|
|
985
|
+
maxRequests: 1
|
|
986
|
+
});
|
|
987
|
+
(0, node_assert_strict.deepStrictEqual)(requests, [siblingId.href]);
|
|
988
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
989
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, siblingId.href);
|
|
990
|
+
});
|
|
991
|
+
(0, node_test.default)("reply tree avoids descendant cycles", async () => {
|
|
992
|
+
const seedId = new URL("https://example.com/notes/1");
|
|
993
|
+
const replyId = new URL("https://example.com/notes/2");
|
|
994
|
+
const note = new _fedify_vocab.Note({ id: seedId });
|
|
995
|
+
const reply = new _fedify_vocab.Note({
|
|
996
|
+
id: replyId,
|
|
997
|
+
replies: new _fedify_vocab.Collection({
|
|
998
|
+
id: new URL("https://example.com/notes/2/replies"),
|
|
999
|
+
items: [note]
|
|
1000
|
+
})
|
|
1001
|
+
});
|
|
1002
|
+
const items = await collect({ documentLoader: () => {
|
|
1003
|
+
throw new Error("documentLoader should not be called");
|
|
1004
|
+
} }, note.clone({ replies: new _fedify_vocab.Collection({
|
|
1005
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
1006
|
+
items: [reply]
|
|
1007
|
+
}) }), { strategies: ["reply-tree"] });
|
|
1008
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1009
|
+
(0, node_assert_strict.strictEqual)(items[0].object, reply);
|
|
1010
|
+
});
|
|
1011
|
+
(0, node_test.default)("context auto overrides overlapping context strategies", async () => {
|
|
1012
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1013
|
+
const item = new _fedify_vocab.Note({ content: "anonymous" });
|
|
1014
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1015
|
+
id: contextId,
|
|
1016
|
+
items: [item]
|
|
1017
|
+
})) }, new _fedify_vocab.Note({
|
|
1018
|
+
id: new URL("https://example.com/notes/1"),
|
|
1019
|
+
contexts: [contextId]
|
|
1020
|
+
}), { strategies: [
|
|
1021
|
+
"context-objects",
|
|
1022
|
+
"context-auto",
|
|
1023
|
+
"reply-tree"
|
|
1024
|
+
] });
|
|
1025
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1026
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
1027
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-auto");
|
|
1028
|
+
});
|
|
1029
|
+
(0, node_test.default)("duplicate strategies are ignored", async () => {
|
|
1030
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1031
|
+
const item = new _fedify_vocab.Note({ content: "anonymous" });
|
|
1032
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1033
|
+
id: contextId,
|
|
1034
|
+
items: [item]
|
|
1035
|
+
})) }, new _fedify_vocab.Note({
|
|
1036
|
+
id: new URL("https://example.com/notes/1"),
|
|
1037
|
+
contexts: [contextId]
|
|
1038
|
+
}), { strategies: ["context-objects", "context-objects"] });
|
|
1039
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1040
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
1041
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-objects");
|
|
1042
|
+
});
|
|
1043
|
+
(0, node_test.default)("context activity collection yields object from embedded Create", async () => {
|
|
1044
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1045
|
+
const item = new _fedify_vocab.Note({
|
|
1046
|
+
id: new URL("https://example.com/notes/2"),
|
|
1047
|
+
content: "hello"
|
|
1048
|
+
});
|
|
1049
|
+
const activity = new _fedify_vocab.Create({
|
|
1050
|
+
id: new URL("https://example.com/activities/1"),
|
|
1051
|
+
object: item
|
|
1052
|
+
});
|
|
1053
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1054
|
+
id: contextId,
|
|
1055
|
+
items: [activity]
|
|
1056
|
+
})) }, new _fedify_vocab.Note({
|
|
1057
|
+
id: new URL("https://example.com/notes/1"),
|
|
1058
|
+
contexts: [contextId]
|
|
1059
|
+
}), { strategies: ["context-activities"] });
|
|
1060
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1061
|
+
(0, node_assert_strict.strictEqual)(items[0].object, item);
|
|
1062
|
+
(0, node_assert_strict.strictEqual)(items[0].id?.href, item.id?.href);
|
|
1063
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-activities");
|
|
1064
|
+
(0, node_assert_strict.strictEqual)(items[0].origin, "collection");
|
|
1065
|
+
});
|
|
1066
|
+
(0, node_test.default)("combined context strategies yield posts and activity objects", async () => {
|
|
1067
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1068
|
+
const post = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") });
|
|
1069
|
+
const activityObject = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/3") });
|
|
1070
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1071
|
+
id: contextId,
|
|
1072
|
+
items: [post, new _fedify_vocab.Create({
|
|
1073
|
+
id: new URL("https://example.com/activities/1"),
|
|
1074
|
+
object: activityObject
|
|
1075
|
+
})]
|
|
1076
|
+
})) }, new _fedify_vocab.Note({
|
|
1077
|
+
id: new URL("https://example.com/notes/1"),
|
|
1078
|
+
contexts: [contextId]
|
|
1079
|
+
}), { strategies: ["context-objects", "context-activities"] });
|
|
1080
|
+
(0, node_assert_strict.strictEqual)(items.length, 2);
|
|
1081
|
+
(0, node_assert_strict.strictEqual)(items[0].object, post);
|
|
1082
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-objects");
|
|
1083
|
+
(0, node_assert_strict.strictEqual)(items[1].object, activityObject);
|
|
1084
|
+
(0, node_assert_strict.strictEqual)(items[1].strategy, "context-activities");
|
|
1085
|
+
});
|
|
1086
|
+
(0, node_test.default)("combined context strategies share context collection loading", async () => {
|
|
1087
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1088
|
+
const post = new _fedify_vocab.Note({
|
|
1089
|
+
id: new URL("https://example.com/notes/2"),
|
|
1090
|
+
content: "hello"
|
|
1091
|
+
});
|
|
1092
|
+
const activityObject = new _fedify_vocab.Note({
|
|
1093
|
+
id: new URL("https://example.com/notes/3"),
|
|
1094
|
+
content: "activity object"
|
|
1095
|
+
});
|
|
1096
|
+
const activity = new _fedify_vocab.Create({
|
|
1097
|
+
id: new URL("https://example.com/activities/1"),
|
|
1098
|
+
object: activityObject
|
|
1099
|
+
});
|
|
1100
|
+
const note = new _fedify_vocab.Note({
|
|
1101
|
+
id: new URL("https://example.com/notes/1"),
|
|
1102
|
+
contexts: [contextId]
|
|
1103
|
+
});
|
|
1104
|
+
let requests = 0;
|
|
1105
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1106
|
+
requests++;
|
|
1107
|
+
(0, node_assert_strict.strictEqual)(iri.href, contextId.href);
|
|
1108
|
+
return Promise.resolve(new _fedify_vocab.Collection({
|
|
1109
|
+
id: contextId,
|
|
1110
|
+
items: [post, activity]
|
|
1111
|
+
}));
|
|
1112
|
+
} }, note, { strategies: ["context-objects", "context-activities"] });
|
|
1113
|
+
(0, node_assert_strict.strictEqual)(requests, 1);
|
|
1114
|
+
(0, node_assert_strict.strictEqual)(items.length, 2);
|
|
1115
|
+
(0, node_assert_strict.strictEqual)(items[0].object, post);
|
|
1116
|
+
(0, node_assert_strict.strictEqual)(items[1].object, activityObject);
|
|
1117
|
+
});
|
|
1118
|
+
(0, node_test.default)("context activity collection dereferences activity object URL", async () => {
|
|
1119
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1120
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1121
|
+
const item = new _fedify_vocab.Note({
|
|
1122
|
+
id: itemId,
|
|
1123
|
+
content: "hello"
|
|
1124
|
+
});
|
|
1125
|
+
const activity = new _fedify_vocab.Create({
|
|
1126
|
+
id: new URL("https://example.com/activities/1"),
|
|
1127
|
+
object: itemId
|
|
1128
|
+
});
|
|
1129
|
+
const note = new _fedify_vocab.Note({
|
|
1130
|
+
id: new URL("https://example.com/notes/1"),
|
|
1131
|
+
contexts: [contextId]
|
|
1132
|
+
});
|
|
1133
|
+
const requests = [];
|
|
1134
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1135
|
+
requests.push(iri);
|
|
1136
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1137
|
+
id: contextId,
|
|
1138
|
+
items: [activity]
|
|
1139
|
+
}));
|
|
1140
|
+
if (iri.href === itemId.href) return Promise.resolve(item);
|
|
1141
|
+
return Promise.resolve(null);
|
|
1142
|
+
} }, note, { strategies: ["context-activities"] });
|
|
1143
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1144
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, item.id?.href);
|
|
1145
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [contextId.href, itemId.href]);
|
|
1146
|
+
});
|
|
1147
|
+
(0, node_test.default)("context activity collection dereferences activity URL", async () => {
|
|
1148
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1149
|
+
const activityId = new URL("https://example.com/activities/1");
|
|
1150
|
+
const item = new _fedify_vocab.Note({
|
|
1151
|
+
id: new URL("https://example.com/notes/2"),
|
|
1152
|
+
content: "hello"
|
|
1153
|
+
});
|
|
1154
|
+
const activity = new _fedify_vocab.Create({
|
|
1155
|
+
id: activityId,
|
|
1156
|
+
object: item
|
|
1157
|
+
});
|
|
1158
|
+
const note = new _fedify_vocab.Note({
|
|
1159
|
+
id: new URL("https://example.com/notes/1"),
|
|
1160
|
+
contexts: [contextId]
|
|
1161
|
+
});
|
|
1162
|
+
const requests = [];
|
|
1163
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1164
|
+
requests.push(iri);
|
|
1165
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1166
|
+
id: contextId,
|
|
1167
|
+
items: [activityId]
|
|
1168
|
+
}));
|
|
1169
|
+
if (iri.href === activityId.href) return Promise.resolve(activity);
|
|
1170
|
+
return Promise.resolve(null);
|
|
1171
|
+
} }, note, { strategies: ["context-activities"] });
|
|
1172
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1173
|
+
(0, node_assert_strict.strictEqual)(items[0].object.id?.href, item.id?.href);
|
|
1174
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [contextId.href, activityId.href]);
|
|
1175
|
+
});
|
|
1176
|
+
(0, node_test.default)("context activity collection deduplicates by extracted object ID", async () => {
|
|
1177
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1178
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1179
|
+
const first = new _fedify_vocab.Create({
|
|
1180
|
+
id: new URL("https://example.com/activities/1"),
|
|
1181
|
+
object: new _fedify_vocab.Note({
|
|
1182
|
+
id: itemId,
|
|
1183
|
+
content: "first"
|
|
1184
|
+
})
|
|
1185
|
+
});
|
|
1186
|
+
const second = new _fedify_vocab.Create({
|
|
1187
|
+
id: new URL("https://example.com/activities/2"),
|
|
1188
|
+
object: new _fedify_vocab.Note({
|
|
1189
|
+
id: itemId,
|
|
1190
|
+
content: "second"
|
|
1191
|
+
})
|
|
1192
|
+
});
|
|
1193
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1194
|
+
id: contextId,
|
|
1195
|
+
items: [first, second]
|
|
1196
|
+
})) }, new _fedify_vocab.Note({
|
|
1197
|
+
id: new URL("https://example.com/notes/1"),
|
|
1198
|
+
contexts: [contextId]
|
|
1199
|
+
}), { strategies: ["context-activities"] });
|
|
1200
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1201
|
+
(0, node_assert_strict.strictEqual)(items[0].id?.href, itemId.href);
|
|
1202
|
+
});
|
|
1203
|
+
(0, node_test.default)("context activity collection skips missing object", async () => {
|
|
1204
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1205
|
+
const activity = new _fedify_vocab.Create({ id: new URL("https://example.com/activities/1") });
|
|
1206
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1207
|
+
id: contextId,
|
|
1208
|
+
items: [activity]
|
|
1209
|
+
})) }, new _fedify_vocab.Note({
|
|
1210
|
+
id: new URL("https://example.com/notes/1"),
|
|
1211
|
+
contexts: [contextId]
|
|
1212
|
+
}), { strategies: ["context-activities"] }), []);
|
|
1213
|
+
});
|
|
1214
|
+
(0, node_test.default)("context activity collection skips unsupported activity type", async () => {
|
|
1215
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1216
|
+
const item = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") });
|
|
1217
|
+
const activity = new _fedify_vocab.Announce({
|
|
1218
|
+
id: new URL("https://example.com/activities/1"),
|
|
1219
|
+
object: item
|
|
1220
|
+
});
|
|
1221
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1222
|
+
id: contextId,
|
|
1223
|
+
items: [activity]
|
|
1224
|
+
})) }, new _fedify_vocab.Note({
|
|
1225
|
+
id: new URL("https://example.com/notes/1"),
|
|
1226
|
+
contexts: [contextId]
|
|
1227
|
+
}), { strategies: ["context-activities"] }), []);
|
|
1228
|
+
});
|
|
1229
|
+
(0, node_test.default)("maxRequests limits activity object dereferencing", async () => {
|
|
1230
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1231
|
+
const activityId = new URL("https://example.com/activities/1");
|
|
1232
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1233
|
+
const activity = new _fedify_vocab.Create({
|
|
1234
|
+
id: activityId,
|
|
1235
|
+
object: itemId
|
|
1236
|
+
});
|
|
1237
|
+
const note = new _fedify_vocab.Note({
|
|
1238
|
+
id: new URL("https://example.com/notes/1"),
|
|
1239
|
+
contexts: [contextId]
|
|
1240
|
+
});
|
|
1241
|
+
const requests = [];
|
|
1242
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: (iri) => {
|
|
1243
|
+
requests.push(iri);
|
|
1244
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1245
|
+
id: contextId,
|
|
1246
|
+
items: [activityId]
|
|
1247
|
+
}));
|
|
1248
|
+
if (iri.href === activityId.href) return Promise.resolve(activity);
|
|
1249
|
+
if (iri.href === itemId.href) return Promise.resolve(new _fedify_vocab.Note({ id: itemId }));
|
|
1250
|
+
return Promise.resolve(null);
|
|
1251
|
+
} }, note, {
|
|
1252
|
+
maxRequests: 2,
|
|
1253
|
+
strategies: ["context-activities"]
|
|
1254
|
+
}), []);
|
|
1255
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [contextId.href, activityId.href]);
|
|
1256
|
+
});
|
|
1257
|
+
(0, node_test.default)("maxItems limits context activity items", async () => {
|
|
1258
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1259
|
+
const first = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") });
|
|
1260
|
+
const second = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/3") });
|
|
1261
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1262
|
+
id: contextId,
|
|
1263
|
+
items: [new _fedify_vocab.Create({
|
|
1264
|
+
id: new URL("https://example.com/activities/1"),
|
|
1265
|
+
object: first
|
|
1266
|
+
}), new _fedify_vocab.Create({
|
|
1267
|
+
id: new URL("https://example.com/activities/2"),
|
|
1268
|
+
object: second
|
|
1269
|
+
})]
|
|
1270
|
+
})) }, new _fedify_vocab.Note({
|
|
1271
|
+
id: new URL("https://example.com/notes/1"),
|
|
1272
|
+
contexts: [contextId]
|
|
1273
|
+
}), {
|
|
1274
|
+
maxItems: 1,
|
|
1275
|
+
strategies: ["context-activities"]
|
|
1276
|
+
});
|
|
1277
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1278
|
+
(0, node_assert_strict.strictEqual)(items[0].id?.href, first.id?.href);
|
|
1279
|
+
});
|
|
1280
|
+
(0, node_test.default)("context collection with URL items loads and yields objects", async () => {
|
|
1281
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1282
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1283
|
+
const item = new _fedify_vocab.Note({
|
|
1284
|
+
id: itemId,
|
|
1285
|
+
content: "hello"
|
|
1286
|
+
});
|
|
1287
|
+
const note = new _fedify_vocab.Note({
|
|
1288
|
+
id: new URL("https://example.com/notes/1"),
|
|
1289
|
+
contexts: [contextId]
|
|
1290
|
+
});
|
|
1291
|
+
const requests = [];
|
|
1292
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1293
|
+
requests.push(iri);
|
|
1294
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1295
|
+
id: contextId,
|
|
1296
|
+
items: [itemId]
|
|
1297
|
+
}));
|
|
1298
|
+
if (iri.href === itemId.href) return Promise.resolve(item);
|
|
1299
|
+
return Promise.resolve(null);
|
|
1300
|
+
} }, note);
|
|
1301
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1302
|
+
(0, node_assert_strict.ok)(items[0].id instanceof URL);
|
|
1303
|
+
(0, node_assert_strict.strictEqual)(items[0].id.href, itemId.href);
|
|
1304
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [contextId.href, itemId.href]);
|
|
1305
|
+
});
|
|
1306
|
+
(0, node_test.default)("seen context collection URL items are not loaded", async () => {
|
|
1307
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1308
|
+
const seedId = new URL("https://example.com/notes/1");
|
|
1309
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1310
|
+
const item = new _fedify_vocab.Note({
|
|
1311
|
+
id: itemId,
|
|
1312
|
+
content: "hello"
|
|
1313
|
+
});
|
|
1314
|
+
const note = new _fedify_vocab.Note({
|
|
1315
|
+
id: seedId,
|
|
1316
|
+
contexts: [contextId]
|
|
1317
|
+
});
|
|
1318
|
+
const requests = [];
|
|
1319
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1320
|
+
requests.push(iri);
|
|
1321
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1322
|
+
id: contextId,
|
|
1323
|
+
items: [seedId, itemId]
|
|
1324
|
+
}));
|
|
1325
|
+
if (iri.href === itemId.href) return Promise.resolve(item);
|
|
1326
|
+
throw new Error("seen collection item should not be loaded");
|
|
1327
|
+
} }, note);
|
|
1328
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1329
|
+
(0, node_assert_strict.strictEqual)(items[0].id?.href, itemId.href);
|
|
1330
|
+
(0, node_assert_strict.deepStrictEqual)(requests.map((url) => url.href), [contextId.href, itemId.href]);
|
|
1331
|
+
});
|
|
1332
|
+
(0, node_test.default)("failed URL collection items are skipped", async () => {
|
|
1333
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1334
|
+
const missingItemId = new URL("https://example.com/notes/missing");
|
|
1335
|
+
const failedItemId = new URL("https://example.com/notes/failed");
|
|
1336
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1337
|
+
const item = new _fedify_vocab.Note({
|
|
1338
|
+
id: itemId,
|
|
1339
|
+
content: "hello"
|
|
1340
|
+
});
|
|
1341
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1342
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1343
|
+
id: contextId,
|
|
1344
|
+
items: [
|
|
1345
|
+
missingItemId,
|
|
1346
|
+
failedItemId,
|
|
1347
|
+
itemId
|
|
1348
|
+
]
|
|
1349
|
+
}));
|
|
1350
|
+
if (iri.href === missingItemId.href) return Promise.resolve(null);
|
|
1351
|
+
if (iri.href === failedItemId.href) return Promise.reject(/* @__PURE__ */ new Error("failed to load"));
|
|
1352
|
+
if (iri.href === itemId.href) return Promise.resolve(item);
|
|
1353
|
+
return Promise.resolve(null);
|
|
1354
|
+
} }, new _fedify_vocab.Note({
|
|
1355
|
+
id: new URL("https://example.com/notes/1"),
|
|
1356
|
+
contexts: [contextId]
|
|
1357
|
+
}));
|
|
1358
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1359
|
+
(0, node_assert_strict.strictEqual)(items[0].id?.href, itemId.href);
|
|
1360
|
+
});
|
|
1361
|
+
(0, node_test.default)("seed is not yielded again when present in collection", async () => {
|
|
1362
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1363
|
+
const note = new _fedify_vocab.Note({
|
|
1364
|
+
id: new URL("https://example.com/notes/1"),
|
|
1365
|
+
contexts: [contextId]
|
|
1366
|
+
});
|
|
1367
|
+
const other = new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") });
|
|
1368
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1369
|
+
id: contextId,
|
|
1370
|
+
items: [note, other]
|
|
1371
|
+
})) }, note);
|
|
1372
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1373
|
+
(0, node_assert_strict.strictEqual)(items[0].object, other);
|
|
1374
|
+
});
|
|
1375
|
+
(0, node_test.default)("duplicate object IDs are skipped", async () => {
|
|
1376
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1377
|
+
const duplicateId = new URL("https://example.com/notes/2");
|
|
1378
|
+
const first = new _fedify_vocab.Note({
|
|
1379
|
+
id: duplicateId,
|
|
1380
|
+
content: "first"
|
|
1381
|
+
});
|
|
1382
|
+
const second = new _fedify_vocab.Note({
|
|
1383
|
+
id: duplicateId,
|
|
1384
|
+
content: "second"
|
|
1385
|
+
});
|
|
1386
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1387
|
+
id: contextId,
|
|
1388
|
+
items: [first, second]
|
|
1389
|
+
})) }, new _fedify_vocab.Note({
|
|
1390
|
+
id: new URL("https://example.com/notes/1"),
|
|
1391
|
+
contexts: [contextId]
|
|
1392
|
+
}));
|
|
1393
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1394
|
+
(0, node_assert_strict.strictEqual)(items[0].object, first);
|
|
1395
|
+
});
|
|
1396
|
+
(0, node_test.default)("maxItems limits yielded items", async () => {
|
|
1397
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1398
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1399
|
+
id: contextId,
|
|
1400
|
+
items: [new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") }), new _fedify_vocab.Note({ id: new URL("https://example.com/notes/3") })]
|
|
1401
|
+
})) }, new _fedify_vocab.Note({
|
|
1402
|
+
id: new URL("https://example.com/notes/1"),
|
|
1403
|
+
contexts: [contextId]
|
|
1404
|
+
}), { maxItems: 1 });
|
|
1405
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1406
|
+
(0, node_assert_strict.strictEqual)(items[0].id?.href, "https://example.com/notes/2");
|
|
1407
|
+
});
|
|
1408
|
+
(0, node_test.default)("maxItems is shared across context and reply tree", async () => {
|
|
1409
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1410
|
+
const reply = new _fedify_vocab.Note({
|
|
1411
|
+
id: new URL("https://example.com/notes/3"),
|
|
1412
|
+
content: "reply"
|
|
1413
|
+
});
|
|
1414
|
+
const note = new _fedify_vocab.Note({
|
|
1415
|
+
id: new URL("https://example.com/notes/1"),
|
|
1416
|
+
contexts: [contextId],
|
|
1417
|
+
replies: new _fedify_vocab.Collection({
|
|
1418
|
+
id: new URL("https://example.com/notes/1/replies"),
|
|
1419
|
+
items: [reply]
|
|
1420
|
+
})
|
|
1421
|
+
});
|
|
1422
|
+
const contextItem = new _fedify_vocab.Note({
|
|
1423
|
+
id: new URL("https://example.com/notes/2"),
|
|
1424
|
+
content: "context item"
|
|
1425
|
+
});
|
|
1426
|
+
const items = await collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1427
|
+
id: contextId,
|
|
1428
|
+
items: [contextItem]
|
|
1429
|
+
})) }, note, {
|
|
1430
|
+
strategies: ["context-auto", "reply-tree"],
|
|
1431
|
+
maxItems: 1
|
|
1432
|
+
});
|
|
1433
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1434
|
+
(0, node_assert_strict.strictEqual)(items[0].object, contextItem);
|
|
1435
|
+
(0, node_assert_strict.strictEqual)(items[0].strategy, "context-auto");
|
|
1436
|
+
});
|
|
1437
|
+
(0, node_test.default)("maxRequests limits dereferencing", async () => {
|
|
1438
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1439
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1440
|
+
(0, node_assert_strict.deepStrictEqual)(await collect({ documentLoader: (iri) => {
|
|
1441
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1442
|
+
id: contextId,
|
|
1443
|
+
items: [itemId]
|
|
1444
|
+
}));
|
|
1445
|
+
return Promise.resolve(new _fedify_vocab.Note({ id: iri }));
|
|
1446
|
+
} }, new _fedify_vocab.Note({
|
|
1447
|
+
id: new URL("https://example.com/notes/1"),
|
|
1448
|
+
contexts: [contextId]
|
|
1449
|
+
}), { maxRequests: 1 }), []);
|
|
1450
|
+
});
|
|
1451
|
+
(0, node_test.default)("maxRequests is shared across context and reply tree", async () => {
|
|
1452
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1453
|
+
const parentId = new URL("https://example.com/notes/0");
|
|
1454
|
+
const note = new _fedify_vocab.Note({
|
|
1455
|
+
id: new URL("https://example.com/notes/1"),
|
|
1456
|
+
contexts: [contextId],
|
|
1457
|
+
replyTarget: parentId
|
|
1458
|
+
});
|
|
1459
|
+
const contextItem = new _fedify_vocab.Note({
|
|
1460
|
+
id: new URL("https://example.com/notes/2"),
|
|
1461
|
+
content: "context item"
|
|
1462
|
+
});
|
|
1463
|
+
const items = await collect({ documentLoader: (iri) => {
|
|
1464
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1465
|
+
id: contextId,
|
|
1466
|
+
items: [contextItem]
|
|
1467
|
+
}));
|
|
1468
|
+
throw new Error("reply-tree request should be budgeted out");
|
|
1469
|
+
} }, note, {
|
|
1470
|
+
strategies: ["context-auto", "reply-tree"],
|
|
1471
|
+
maxRequests: 1
|
|
1472
|
+
});
|
|
1473
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1474
|
+
(0, node_assert_strict.strictEqual)(items[0].object, contextItem);
|
|
1475
|
+
});
|
|
1476
|
+
(0, node_test.default)("AbortSignal stops traversal", async () => {
|
|
1477
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1478
|
+
const note = new _fedify_vocab.Note({
|
|
1479
|
+
id: new URL("https://example.com/notes/1"),
|
|
1480
|
+
contexts: [contextId]
|
|
1481
|
+
});
|
|
1482
|
+
const controller = new AbortController();
|
|
1483
|
+
controller.abort();
|
|
1484
|
+
await (0, node_assert_strict.rejects)(collect({ documentLoader: () => Promise.resolve(new _fedify_vocab.Collection({
|
|
1485
|
+
id: contextId,
|
|
1486
|
+
items: [new _fedify_vocab.Note({ id: new URL("https://example.com/notes/2") })]
|
|
1487
|
+
})) }, note, { signal: controller.signal }), { name: "AbortError" });
|
|
1488
|
+
});
|
|
1489
|
+
(0, node_test.default)("AbortSignal stops traversal across strategies", async () => {
|
|
1490
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1491
|
+
const parentId = new URL("https://example.com/notes/0");
|
|
1492
|
+
const controller = new AbortController();
|
|
1493
|
+
const note = new _fedify_vocab.Note({
|
|
1494
|
+
id: new URL("https://example.com/notes/1"),
|
|
1495
|
+
contexts: [contextId],
|
|
1496
|
+
replyTarget: parentId
|
|
1497
|
+
});
|
|
1498
|
+
const contextItem = new _fedify_vocab.Note({
|
|
1499
|
+
id: new URL("https://example.com/notes/2"),
|
|
1500
|
+
content: "context item"
|
|
1501
|
+
});
|
|
1502
|
+
let requests = 0;
|
|
1503
|
+
const context = { documentLoader: (iri) => {
|
|
1504
|
+
requests++;
|
|
1505
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1506
|
+
id: contextId,
|
|
1507
|
+
items: [contextItem]
|
|
1508
|
+
}));
|
|
1509
|
+
throw new Error("reply-tree request should not be started");
|
|
1510
|
+
} };
|
|
1511
|
+
const items = [];
|
|
1512
|
+
await (0, node_assert_strict.rejects)(async () => {
|
|
1513
|
+
for await (const item of backfill(context, note, {
|
|
1514
|
+
strategies: ["context-auto", "reply-tree"],
|
|
1515
|
+
signal: controller.signal
|
|
1516
|
+
})) {
|
|
1517
|
+
items.push(item);
|
|
1518
|
+
controller.abort();
|
|
1519
|
+
}
|
|
1520
|
+
}, { name: "AbortError" });
|
|
1521
|
+
(0, node_assert_strict.strictEqual)(requests, 1);
|
|
1522
|
+
(0, node_assert_strict.strictEqual)(items.length, 1);
|
|
1523
|
+
(0, node_assert_strict.strictEqual)(items[0].object, contextItem);
|
|
1524
|
+
});
|
|
1525
|
+
(0, node_test.default)("documentLoader receives AbortSignal", async () => {
|
|
1526
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1527
|
+
const note = new _fedify_vocab.Note({
|
|
1528
|
+
id: new URL("https://example.com/notes/1"),
|
|
1529
|
+
contexts: [contextId]
|
|
1530
|
+
});
|
|
1531
|
+
const controller = new AbortController();
|
|
1532
|
+
let receivedSignal;
|
|
1533
|
+
await collect({ documentLoader: (_iri, options) => {
|
|
1534
|
+
receivedSignal = options?.signal;
|
|
1535
|
+
return Promise.resolve(new _fedify_vocab.Collection({
|
|
1536
|
+
id: contextId,
|
|
1537
|
+
items: []
|
|
1538
|
+
}));
|
|
1539
|
+
} }, note, { signal: controller.signal });
|
|
1540
|
+
(0, node_assert_strict.strictEqual)(receivedSignal, controller.signal);
|
|
1541
|
+
});
|
|
1542
|
+
(0, node_test.default)("interval callback receives zero-based request index", async () => {
|
|
1543
|
+
const contextId = new URL("https://example.com/contexts/1");
|
|
1544
|
+
const itemId = new URL("https://example.com/notes/2");
|
|
1545
|
+
const note = new _fedify_vocab.Note({
|
|
1546
|
+
id: new URL("https://example.com/notes/1"),
|
|
1547
|
+
contexts: [contextId]
|
|
1548
|
+
});
|
|
1549
|
+
const iterations = [];
|
|
1550
|
+
await collect({ documentLoader: (iri) => {
|
|
1551
|
+
if (iri.href === contextId.href) return Promise.resolve(new _fedify_vocab.Collection({
|
|
1552
|
+
id: contextId,
|
|
1553
|
+
items: [itemId]
|
|
1554
|
+
}));
|
|
1555
|
+
return Promise.resolve(new _fedify_vocab.Note({ id: iri }));
|
|
1556
|
+
} }, note, { interval: (iteration) => {
|
|
1557
|
+
iterations.push(iteration);
|
|
1558
|
+
return { milliseconds: 0 };
|
|
1559
|
+
} });
|
|
1560
|
+
(0, node_assert_strict.deepStrictEqual)(iterations, [0, 1]);
|
|
1561
|
+
});
|
|
1562
|
+
});
|
|
1563
|
+
//#endregion
|