@estiva-app/interop 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,92 @@ how a declaration is read, is a MAJOR — in `0.x`, a MINOR — even when no
6
6
  TypeScript signature moved. A consumer upgrading must be able to tell whether
7
7
  manifests already published still mean what they meant.
8
8
 
9
+ ## 0.4.0 — 2026-09-01
10
+
11
+ **Manifest behaviour: unchanged.** Nothing a manifest may declare moved. What
12
+ moves is a cache that had been living in one app.
13
+
14
+ - **`createPeopleCache()`** — the foreign-profile cache, with its batching and
15
+ its two TTLs, lifted out of Peek.
16
+
17
+ It was written there for PEE-3, and the shape is the interesting part.
18
+ `PROFILE_HIT_TTL_MS` is ten minutes because names change rarely;
19
+ `PROFILE_MISS_TTL_MS` is one, because **a miss is somebody who has not
20
+ finished setting up their identity — precisely the person whose name is about
21
+ to arrive.** Cached as long as a hit, they render as `nostr:<8 chars>` until a
22
+ full reload, and a consumer that re-reads on a timer looks like its refresh is
23
+ broken.
24
+
25
+ Ship needed the same behaviour, and a second consumer is what moves something
26
+ out of one app rather than duplicating it. Without it Ship re-read `kind:0`
27
+ for every author of every child on every tick: a referenced Peek topic with 50
28
+ messages cost **2 requests a tick where a Ship object cost 1** — the cross-app
29
+ case PRO-7 exists for being the dearest rather than the cheapest.
30
+
31
+ **Store and lookup have different lifetimes**, which is why this is a cache
32
+ with `through(fromRelay)` rather than a wrapped function. A consumer builds
33
+ its query per call — Peek's carries the viewer's token — while the profiles it
34
+ finds are public and worth keeping across all of them. `viaRelay(query)` is
35
+ the common case. Caller-owned, like `createProjectionCache`; Peek's original
36
+ was a module-level `Map`, which worked but meant every test needed a reset
37
+ hook to undo the previous one.
38
+
39
+ Peek's seven cases travel with it, converted from vitest **by hand** — a regex
40
+ pass over an earlier suite produced assertions with no `assert` in them, which
41
+ read fine and test nothing — and five more pin the store's lifetime, which the
42
+ original could not express.
43
+
44
+ Additive, so a MINOR by the rule ADR 0002 §4b sets for `0.x`.
45
+
46
+ ## 0.3.0 — 2026-09-01
47
+
48
+ **Manifest behaviour: unchanged.** Nothing a manifest may declare moved, and
49
+ nothing already published means anything different. What changes is how many
50
+ round trips reading one costs.
51
+
52
+ - **`createProjectionCache()`**, passed as the last argument to
53
+ `resolveForeignObject`, `resolveForeignEvent` and `resolveFolderProject`.
54
+ Omitting it is exactly the old behaviour.
55
+
56
+ A consumer that re-resolves on a timer paid the whole resolve every tick, and
57
+ **two of the four round trips were NIP-89 discovery** — the author's
58
+ `kind:31989` recommendation, then the `kind:31990` manifest. Those answer the
59
+ same thing until an app republishes. Memoised per kind and author with a
60
+ five-minute TTL; a negative answer is cached too, because "no app claims this
61
+ kind" costs the same two round trips and is just as stable.
62
+
63
+ **Owned by the caller.** A module-level cache would be invisible global state
64
+ shared by every consumer in a process, impossible to scope to a screen and
65
+ awkward to reset in a test.
66
+
67
+ Deliberately only the manifest. The object, its changes, its comments and its
68
+ children are what a refresh exists to notice — caching those is how a live
69
+ widget becomes a screenshot, which is the defect PRO-10 was filed for.
70
+
71
+ - **A `list` slot no longer costs a round trip of its own.** The child filter is
72
+ built from the manifest and the *pointer*, and an addressable event's `d` is
73
+ `pointer.identifier` by definition — it is what the root filter matches on. So
74
+ the second query never needed to wait for the first, and the children now ride
75
+ in the same request.
76
+
77
+ The two together: **10 requests a tick to 3** for three references on one Ship
78
+ issue, measured against production — one per reference — rendering identically
79
+ (title, kind, comment count, child count, widget). A comment posted between
80
+ ticks appeared on the next one-request resolve, so the widget is still live.
81
+
82
+ - **Comments and children are now matched on their filter's own criteria**, not
83
+ on kind. This is load-bearing rather than tidying: Peek's Topic declares
84
+ `kind:9` messages as children *and* `kind:9` as its comment kind, so merging
85
+ the two filters into one request puts both under one number and only the tag
86
+ separates them. Matching on kind alone would have made every message in a
87
+ Folder a comment on its own Topic. The predicate uses **any** matching tag,
88
+ as a relay's `#a` does — an event may carry several, and reading only the
89
+ first would silently drop a comment that references something else before its
90
+ parent.
91
+
92
+ Additive, with a changed request pattern, so a MINOR: ADR 0002 §4b puts the
93
+ break on MINOR within `0.x`.
94
+
9
95
  ## 0.2.0 — 2026-09-01
