diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts
index 2371e809fc..811efbcf59 100644
--- a/packages/base/card-api.gts
+++ b/packages/base/card-api.gts
@@ -4787,18 +4787,31 @@ function getStore(instance: BaseDef): CardStore {
return stores.get(instance as BaseDef) ?? new FallbackCardStore();
}
-// The VirtualNetwork associated with an instance's store, for prefix/RRI
-// resolution outside this module. Returns undefined when the instance is
-// detached (no store, no loader-attached VN) — callers handle that by
-// degrading to URL math or throwing.
-export function virtualNetworkFor(
- instance: BaseDef,
-): VirtualNetwork | undefined {
+// Resolve a (possibly relative or RRI) reference to a real, fetchable URL,
+// relative to the instance's own location. Card definitions that must hand a
+// real URL to a boundary that can't consume canonical RRI (an `
`, the
+// AI source-file reader's `new URL(...)`) use this rather than reaching for the
+// VirtualNetwork object directly — they get back a URL, not the network itself.
+// Resolves through the active Loader's VirtualNetwork (the network boundary);
+// returns undefined when none is available (detached / static-parse contexts)
+// so callers can degrade to URL math.
+export function resolveInstanceURL(
+ instance: CardDef,
+ reference: string,
+): URL | undefined {
+ let virtualNetwork: VirtualNetwork | undefined;
try {
- return getStore(instance).virtualNetwork;
+ virtualNetwork = myLoader().getVirtualNetwork();
} catch {
return undefined;
}
+ if (!virtualNetwork) {
+ return undefined;
+ }
+ return virtualNetwork.resolveURL(
+ reference,
+ instance.id ?? instance[relativeTo],
+ );
}
// Resolve a (possibly relative) reference to its absolute canonical RRI,
diff --git a/packages/base/spec.gts b/packages/base/spec.gts
index c890852b96..3e1d8dafb8 100644
--- a/packages/base/spec.gts
+++ b/packages/base/spec.gts
@@ -3,12 +3,11 @@ import {
field,
Component,
CardDef,
- relativeTo,
linksToMany,
FieldDef,
containsMany,
getCardMeta,
- virtualNetworkFor,
+ resolveInstanceURL,
type CardOrFieldTypeIcon,
BaseDef,
type CardContext,
@@ -81,23 +80,20 @@ class PopulateFieldSpecExampleCommand extends PopulateWithSampleDataCommand {
if (!codeRef) {
return [];
}
- // The attached-file identifiers are read as fetchable source URLs (the AI
- // source-file reader does `new URL(...)`), so this must resolve to a real
- // URL — keep the VirtualNetwork here (a scoped RRI can't be fetched).
- let vn = virtualNetworkFor(card);
- if (!vn) {
- return [];
- }
+ // The attached-file identifier is read as a fetchable source URL (the AI
+ // source-file reader does `new URL(...)`), so the card's type module must
+ // resolve to a real URL — a scoped RRI can't be fetched.
codeRef = codeRefWithAbsoluteIdentifier(
codeRef,
- vn.toURL(card.id!),
+ card.id,
undefined,
- vn,
)! as ResolvedCodeRef;
- let cardOrFieldModuleURL = codeRef.module
- ? ensureExtension(codeRef.module, { default: '.gts' })
+ let moduleURL = codeRef.module
+ ? resolveInstanceURL(card, codeRef.module)
: undefined;
- return cardOrFieldModuleURL ? [cardOrFieldModuleURL] : [];
+ return moduleURL
+ ? [ensureExtension(moduleURL.href, { default: '.gts' })]
+ : [];
}
}
@@ -941,13 +937,9 @@ export class Spec extends CardDef {
}
// `moduleHref` is consumed as a fetchable / absolute URL (source reader's
// `new URL(...)`, and URL-form comparisons in the code submode), so it
- // must resolve to a real URL — keep the VirtualNetwork here (RRI space
- // would leave a scoped prefix that those readers can't use).
- let vn = virtualNetworkFor(this);
- if (!vn) {
- return undefined;
- }
- return vn.resolveURL(this.ref.module, this.id ?? this[relativeTo]).href;
+ // must resolve to a real URL — RRI space would leave a scoped prefix that
+ // those readers can't use.
+ return resolveInstanceURL(this, this.ref.module)?.href;
},
});
@field linkedExamples = linksToMany(CardDef);
diff --git a/packages/experiments-realm/asset.gts b/packages/experiments-realm/asset.gts
index e44521a8c8..ac0650c16d 100644
--- a/packages/experiments-realm/asset.gts
+++ b/packages/experiments-realm/asset.gts
@@ -4,8 +4,7 @@ import {
CardDef,
FieldDef,
Component,
- relativeTo,
- virtualNetworkFor,
+ resolveInstanceURL,
} from 'https://cardstack.com/base/card-api';
import StringField from 'https://cardstack.com/base/string';
import CurrencyIcon from '@cardstack/boxel-icons/currency';
@@ -21,19 +20,13 @@ export class Asset extends CardDef {
if (!this.logoURL) {
return null;
}
- let rel = this[relativeTo] || this.id;
- // The instance may have no store-attached VirtualNetwork (detached
- // / static-parse contexts), and `rel` may be a prefix-form RRI
- // (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse on
- // its own. If we can't resolve a base, return `logoURL` raw so the
- //
binding still has a string to render rather than
- // letting the compute throw.
+ // `logoURL` may be relative to the instance, which lives at a prefix-form
+ // RRI (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse.
+ // Resolve it against the instance's real URL for the
; if no
+ // VirtualNetwork is available (detached / static-parse), fall back to the
+ // raw value.
try {
- let base =
- typeof rel === 'string'
- ? (virtualNetworkFor(this)?.toURL(rel) ?? new URL(rel))
- : rel;
- return new URL(this.logoURL, base).href;
+ return resolveInstanceURL(this, this.logoURL)?.href ?? this.logoURL;
} catch {
return this.logoURL;
}
@@ -105,19 +98,13 @@ class AssetField extends FieldDef {
if (!this.logoURL) {
return null;
}
- let rel = this[relativeTo] || this.id;
- // The instance may have no store-attached VirtualNetwork (detached
- // / static-parse contexts), and `rel` may be a prefix-form RRI
- // (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse on
- // its own. If we can't resolve a base, return `logoURL` raw so the
- //
binding still has a string to render rather than
- // letting the compute throw.
+ // `logoURL` may be relative to the instance, which lives at a prefix-form
+ // RRI (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse.
+ // Resolve it against the instance's real URL for the
; if no
+ // VirtualNetwork is available (detached / static-parse), fall back to the
+ // raw value.
try {
- let base =
- typeof rel === 'string'
- ? (virtualNetworkFor(this)?.toURL(rel) ?? new URL(rel))
- : rel;
- return new URL(this.logoURL, base).href;
+ return resolveInstanceURL(this, this.logoURL)?.href ?? this.logoURL;
} catch {
return this.logoURL;
}