openvibers/openvibe.wiki Commits
Wiki articles resolve their links together (one round trip each in series) (#187)
Changes
@@@ -392,10 +392,12 @@ function createPages({ svc, viewers, platform, config, log = console, limits, vi}// Infobox values of type page link to that page: resolved before the (synchronous) view renders.const infoboxLinks = new Map();++ const infoboxTargets = new Map();for (const x of v.infobox) {-- if (x.type !== 'page' || infoboxLinks.has(x.value)) continue;-- infoboxLinks.set(x.value, await svc.resolveLink(content.parseTarget(x.value, space.slug) || { space: space.slug, slug: '-', title: x.value }, req.actor));++ if (x.type !== 'page' || infoboxTargets.has(x.value)) continue;++ infoboxTargets.set(x.value, content.parseTarget(x.value, space.slug) || { space: space.slug, slug: '-', title: x.value });}++ await Promise.all([...infoboxTargets].map(async ([value, target]) => infoboxLinks.set(value, await svc.resolveLink(target, req.actor))));const [bodyHtml, discussion] = await Promise.all([svc.renderRevision(space, v.revision, req.actor),v.isPublishedRevision ? await discussionFor(space, page, req.actor) : { state: 'not_public' },@@@ -1117,8 +1117,10 @@ function createWikiService({ db, stores, outbox, config, community = null, vip =*/async renderRevision(space, rev, actor, { attachments = null, mediaUrl = null } = {}) {const targets = content.extractLinks(rev.content, space.slug);-- const resolved = new Map();-- for (const t of targets) resolved.set(`${t.space}\u0000${t.slug}`, await svc.resolveLink(t, actor));++ // One lookup per distinct target, all at once: a well-linked article names dozens, and one round trip each++ // in series made /w/:space/:slug wait on every link before it rendered.++ const unique = new Map(targets.map((t) => [`${t.space}\u0000${t.slug}`, t]));++ const resolved = new Map(await Promise.all([...unique].map(async ([key, t]) => [key, await svc.resolveLink(t, actor)])));const byMedia = new Map((attachments || []).map((a) => [a.mediaId, a]));const media = mediaUrl ? (id, caption) => {const a = byMedia.get(id);nnew file mode 100644@@@ -0,0 +1,36 @@++'use strict';++/**++ * An article's [[links]] are resolved before it renders (server/wiki/service.js renderRevision): every distinct target++ * once, together, so a well-linked page does not wait on one database round trip per link. The page still links every++ * page that exists, whatever the case or spelling, and still names a page that does not.++ */++const assert = require('assert');++const H = require('./helpers');++++(async () => {++ const h = await H.boot();++ const me = { kind: 'user', subject: H.subject() };++ try {++ const space = await h.svc.createSpace({ name: 'Links', slug: 'links' }, me);++ const make = async (title, body) => {++ const { page } = await h.svc.createPage(space.id, {++ title, body: `${body} ${H.LONG}`, summary: `${title} in one line.`,++ citations: [{ url: 'https://example.org/', title: 'Example', retrievedAt: '2026-10-01' }],++ }, me);++ await h.svc.publish(page.id, {}, me);++ return page;++ };++ const beta = await make('Beta', 'Beta page.');++ const gamma = await make('Gamma', 'Gamma page.');++ await make('Alpha', 'Alpha links to [[Beta]], [[gamma]] and [[Missing page]].');++++ const r = await H.req(h, 'GET', '/w/links/alpha');++ assert.strictEqual(r.status, 200);++ assert.ok(r.text.includes(`href="/w/links/${beta.slug}"`), 'the link to Beta resolved');++ assert.ok(r.text.includes(`href="/w/links/${gamma.slug}"`), 'the lower-case link to Gamma resolved to the same page');++ assert.ok(r.text.includes('Missing page'), 'a page that does not exist is still named');++ console.log('article links ok');++ } finally {++ await h.stop();++ }++})().catch((e) => { console.error(e); process.exit(1); });
@@ -392,10 +392,12 @@ function createPages({ svc, viewers, platform, config, log = console, limits, vi
@@ -392,10 +392,12 @@ function createPages({ svc, viewers, platform, config, log = console, limits, vi
}
}
// Infobox values of type page link to that page: resolved before the (synchronous) view renders.
// Infobox values of type page link to that page: resolved before the (synchronous) view renders.
const infoboxLinks = new Map();
const infoboxLinks = new Map();
+ const infoboxTargets = new Map();
for (const x of v.infobox) {
for (const x of v.infobox) {
- if (x.type !== 'page' || infoboxLinks.has(x.value)) continue;
- infoboxLinks.set(x.value, await svc.resolveLink(content.parseTarget(x.value, space.slug) || { space: space.slug, slug: '-', title: x.value }, req.actor));
+ if (x.type !== 'page' || infoboxTargets.has(x.value)) continue;
+ infoboxTargets.set(x.value, content.parseTarget(x.value, space.slug) || { space: space.slug, slug: '-', title: x.value });
}
}
+ await Promise.all([...infoboxTargets].map(async ([value, target]) => infoboxLinks.set(value, await svc.resolveLink(target, req.actor))));
const [bodyHtml, discussion] = await Promise.all([
const [bodyHtml, discussion] = await Promise.all([
svc.renderRevision(space, v.revision, req.actor),
svc.renderRevision(space, v.revision, req.actor),
v.isPublishedRevision ? await discussionFor(space, page, req.actor) : { state: 'not_public' },
v.isPublishedRevision ? await discussionFor(space, page, req.actor) : { state: 'not_public' },
@@ -1117,8 +1117,10 @@ function createWikiService({ db, stores, outbox, config, community = null, vip =
@@ -1117,8 +1117,10 @@ function createWikiService({ db, stores, outbox, config, community = null, vip =
*/
*/
async renderRevision(space, rev, actor, { attachments = null, mediaUrl = null } = {}) {
async renderRevision(space, rev, actor, { attachments = null, mediaUrl = null } = {}) {
const targets = content.extractLinks(rev.content, space.slug);
const targets = content.extractLinks(rev.content, space.slug);
- const resolved = new Map();
- for (const t of targets) resolved.set(`${t.space}\u0000${t.slug}`, await svc.resolveLink(t, actor));
+ // One lookup per distinct target, all at once: a well-linked article names dozens, and one round trip each
+ // in series made /w/:space/:slug wait on every link before it rendered.
+ const unique = new Map(targets.map((t) => [`${t.space}\u0000${t.slug}`, t]));
+ const resolved = new Map(await Promise.all([...unique].map(async ([key, t]) => [key, await svc.resolveLink(t, actor)])));
const byMedia = new Map((attachments || []).map((a) => [a.mediaId, a]));
const byMedia = new Map((attachments || []).map((a) => [a.mediaId, a]));
const media = mediaUrl ? (id, caption) => {
const media = mediaUrl ? (id, caption) => {
const a = byMedia.get(id);
const a = byMedia.get(id);
new file mode 100644
new file mode 100644
@@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
+'use strict';
+/**
+ * An article's [[links]] are resolved before it renders (server/wiki/service.js renderRevision): every distinct target
+ * once, together, so a well-linked page does not wait on one database round trip per link. The page still links every
+ * page that exists, whatever the case or spelling, and still names a page that does not.
+ */
+const assert = require('assert');
+const H = require('./helpers');
+
+(async () => {
+ const h = await H.boot();
+ const me = { kind: 'user', subject: H.subject() };
+ try {
+ const space = await h.svc.createSpace({ name: 'Links', slug: 'links' }, me);
+ const make = async (title, body) => {
+ const { page } = await h.svc.createPage(space.id, {
+ title, body: `${body} ${H.LONG}`, summary: `${title} in one line.`,
+ citations: [{ url: 'https://example.org/', title: 'Example', retrievedAt: '2026-10-01' }],
+ }, me);
+ await h.svc.publish(page.id, {}, me);
+ return page;
+ };
+ const beta = await make('Beta', 'Beta page.');
+ const gamma = await make('Gamma', 'Gamma page.');
+ await make('Alpha', 'Alpha links to [[Beta]], [[gamma]] and [[Missing page]].');
+
+ const r = await H.req(h, 'GET', '/w/links/alpha');
+ assert.strictEqual(r.status, 200);
+ assert.ok(r.text.includes(`href="/w/links/${beta.slug}"`), 'the link to Beta resolved');
+ assert.ok(r.text.includes(`href="/w/links/${gamma.slug}"`), 'the lower-case link to Gamma resolved to the same page');
+ assert.ok(r.text.includes('Missing page'), 'a page that does not exist is still named');
+ console.log('article links ok');
+ } finally {
+ await h.stop();
+ }
+})().catch((e) => { console.error(e); process.exit(1); });