10
96
 
11
97
  **Manifest behaviour: unchanged for addressable objects.** What changes is that
package/dist/index.d.ts CHANGED
@@ -24,5 +24,5 @@
24
24
  * of every app that installed it, which is the objection that rules out
25
25
  * iframes.
26
26
  */
27
- export { resolveManifest, resolveForeignObject, resolveForeignEvent, resolveFolderProject, commentKindsOf, buildActionEvent, pickWidget, widgetChainProblem, CLOSED_WIDGETS, peopleViaRelay, type QueryFn, type PeopleFn, type People, type ForeignObject, type FolderProject, type ResolvedSlot, type ResolvedAction, type ManifestAction, type UnsignedActionEvent, } from './projection.js';
27
+ export { resolveManifest, resolveForeignObject, resolveForeignEvent, resolveFolderProject, commentKindsOf, buildActionEvent, pickWidget, widgetChainProblem, CLOSED_WIDGETS, peopleViaRelay, createProjectionCache, createPeopleCache, MANIFEST_TTL_MS, PROFILE_HIT_TTL_MS, PROFILE_MISS_TTL_MS, type PeopleCache, type PeopleCacheOptions, type ProjectionCache, type ResolvedManifest, type QueryFn, type PeopleFn, type People, type ForeignObject, type FolderProject, type ResolvedSlot, type ResolvedAction, type ManifestAction, type UnsignedActionEvent, } from './projection.js';
28
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -24,5 +24,5 @@
24
24
  * of every app that installed it, which is the objection that rules out
25
25
  * iframes.
26
26
  */
27
- export { resolveManifest, resolveForeignObject, resolveForeignEvent, resolveFolderProject, commentKindsOf, buildActionEvent, pickWidget, widgetChainProblem, CLOSED_WIDGETS, peopleViaRelay, } from './projection.js';
27
+ export { resolveManifest, resolveForeignObject, resolveForeignEvent, resolveFolderProject, commentKindsOf, buildActionEvent, pickWidget, widgetChainProblem, CLOSED_WIDGETS, peopleViaRelay, createProjectionCache, createPeopleCache, MANIFEST_TTL_MS, PROFILE_HIT_TTL_MS, PROFILE_MISS_TTL_MS, } from './projection.js';
28
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,GAUf,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,GAcpB,MAAM,iBAAiB,CAAA"}
@@ -45,6 +45,51 @@ export type People = Record<string, Profile>;
45
45
  */
46
46
  export type PeopleFn = (pubkeys: string[]) => Promise<People>;
47
47
  /** The default: one kind:0 query, straight to the relay. */
48
+ /**
49
+ * How long a *name* may be believed.
50
+ *
51
+ * Names change rarely, so this is generous. See {@link PROFILE_MISS_TTL_MS} for
52
+ * the half that matters.
53
+ */
54
+ export declare const PROFILE_HIT_TTL_MS: number;
55
+ /**
56
+ * How long an *absent* profile may be believed, and it is deliberately short.
57
+ *
58
+ * A miss is somebody who has not finished setting up their identity — which is
59
+ * to say, precisely the person whose name is about to arrive. Cached as long as
60
+ * a hit, they render as `nostr:<8 chars>` until a full reload, and a consumer
61
+ * that re-reads on a timer looks like its refresh is broken. That is PEE-3,
62
+ * found in Peek once its panels started polling.
63
+ */
64
+ export declare const PROFILE_MISS_TTL_MS = 60000;
65
+ export interface PeopleCacheOptions {
66
+ hitTtlMs?: number;
67
+ missTtlMs?: number;
68
+ /** Injectable clock. A test needs to move time, not wait ten minutes. */
69
+ now?: () => number;
70
+ }
71
+ /**
72
+ * A profile store that outlives any one lookup.
73
+ *
74
+ * The store and the lookup have different lifetimes, which is why this is not
75
+ * simply a wrapped {@link PeopleFn}: a consumer builds its query per call —
76
+ * Peek's carries the viewer's token — while the profiles it finds are public
77
+ * and worth keeping across all of them. `through` takes the lookup of the
78
+ * moment and answers from one store.
79
+ *
80
+ * **Caller-owned**, like {@link createProjectionCache}. Peek's original lived in
81
+ * a module-level `Map`, which worked but meant every test needed a reset hook
82
+ * to undo the previous one.
83
+ */
84
+ export interface PeopleCache {
85
+ /** Wrap a lookup so it batches and expires against this store. */
86
+ through(fromRelay: PeopleFn): PeopleFn;
87
+ /** The common case: {@link peopleViaRelay}, cached. */
88
+ viaRelay(query: QueryFn): PeopleFn;
89
+ /** Forget everything. What a sign-out should call. */
90
+ clear(): void;
91
+ }
92
+ export declare function createPeopleCache(options?: PeopleCacheOptions): PeopleCache;
48
93
  export declare function peopleViaRelay(query: QueryFn): PeopleFn;
