diff --git a/src/common/tiles.js b/src/common/tiles.js index 744815e0..c24cf9e2 100644 --- a/src/common/tiles.js +++ b/src/common/tiles.js @@ -787,10 +787,47 @@ export function tileFromFiles(files) { } /** - * Lifts a legacy facet record that carries a raw `html` string (and possibly a - * `cid`) into a tile facet: the `html` becomes the `/` resource, with its bytes - * stored in `blocks`, and the old `html`/`cid` fields are dropped. This is the - * one-shot migration for facilities stored before the tile schema. Synchronous + * Upgrade a stored facet's `uri` from an old `.html` bundle path to the new + * `.tile` path. Before the tile schema the bundled facets were pointed at loose + * `index.html` files (`diffuse://facets/…/index.html`); the build now packages + * each facet into an `index.tile` CAR at the same directory, so a stale `.html` + * `uri` no longer resolves. This rewrites the `diffuse://` scheme's trailing + * `index.html` to `index.tile`. Only the Diffuse bundle's own facets moved to + * `.tile`, so external (`http(s)://`, `at://`) and other `.html` URIs are left + * untouched. + * + * @param {Record} record + * @returns {Record} + * + * @example Rewrites a stale bundle html path to its tile + * ```js + * import { rewriteLegacyURIPath } from "~/common/tiles.js"; + * + * const upgraded = rewriteLegacyURIPath({ id: "f", name: "x", uri: "diffuse://facets/data/file-manager/index.html" }); + * if (upgraded.uri !== "diffuse://facets/data/file-manager/index.tile") throw new Error("expected the tile uri"); + * const untouched = rewriteLegacyURIPath({ id: "g", name: "y", uri: "https://example.com/facet.html" }); + * if (untouched.uri !== "https://example.com/facet.html") throw new Error("external uris should be left alone"); + * ``` + */ +export function rewriteLegacyURIPath(record) { + const uri = record.uri; + if (typeof uri !== "string") return record; + + const upgraded = uri.replace( + /^diffuse:\/\/(.+)\/index\.html$/i, + "diffuse://$1/index.tile", + ); + return upgraded === uri ? record : { ...record, uri: upgraded }; +} + +/** + * Lifts a legacy facet record to the current tile shape. In addition to + * rewriting any stale `.html` bundle `uri` (see {@link rewriteLegacyURIPath}), a + * record that carries a raw `html` string (and possibly a `cid`) is lifted into + * a tile facet: the `html` becomes the `/` resource, with its bytes stored in + * `blocks`, and the old `html`/`cid` fields are dropped. Already-tile records + * (`resources` present) are left unchanged apart from any uri rewrite. This is + * the one-shot migration for facets stored before the tile schema. Synchronous * because {@link htmlFacetTile} no longer needs async compression. * * @param {Record} record @@ -806,12 +843,21 @@ export function tileFromFiles(files) { * const root = await resolveInlineTile(lifted.resources, lifted.blocks); * if (root?.html !== "

hi

") throw new Error("lifted content should resolve"); * ``` + * + * @example Rewrites a stale uri even for already-tile facets + * ```js + * import { migrateLegacyFacet } from "~/common/tiles.js"; + * + * const upgraded = migrateLegacyFacet({ id: "f", name: "x", uri: "diffuse://facets/data/sources/index.html", resources: { "/": {} }, blocks: {} }); + * if (upgraded.uri !== "diffuse://facets/data/sources/index.tile") throw new Error("stale uri should be upgraded"); + * ``` */ export function migrateLegacyFacet(record) { - const html = record.html; - if (typeof html !== "string" || record.resources) return record; + const upgraded = rewriteLegacyURIPath(record); + const html = upgraded.html; + if (typeof html !== "string" || upgraded.resources) return upgraded; - const { html: _html, cid: _cid, ...rest } = record; + const { html: _html, cid: _cid, ...rest } = upgraded; const tile = htmlFacetTile(html); return { ...rest, resources: tile.resources, blocks: tile.blocks }; } diff --git a/tests/common/self-describing-lenses/test.ts b/tests/common/self-describing-lenses/test.ts index 1ee9bc7f..b194bf54 100644 --- a/tests/common/self-describing-lenses/test.ts +++ b/tests/common/self-describing-lenses/test.ts @@ -21,7 +21,7 @@ import { register, resolve, } from "~/common/lens-registry.js"; -import { resolveInlineTile } from "~/common/tiles.js"; +import { resolveInlineTile, rewriteLegacyURIPath } from "~/common/tiles.js"; // Unit tests for the self-describing envelope + migration/write-back machinery. // These use distinct NSIDs per test so the module-level lens registry does not @@ -116,6 +116,46 @@ describe("common/self-describing + lens", () => { expect(alreadyTile.blocks).toBeDefined(); }); + it("lifts a legacy facet uri from the old .html bundle path to .tile on read", () => { + // A facet stored before the tile conversion points at the loose bundle + // `index.html`; the build now serves an `index.tile` CAR at the same dir. + const envelope = wrap([ + { id: "f1", name: "Old", uri: "diffuse://facets/data/file-manager/index.html" }, + // Already-tile facets also carry a stale uri and must be upgraded. + { id: "f2", name: "Tile", uri: "diffuse://facets/data/sources/index.html", resources: { "/": { src: { $link: "bafk..." } } }, blocks: {} }, + // External uris are not bundle paths and must be left alone. + { id: "f3", name: "Ext", uri: "https://example.com/facet.html" }, + ], { schema: "sh.diffuse.output.facet" }); + const out = migrateEnvelope(envelope, "facets", resolve); + const records = out.data as Array>; + + const lifted = records.find((r) => r.id === "f1")!; + expect(lifted.uri).toBe("diffuse://facets/data/file-manager/index.tile"); + + const tile = records.find((r) => r.id === "f2")!; + expect(tile.uri).toBe("diffuse://facets/data/sources/index.tile"); + expect(tile.resources).toBeDefined(); + + const ext = records.find((r) => r.id === "f3")!; + expect(ext.uri).toBe("https://example.com/facet.html"); + }); + + it("rewriteLegacyURIPath rewrites only stale diffuse bundle paths", () => { + const upgraded = rewriteLegacyURIPath({ + id: "f", name: "x", uri: "diffuse://facets/themes/blur/facet/index.html", + }); + expect(upgraded.uri).toBe("diffuse://facets/themes/blur/facet/index.tile"); + + const untouched = rewriteLegacyURIPath({ + id: "g", name: "y", uri: "https://example.com/facet.html", + }); + expect(untouched.uri).toBe("https://example.com/facet.html"); + + // A missing/untyped uri passes through as-is. + const noUri = rewriteLegacyURIPath({ id: "h", name: "z" }); + expect("uri" in noUri).toBe(false); + }); + it("migrateEnvelope tolerates non-array facets data (no crash, passes through)", () => { // A malformed/legacy stored facets value that is not an array must not // throw (the lift would otherwise call .map on a non-function).