49
94
  /**
50
95
  * Every kind an app's comments might be under — what it publishes **now**, plus
@@ -223,13 +268,62 @@ interface Manifest {
223
268
  stage?: string;
224
269
  }[]>;
225
270
  }
226
- export declare function resolveManifest(pointer: AddressPointer, query: QueryFn): Promise<{
271
+ /** What {@link resolveManifest} answers with. */
272
+ export interface ResolvedManifest {
227
273
  manifest: Manifest;
228
274
  address: string;
229
275
  viaRecommendation: boolean;
230
276
  /** NIP-89 `web` template, `<bech32>` not yet substituted. */
231
277
  webTemplate?: string;
232
- } | null>;
278
+ }
279
+ /**
280
+ * A memo for the half of a resolve that does not change between refreshes.
281
+ *
282
+ * Resolving one reference costs four round trips, and **two of them are NIP-89
283
+ * discovery** — the author's `kind:31989` recommendation, then the `kind:31990`
284
+ * manifest itself. A consumer that re-resolves on a timer pays for both every
285
+ * time, and they answer the same thing until an app republishes its manifest.
286
+ * Measured on Ship, where a reference widget re-resolves on the poll: 4
287
+ * requests per reference per tick, identical on the second resolve, against a
288
+ * relay that meters reads at 300 a minute.
289
+ *
290
+ * **Owned by the caller, not this module.** A module-level cache would be
291
+ * invisible global state shared by every consumer in the process, impossible to
292
+ * scope to a screen and awkward to reset in a test. A caller that wants no
293
+ * caching passes nothing and gets exactly the old behaviour.
294
+ *
295
+ * Deliberately only the manifest. The object, its changes, its comments and its
296
+ * children are the parts a refresh exists to notice, and caching those is how
297
+ * a live widget becomes a screenshot.
298
+ */
299
+ export interface ProjectionCache {
300
+ /** Forget everything. Worth calling after publishing a manifest. */
301
+ clear(): void;
302
+ /** @internal */
303
+ lookup(key: string, now: number): {
304
+ value: ResolvedManifest | null;
305
+ } | undefined;
306
+ /** @internal */
307
+ remember(key: string, value: ResolvedManifest | null, now: number): void;
308
+ }
309
+ /**
310
+ * How long a manifest may be believed without asking again.
311
+ *
312
+ * Five minutes is a compromise with one real cost: republish a manifest and
313
+ * consumers keep drawing the old projection for up to that long. That is
314
+ * recoverable and self-correcting, where the alternative — asking twice per
315
+ * reference per tick, for ever — is neither.
316
+ */
317
+ export declare const MANIFEST_TTL_MS: number;
318
+ export declare function createProjectionCache(ttlMs?: number): ProjectionCache;
319
+ /**
320
+ * A negative answer is cached too.
321
+ *
322
+ * "No app claims this kind" costs the same two round trips as a hit and is just
323
+ * as stable. Caching only successes would leave the expensive case — a
324
+ * reference nothing can draw — paying full price on every tick for ever.
325
+ */
326
+ export declare function resolveManifest(pointer: AddressPointer, query: QueryFn, cache?: ProjectionCache): Promise<ResolvedManifest | null>;
233
327
  /**
234
328
  * Which layout to draw, from a declared type or an ordered chain of them.
235
329
  *
@@ -456,7 +550,9 @@ export interface ForeignObject {
456
550
  */
457
551
  export declare function resolveForeignEvent(reference: string, query: QueryFn,
458
552
  /** Defaults to asking the relay. The browser passes a cached lookup. */
459
- lookupPeople?: PeopleFn): Promise<ForeignObject | null>;
553
+ lookupPeople?: PeopleFn,
554
+ /** See {@link ProjectionCache}. Omitting it is exactly the old behaviour. */
555
+ cache?: ProjectionCache): Promise<ForeignObject | null>;
460
556
  export declare function resolveForeignObject(naddr: string, query: QueryFn,
461
557
  /** Defaults to asking the relay. The browser passes a cached lookup. */
462
558
  lookupPeople?: PeopleFn,
@@ -465,7 +561,12 @@ lookupPeople?: PeopleFn,
465
561
  * module leave it at 0; it is the budget that stops a child's own `list`
466
562
  * recursing forever. See `MAX_LIST_DEPTH`.
467
563
  */
468
- depth?: number): Promise<ForeignObject | null>;
564
+ depth?: number,
565
+ /**
566
+ * Optional memo for the NIP-89 discovery half. See {@link ProjectionCache} —
567
+ * omitting it is exactly the old behaviour.
568
+ */
569
+ cache?: ProjectionCache): Promise<ForeignObject | null>;
469
570
  /**
470
571
  * A Folder's project and the tickets in motion in it.
471
572
  *
@@ -513,7 +614,9 @@ lookupPeople?: PeopleFn,
513
614
  * outside this module leave it at 0; it exists so that following a child's
514
615
  * own `list` is a budget check rather than a thing nobody remembered.
515
616
  */
516
- depth?: number): Promise<FolderProject | null>;
617
+ depth?: number,
618
+ /** See {@link ProjectionCache}. Omitting it is exactly the old behaviour. */
619
+ cache?: ProjectionCache): Promise<FolderProject | null>;
517
620
  /**
518
621
  * Build the event that performs a manifest-declared action.
519
622
  *
@@ -1 +1 @@
1
- {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAiF,KAAK,cAAc,EAAqB,MAAM,sBAAsB,CAAA;AAC5J,OAAO,EAAgB,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEnF,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;AAEpF,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAE7D,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAevD;AA0CD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAK3D;AAED,0DAA0D;AAC1D,UAAU,WAAW;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/C;AAED,UAAU,QAAQ;IAChB;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd;;;;;;;;;;;;;WAaG;QACH,KAAK,CAAC,EAAE,SAAS,GAAG,YAAY,CAAA;KACjC,CAAA;CACF;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC5B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;QACd;;;;;;;;;WASG;QACH,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB;;;;;WAKG;QACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KACpB,CAAA;IACD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;IACrC,gEAAgE;IAChE,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC7D,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,cAAc,EAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAAC,CAAA;KAAE,CAAC,CAAA;IACzG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAA;CAClG;AAmFD,wBAAsB,eAAe,CACnC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,OAAO,CAAA;IAC1B,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,IAAI,CAAC,CAmDR;AAmFD;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,2CAA4C,CAAA;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAiBnE;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EACzC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,EAC3B,WAAW,EAAE,SAAS,CAAC,EAAE,EACzB,QAAQ,EAAE,CAAC,GACV,CAAC,CAKH;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA+GD,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;OAYG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;;;;;OAcG;IACH,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACnC,oCAAoC;IACpC,IAAI,EAAE,YAAY,EAAE,CAAA;IACpB,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3E;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,iBAAiB,EAAE,OAAO,CAAA;IAC1B;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAqED;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO;AACd,wEAAwE;AACxE,YAAY,CAAC,EAAE,QAAQ,GACtB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA6C/B;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO;AACd,wEAAwE;AACxE,YAAY,CAAC,EAAE,QAAQ;AACvB;;;;GAIG;AACH,KAAK,SAAI,GACR,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAwH/B;AAyHD;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAA;IACtB;;;;OAIG;IACH,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;CAClB;AAgMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO;AACd,wEAAwE;AACxE,YAAY,CAAC,EAAE,QAAQ;AACvB;;;;GAIG;AACH,KAAK,SAAI,GACR,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAqW/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,QAAQ,EAAE;QAAE,OAAO,CAAC,EAAE,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;QAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAA;KAAE,CAAA;IACxG,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,mBAAmB,GAAG,MAAM,CAyD/B;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,WAAW,GAChB,MAAM,GAAG,SAAS,CAIpB"}
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,57 @@
15
15
  import { decodeNevent, encodeNaddr, encodeNevent, pointerToAddress, referenceToPointer } from '@estiva-app/protocol';
16
16
  import { parseProfile } from '@estiva-app/protocol';
17
17
  /** The default: one kind:0 query, straight to the relay. */
18
+ /**
19
+ * How long a *name* may be believed.
20
+ *
21
+ * Names change rarely, so this is generous. See {@link PROFILE_MISS_TTL_MS} for
22
+ * the half that matters.
23
+ */
24
+ export const PROFILE_HIT_TTL_MS = 10 * 60_000;
25
+ /**
26
+ * How long an *absent* profile may be believed, and it is deliberately short.
27
+ *
28
+ * A miss is somebody who has not finished setting up their identity — which is
29
+ * to say, precisely the person whose name is about to arrive. Cached as long as
30
+ * a hit, they render as `nostr:<8 chars>` until a full reload, and a consumer
31
+ * that re-reads on a timer looks like its refresh is broken. That is PEE-3,
32
+ * found in Peek once its panels started polling.
33
+ */
34
+ export const PROFILE_MISS_TTL_MS = 60_000;
35
+ /** A profile with nothing in it is a miss — the relay had no `kind:0`. */
36
+ const isMiss = (person) => Object.keys(person).length === 0;
37
+ export function createPeopleCache(options = {}) {
38
+ const hitTtlMs = options.hitTtlMs ?? PROFILE_HIT_TTL_MS;
39
+ const missTtlMs = options.missTtlMs ?? PROFILE_MISS_TTL_MS;
40
+ const now = options.now ?? Date.now;
41
+ const known = new Map();
42
+ const through = (fromRelay) => async (pubkeys) => {
43
+ const at = now();
44
+ /*
45
+ One lookup for the whole unknown set, never one per key. A screen with ten
46
+ reference widgets asks about the same handful of people, and asking
47
+ separately would turn one request into N — which is the cost this exists
48
+ to avoid, not a detail of it.
49
+ */
50
+ const unknown = pubkeys.filter((key) => {
51
+ const cached = known.get(key);
52
+ return cached === undefined || cached.expiresAt <= at;
53
+ });
54
+ if (unknown.length > 0) {
55
+ const found = await fromRelay(unknown);
56
+ for (const key of unknown) {
57
+ const person = found[key] ?? {};
58
+ known.set(key, { person, expiresAt: at + (isMiss(person) ? missTtlMs : hitTtlMs) });
59
+ }
60
+ }
61
+ return Object.fromEntries(pubkeys.map((key) => [key, known.get(key)?.person ?? {}]));
62
+ };
63
+ return {
64
+ through,
65
+ viaRelay: (query) => through(peopleViaRelay(query)),
66
+ clear: () => known.clear(),
67
+ };
68
+ }
18
69
  export function peopleViaRelay(query) {
19
70
  return async (pubkeys) => {
20
71
  if (pubkeys.length === 0)
@@ -124,6 +175,17 @@ function resolveActions(manifest, kind, folded) {
124
175
  return out;
125
176
  }
126
177
  const tagValue = (e, name) => e.tags.find((t) => t[0] === name)?.[1];
178
+ /**
179
+ * Does the event carry this tag with this value — on **any** of them?
180
+ *
181
+ * `tagValue` reads the first tag with a given name, which is the wrong test for
182
+ * deciding whether an event would have matched a filter: a relay's `#a` matches
183
+ * if *any* `a` tag equals the wanted value, and an event may legitimately carry
184
+ * several. Used where a filter's own predicate is re-applied to a merged
185
+ * response (SHI-13), so that "did this come back because of that filter?" is
186
+ * answered the way the relay answered it.
187
+ */
188
+ const hasTagValue = (e, name, value) => e.tags.some((t) => t[0] === name && t[1] === value);
127
189
  /** A manifest event's `content`, or null when it is not parseable JSON. */
128
190
  function parseManifest(event) {
129
191
  try {
@@ -172,7 +234,54 @@ function webTemplate(event, entity) {
172
234
  }
173
235
  return undefined;
174
236
  }
175
- export async function resolveManifest(pointer, query) {
237
+ /**
238
+ * How long a manifest may be believed without asking again.
239
+ *
240
+ * Five minutes is a compromise with one real cost: republish a manifest and
241
+ * consumers keep drawing the old projection for up to that long. That is
242
+ * recoverable and self-correcting, where the alternative — asking twice per
243
+ * reference per tick, for ever — is neither.
244
+ */
245
+ export const MANIFEST_TTL_MS = 5 * 60_000;
246
+ export function createProjectionCache(ttlMs = MANIFEST_TTL_MS) {
247
+ const entries = new Map();
248
+ return {
249
+ clear: () => entries.clear(),
250
+ lookup(key, now) {
251
+ const found = entries.get(key);
252
+ if (!found)
253
+ return undefined;
254
+ // `>=`, not `>`: a TTL of 0 must mean "never believe it", and an entry
255
+ // exactly at the boundary has expired rather than being on its last tick.
256
+ if (now - found.at >= ttlMs) {
257
+ entries.delete(key);
258
+ return undefined;
259
+ }
260
+ return { value: found.value };
261
+ },
262
+ remember(key, value, now) {
263
+ entries.set(key, { at: now, value });
264
+ },
265
+ };
266
+ }
267
+ /**
268
+ * A negative answer is cached too.
269
+ *
270
+ * "No app claims this kind" costs the same two round trips as a hit and is just
271
+ * as stable. Caching only successes would leave the expensive case — a
272
+ * reference nothing can draw — paying full price on every tick for ever.
273
+ */
274
+ export async function resolveManifest(pointer, query, cache) {
275
+ const key = `${pointer.kind}:${pointer.pubkey}`;
276
+ const now = Date.now();
277
+ const memo = cache?.lookup(key, now);
278
+ if (memo)
279
+ return memo.value;
280
+ const answer = await resolveManifestUncached(pointer, query);
281
+ cache?.remember(key, answer, now);
282
+ return answer;
283
+ }
284
+ async function resolveManifestUncached(pointer, query) {
176
285
  const parse = parseManifest;
177
286
  const addressOf = (event) => `${event.kind}:${event.pubkey}:${tagValue(event, 'd') ?? ''}`;
178
287
  const recommended = await query([
@@ -554,7 +663,9 @@ function buildObject(args) {
554
663
  */
555
664
  export async function resolveForeignEvent(reference, query,
556
665
  /** Defaults to asking the relay. The browser passes a cached lookup. */
557
- lookupPeople) {
666
+ lookupPeople,
667
+ /** See {@link ProjectionCache}. Omitting it is exactly the old behaviour. */
668
+ cache) {
558
669
  let pointer;
559
670
  try {
560
671
  pointer = /^[0-9a-f]{64}$/i.test(reference.replace(/^nostr:/i, ''))
@@ -574,7 +685,7 @@ lookupPeople) {
574
685
  // Whoever signed it is the app's own author, which is what a `#k` lookup
575
686
  // needs when no recommendation exists. The pointer's `author` is a hint and
576
687
  // may disagree with the event; the event wins, because it is the thing.
577
- const resolved = await resolveManifest({ kind: root.kind, pubkey: root.pubkey, identifier: '', relays: pointer.relays }, query);
688
+ const resolved = await resolveManifest({ kind: root.kind, pubkey: root.pubkey, identifier: '', relays: pointer.relays }, query, cache);
578
689
  if (!resolved)
579
690
  return null;
580
691
  const projection = resolved.manifest.projections?.[String(root.kind)];
@@ -603,7 +714,12 @@ lookupPeople,
603
714
  * module leave it at 0; it is the budget that stops a child's own `list`
604
715
  * recursing forever. See `MAX_LIST_DEPTH`.
605
716
  */
606
- depth = 0) {
717
+ depth = 0,
718
+ /**
719
+ * Optional memo for the NIP-89 discovery half. See {@link ProjectionCache} —
720
+ * omitting it is exactly the old behaviour.
721
+ */
722
+ cache) {
607
723
  let pointer;
608
724
  try {
609
725
  // Either form: a body carries `naddr1…`, an `a` tag carries the plain
@@ -614,7 +730,7 @@ depth = 0) {
614
730
  return null;
615
731
  }
616
732
  const address = pointerToAddress(pointer);
617
- const resolved = await resolveManifest(pointer, query);
733
+ const resolved = await resolveManifest(pointer, query, cache);
618
734
  if (!resolved)
619
735
  return null;
620
736
  const { manifest, viaRecommendation } = resolved;
@@ -626,15 +742,29 @@ depth = 0) {
626
742
  if (!projection)
627
743
  return null;
628
744
  const records = foldRuleOf(manifest);
629
- // One round trip for the root, its changes and its comments. `#a` on both the
630
- // change and the comment kind, because both point at the object by *address*
631
- // rather than by event id — which is what makes them survive the author
632
- // replacing the root event.
745
+ /*
746
+ One round trip for the root, its changes, its comments **and its children**.
747
+
748
+ `#a` on both the change and the comment kind, because both point at the
749
+ object by *address* rather than by event id — which is what makes them
750
+ survive the author replacing the root event.
751
+
752
+ The children used to be a second round trip, issued after the root came
753
+ back. They never needed to be: the child filter is built from the manifest
754
+ and the *pointer*, and an addressable event's `d` is `pointer.identifier` by
755
+ definition — it is what we just queried by. So once the manifest is known,
756
+ nothing about the child filter depends on the root's contents (SHI-13).
757
+
758
+ That is the difference between two requests per reference per refresh and
759
+ one, and with the manifest memoised it is the whole cost of a tick.
760
+ */
761
+ const childFilter = childFilterFor({ projection, manifest, pointer, depth });
633
762
  const events = await query([
634
763
  { kinds: [pointer.kind], authors: [pointer.pubkey], '#d': [pointer.identifier], limit: 1 },
635
764
  // Only when the app actually declares a change kind — see `foldRuleOf`.
636
765
  ...(manifest.records ? [{ kinds: [manifest.records.changeKind], '#a': [address], limit: 500 }] : []),
637
766
  { kinds: commentKinds, '#a': [address], limit: 200 },
767
+ ...(childFilter ? [childFilter.filter] : []),
638
768
  ]);
639
769
  const root = events.find((e) => e.kind === pointer.kind && tagValue(e, 'd') === pointer.identifier);
640
770
  if (!root) {
@@ -662,8 +792,19 @@ depth = 0) {
662
792
  };
663
793
  }
664
794
  const folded = foldChanges(events.filter((e) => e.kind === records.changeKind && tagValue(e, records.targetTag) === address), records);
795
+ /*
796
+ Matched on the comment filter's own criteria — the kind **and** the `a` tag.
797
+
798
+ Kind alone was safe while this was its own query: the relay only returned
799
+ what the comment filter asked for. Now that the children ride in the same
800
+ request (SHI-13), a child sharing the comment kind would arrive here too and
801
+ be counted as a comment on its own parent. Peek is exactly that shape: its
802
+ Topic declares `kind:9` messages as children and `kind:9` as its comment
803
+ kind, so every message in the Folder would have become a comment on the
804
+ Topic — a widget silently showing a conversation twice.
805
+ */
665
806
  const comments = events
666
- .filter((e) => commentKinds.includes(e.kind))
807
+ .filter((e) => commentKinds.includes(e.kind) && hasTagValue(e, 'a', address))
667
808
  .sort(byOrder)
668
809
  .map((e) => ({ id: e.id, author: e.pubkey, body: e.content, createdAt: e.created_at }));
669
810
  const object = buildObject({
@@ -691,12 +832,10 @@ depth = 0) {
691
832
  A child may be a regular event with no address of its own (Peek's messages
692
833
  are), so this builds by event rather than by pointer.
693
834
  */
694
- const children = await resolveChildren({
695
- projection,
696
- root,
835
+ const children = childrenFrom({
836
+ events,
837
+ childFilter,
697
838
  manifest,
698
- query,
699
- depth,
700
839
  webTemplate: resolved.webTemplate,
701
840
  viaRecommendation,
702
841
  });
@@ -713,36 +852,69 @@ depth = 0) {
713
852
  return { ...object, people, ...(children ? { children: children.map((c) => ({ ...c, people })) } : {}) };
714
853
  }
715
854
  /**
716
- * Resolve a projection's `list` slot into child objects.
855
+ * The filter for a projection's `list` slot, or nothing.
856
+ *
857
+ * Split out from fetching so it can be built **before** the root event is in
858
+ * hand and merged into the object's own round trip (SHI-13). Everything it
859
+ * needs is in the manifest and the pointer: an addressable event's `d` *is*
860
+ * `pointer.identifier`, since that is what the root filter matches on, so
861
+ * reading it back off the root taught us nothing we did not already know.
717
862
  *
718
- * Returns undefined when no `list` is declared — distinct from `[]`, which
719
- * means "declared, and nothing matched". A renderer needs to tell "this holds
720
- * nothing" from "this holds no list".
863
+ * Returns undefined when no `list` is declared — distinct from a declared list
864
+ * that matches nothing, which a renderer must be able to tell apart. The two
865
+ * other "declared but not renderable" cases are folded in here too, and both
866
+ * come back as `[]` from {@link childrenFrom}: a depth budget already spent,
867
+ * and a child kind the manifest never says how to draw.
721
868
  */
722
- async function resolveChildren(args) {
723
- const { projection, root, manifest, query, depth, webTemplate, viaRecommendation } = args;
869
+ function childFilterFor(args) {
870
+ const { projection, manifest, pointer, depth } = args;
724
871
  const spec = projection.slots.list;
725
872
  const children = !Array.isArray(spec) ? spec?.children : undefined;
726
873
  if (!children)
727
874
  return undefined;
728
875
  // The consumer's budget, not the manifest's — see MAX_LIST_DEPTH.
729
876
  if (depth >= MAX_LIST_DEPTH)
730
- return [];
731
- const childProjection = manifest.projections?.[String(children.kind)];
877
+ return null;
732
878
  // A declared list whose child kind has no projection is not renderable, and
733
879
  // an empty list is the honest answer: the objects exist, this app has not
734
880
  // said how to draw them.
881
+ if (!manifest.projections?.[String(children.kind)])
882
+ return null;
883
+ const parent = children.match === 'identifier'
884
+ ? pointer.identifier
885
+ : pointerToAddress({ ...pointer, relays: [] });
886
+ return {
887
+ filter: { kinds: [children.kind], [`#${children.via}`]: [parent], limit: children.limit ?? 100 },
888
+ kind: children.kind,
889
+ via: children.via,
890
+ parent,
891
+ };
892
+ }
893
+ /**
894
+ * The child objects, picked back out of the merged result set.
895
+ *
896
+ * **Matched on the filter's own criteria, never on kind alone.** Peek's Topic
897
+ * declares `kind:9` messages as its children and `kind:9` as its comment kind,
898
+ * so a merged response carries both under one number and only the tag tells
899
+ * them apart. An event can honestly be both — Ship writes a `kind:9` with an
900
+ * `a` naming the object *and* an `h` naming the Folder — and it appeared in
901
+ * both result sets when these were two queries. Re-applying each filter's own
902
+ * predicate reproduces that, rather than making them compete.
903
+ */
904
+ function childrenFrom(args) {
905
+ const { events, childFilter, manifest, webTemplate, viaRecommendation } = args;
906
+ if (childFilter === undefined)
907
+ return undefined;
908
+ if (childFilter === null)
909
+ return [];
910
+ const childProjection = manifest.projections?.[String(childFilter.kind)];
735
911
  if (!childProjection)
736
912
  return [];
737
- const identifier = tagValue(root, 'd') ?? '';
738
- const parent = children.match === 'identifier'
739
- ? identifier
740
- : pointerToAddress({ kind: root.kind, pubkey: root.pubkey, identifier, relays: [] });
741
- const found = await query([
742
- { kinds: [children.kind], [`#${children.via}`]: [parent], limit: children.limit ?? 100 },
743
- ]);
744
913
  const records = foldRuleOf(manifest);
745
- return found.sort(byOrder).map((event) => buildChildObject({ root: event, manifest, projection: childProjection, records, webTemplate, viaRecommendation }));
914
+ return events
915
+ .filter((e) => e.kind === childFilter.kind && hasTagValue(e, childFilter.via, childFilter.parent))
916
+ .sort(byOrder)
917
+ .map((event) => buildChildObject({ root: event, manifest, projection: childProjection, records, webTemplate, viaRecommendation }));
746
918
  }
747
919
  /**
748
920
  * Build a `ForeignObject` from an event that may have no address of its own.
@@ -996,7 +1168,9 @@ lookupPeople,
996
1168
  * outside this module leave it at 0; it exists so that following a child's
997
1169
  * own `list` is a budget check rather than a thing nobody remembered.
998
1170
  */
999
- depth = 0) {
1171
+ depth = 0,
1172
+ /** See {@link ProjectionCache}. Omitting it is exactly the old behaviour. */
1173
+ cache) {
1000
1174
  // The render loop this forbids is not hypothetical: two folders naming each
1001
1175
  // other's projects resolve forever, and the manifest declaring them is
1002
1176
  // another app's. See MAX_LIST_DEPTH.
@@ -1191,7 +1365,7 @@ depth = 0) {
1191
1365
  const address = pointerToAddress(pointer);
1192
1366
  // 3. Now the authoritative manifest — the object's author gets to say which
1193
1367
  // app renders their project (kind:31989), same as for an inline reference.
1194
- const resolved = await resolveManifest(pointer, query);
1368
+ const resolved = await resolveManifest(pointer, query, cache);
1195
1369
  if (!resolved) {
1196
1370
  return null;
1197
1371
  }