Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 12 additions & 48 deletions packages/base/card-api.gts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import {
runtimeQueryDependencyContext,
type RuntimeDependencyTrackingContext,
rri,
resolveRRIReference,
type RealmResourceIdentifier,
type VirtualNetwork,
isDirectIndexedFieldKey,
Expand Down Expand Up @@ -1433,7 +1434,7 @@ class LinksTo<CardT extends LinkableDefConstructor> implements Field<CardT> {
if (reference == null || reference === '') {
return null;
}
let href = resolveRef(store.virtualNetwork, reference, relativeTo);
let href = resolveRef(reference, relativeTo);
let cachedInstance = isFileDef(this.card)
? store.getFileMeta(href)
: store.getCard(href);
Expand Down Expand Up @@ -2006,11 +2007,7 @@ class LinksToMany<FieldT extends LinkableDefConstructor> implements Field<
if (reference == null) {
return null;
}
let normalizedReference = resolveRef(
store.virtualNetwork,
reference,
relativeTo,
);
let normalizedReference = resolveRef(reference, relativeTo);
let cachedInstance = isFileDef(this.card)
? store.getFileMeta(normalizedReference)
: store.getCard(normalizedReference);
Expand Down Expand Up @@ -2463,11 +2460,7 @@ export class BaseDef {
if (!value[relativeTo]) {
return maybeRelativeReference;
}
return resolveRef(
getStore(value).virtualNetwork,
maybeRelativeReference,
value[relativeTo],
);
return resolveRef(maybeRelativeReference, value[relativeTo]);
}
return Object.fromEntries(
Object.entries(
Expand Down Expand Up @@ -2498,11 +2491,7 @@ export class BaseDef {
if (isNonPresentLink(rawValue)) {
let normalizedId = rawValue.reference;
if (value[relativeTo]) {
normalizedId = resolveRef(
getStore(value).virtualNetwork,
normalizedId,
value[relativeTo],
);
normalizedId = resolveRef(normalizedId, value[relativeTo]);
}
return [fieldName, { id: makeAbsoluteURL(rawValue.reference) }];
}
Expand Down Expand Up @@ -3472,11 +3461,7 @@ function lazilyLoadLink(
inflightLinkLoads.set(instance, inflightLoads);
}
let store = getStore(instance);
let reference = resolveRef(
store.virtualNetwork,
link,
instance.id ?? instance[relativeTo],
);
let reference = resolveRef(link, instance.id ?? instance[relativeTo]);
let key = `${field.name}/${reference}`;
let promise = inflightLoads.get(key);
if (promise) {
Expand Down Expand Up @@ -3592,7 +3577,6 @@ function lazilyLoadLink(
continue;
}
let notLoadedRef = resolveRef(
store.virtualNetwork,
item.reference,
instance.id ?? instance[relativeTo],
);
Expand Down Expand Up @@ -3680,7 +3664,6 @@ function lazilyLoadLink(
continue;
}
let notLoadedRef = resolveRef(
store.virtualNetwork,
item.reference,
instance.id ?? instance[relativeTo],
);
Expand Down Expand Up @@ -4819,35 +4802,16 @@ export function virtualNetworkFor(
}
}

// Resolve a (possibly prefix-form or relative) reference to an absolute URL
// string through the supplied VirtualNetwork. When the caller can't supply
// one (test stubs, detached instances), fall back to plain URL math: it
// covers URL-form refs and relative refs against URL-form bases. Prefix-form
// refs and refs against prefix-form bases can't be resolved without a VN —
// `new URL()` throws on those, so we return the raw reference unchanged
// instead of bubbling the error to callers (e.g. relationship deserialize
// uses the returned string as a "did this resolve?" signal).
// Resolve a (possibly relative) reference to its absolute canonical RRI,
// relative to `relativeTo`. Identifiers are canonical RRI by the time they
// reach here, so this is pure form-preserving path math (see
// `resolveRRIReference`) — no VirtualNetwork, no realm-mapping lookup. The
// returned string is used as an opaque store key / "did this resolve?" signal.
function resolveRef(
virtualNetwork: VirtualNetwork | undefined,
reference: string,
relativeTo: RealmResourceIdentifier | URL | undefined,
): string {
if (virtualNetwork) {
return virtualNetwork.resolveURL(reference, relativeTo).href;
}
let base: URL | string | undefined;
if (relativeTo instanceof URL) {
base = relativeTo;
} else if (typeof relativeTo === 'string') {
if (relativeTo.startsWith('http://') || relativeTo.startsWith('https://')) {
base = relativeTo;
}
}
try {
return new URL(reference, base).href;
} catch {
return reference;
}
return resolveRRIReference(reference, relativeTo);
}

function myLoader(): Loader {
Expand Down
64 changes: 27 additions & 37 deletions packages/base/card-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
} from '@cardstack/runtime-common';
import type { BaseDef, BaseDefConstructor, CardDef } from './card-api';
import type { FileDef } from './file-api';
import type { ResourceID, VirtualNetwork } from '@cardstack/runtime-common';
import type { ResourceID } from '@cardstack/runtime-common';

// --- Runtime Imports ---

Expand All @@ -35,10 +35,10 @@ import {
loadCardDef,
localId,
maybeRelativeReference,
maybeURL,
meta,
primitive,
relativeTo,
resolveRRIReference,
rri,
} from '@cardstack/runtime-common';
import { getFieldOverrides, getFields, serializedGet } from './field-support';
Expand Down Expand Up @@ -72,9 +72,6 @@ export interface SerializeOpts {
omitQueryFields?: boolean;
maybeRelativeReference?: (possibleReference: string) => string;
overrides?: Map<string, typeof BaseDef>;
// The VirtualNetwork to consult for prefix/RRI resolution during
// serialization. Required — every caller must thread a VN.
virtualNetwork: VirtualNetwork;
}

export interface DeserializeOpts {
Expand Down Expand Up @@ -220,32 +217,25 @@ export function serializeCard(
};
let modelRelativeTo: RealmResourceIdentifier | URL | undefined =
model.id ?? model[relativeTo];
let vn = opts.virtualNetwork;
let data = serializeCardResource(model, doc, {
...opts,
...{
maybeRelativeReference(possibleReference: string) {
// Registered prefix refs (e.g. @cardstack/catalog/foo) are already
// in their canonical portable form — return as-is.
if (vn.isRegisteredPrefix(possibleReference)) {
// Prefix-form RRIs (e.g. @cardstack/catalog/foo) are already in their
// canonical portable form — return as-is.
if (possibleReference.startsWith('@')) {
return possibleReference;
}
let modelRelativeToForURL: URL | undefined =
typeof modelRelativeTo === 'string'
? vn.toURL(modelRelativeTo)
: modelRelativeTo;
let url = maybeURL(possibleReference, modelRelativeToForURL);
if (!url) {
throw new Error(
`could not determine url from '${possibleReference}' relative to ${modelRelativeTo}`,
);
}
// Identifiers are canonical RRI, so resolve relative refs to their
// absolute form with plain path math (no VirtualNetwork), then
// relativize against the model's own id.
let absolute = resolveRRIReference(possibleReference, modelRelativeTo);
if (!modelRelativeTo) {
return url.href;
return absolute;
}
const realmURLString = getCardMeta(model, 'realmURL');
const realmURL = realmURLString ? new URL(realmURLString) : undefined;
return maybeRelativeReference(url, modelRelativeTo, realmURL);
return maybeRelativeReference(rri(absolute), modelRelativeTo, realmURL);
},
},
});
Expand Down Expand Up @@ -325,35 +315,35 @@ export function serializeFileDef(
};
let modelRelativeTo: RealmResourceIdentifier | URL | undefined =
model.id ?? model[relativeTo];
let vn = opts.virtualNetwork;
let data = serializeCardResource(
model,
doc,
{
...opts,
...{
maybeRelativeReference(possibleReference: string) {
// Registered prefix refs (e.g. @cardstack/catalog/foo) are
// already in their canonical portable form — return as-is.
if (vn.isRegisteredPrefix(possibleReference)) {
// Prefix-form RRIs (e.g. @cardstack/catalog/foo) are already in
// their canonical portable form — return as-is.
if (possibleReference.startsWith('@')) {
return possibleReference;
}
let modelRelativeToForURL: URL | undefined =
typeof modelRelativeTo === 'string'
? vn.toURL(modelRelativeTo)
: modelRelativeTo;
let url = maybeURL(possibleReference, modelRelativeToForURL);
if (!url) {
throw new Error(
`could not determine url from '${possibleReference}' relative to ${modelRelativeTo}`,
);
}
// Identifiers are canonical RRI, so resolve relative refs to their
// absolute form with plain path math (no VirtualNetwork), then
// relativize against the model's own id.
let absolute = resolveRRIReference(
possibleReference,
modelRelativeTo,
);
if (!modelRelativeTo) {
return url.href;
return absolute;
}
const realmURLString = getCardMeta(model, 'realmURL');
const realmURL = realmURLString ? new URL(realmURLString) : undefined;
return maybeRelativeReference(url, modelRelativeTo, realmURL);
return maybeRelativeReference(
rri(absolute),
modelRelativeTo,
realmURL,
);
},
},
},
Expand Down
8 changes: 1 addition & 7 deletions packages/host/app/commands/copy-and-edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,7 @@ export default class CopyAndEditCommand extends HostBaseCommand<
fieldName: string,
): string | undefined {
try {
let vn = this.loaderService.loader.getVirtualNetwork();
if (!vn) {
return undefined;
}
let serialized = this.#cardAPI?.serializeCard(card, {
virtualNetwork: vn,
});
let serialized = this.#cardAPI?.serializeCard(card, {});
let relationships = (serialized?.data as any)?.relationships ?? {};
return Object.keys(relationships).find(
(key) => key === fieldName || key.endsWith(`.${fieldName}`),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,7 @@ class SpecPreviewContent extends GlimmerComponent<ContentSignature> {
}

@action private async viewSpecInstance() {
if (
!this.selectedId ||
isLocalId(this.selectedId, this.network.virtualNetwork)
) {
if (!this.selectedId || isLocalId(this.selectedId)) {
return;
}

Expand Down
29 changes: 9 additions & 20 deletions packages/host/app/lib/gc-card-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,8 @@ export default class CardStoreWithGarbageCollection implements CardStore {
this.deleteFileMeta(id);
return;
}
let localId = isLocalId(id, this.#virtualNetwork) ? id : undefined;
let remoteId = !isLocalId(id, this.#virtualNetwork) ? id : undefined;
let localId = isLocalId(id) ? id : undefined;
let remoteId = !isLocalId(id) ? id : undefined;

if (localId) {
let remoteIds = this.#idResolver.getRemoteIds(localId);
Expand Down Expand Up @@ -792,7 +792,7 @@ export default class CardStoreWithGarbageCollection implements CardStore {
id = id.replace(/\.json$/, '');
let { item, localId } = this.tryFindingCardItem(type, id);

if (!item && isLocalId(id, this.#virtualNetwork)) {
if (!item && isLocalId(id)) {
let maybeRemoteId = this.#idResolver.findRemoteId(id);
if (maybeRemoteId) {
({ item, localId } = this.tryFindingCardItem(type, maybeRemoteId));
Expand Down Expand Up @@ -846,12 +846,8 @@ export default class CardStoreWithGarbageCollection implements CardStore {
type === 'instance'
? this.#nonTrackedCardInstances
: this.#nonTrackedCardInstanceErrors;
let localId = isLocalId(localOrRemoteId, this.#virtualNetwork)
? localOrRemoteId
: undefined;
let remoteId = !isLocalId(localOrRemoteId, this.#virtualNetwork)
? localOrRemoteId
: undefined;
let localId = isLocalId(localOrRemoteId) ? localOrRemoteId : undefined;
let remoteId = !isLocalId(localOrRemoteId) ? localOrRemoteId : undefined;
let item: CardDef | CardErrorJSONAPI | undefined;
if (remoteId) {
if (localId) {
Expand Down Expand Up @@ -897,7 +893,7 @@ export default class CardStoreWithGarbageCollection implements CardStore {
let errorBucket = notTracked
? this.#nonTrackedCardInstanceErrors
: this.#cardInstanceErrors;
let isRemoteId = !isLocalId(id, this.#virtualNetwork);
let isRemoteId = !isLocalId(id);
if (isRemoteId) {
if (isCardInstance(item)) {
this.#idResolver.addIdPair(item[localIdSymbol], id);
Expand All @@ -913,15 +909,10 @@ export default class CardStoreWithGarbageCollection implements CardStore {
}
let instance = isCardInstance(item) ? item : undefined;
let error = !isCardInstance(item) ? item : undefined;
if (
error &&
isRemoteId &&
error.id &&
isLocalId(error.id, this.#virtualNetwork)
) {
if (error && isRemoteId && error.id && isLocalId(error.id)) {
this.#idResolver.addIdPair(error.id, id);
}
let localId = isLocalId(id, this.#virtualNetwork) ? id : undefined;
let localId = isLocalId(id) ? id : undefined;
let remoteIds = isRemoteId ? [id] : [];
if (localId) {
remoteIds = this.#idResolver.getRemoteIds(localId);
Expand Down Expand Up @@ -996,9 +987,7 @@ export default class CardStoreWithGarbageCollection implements CardStore {

private hasReferences(id: string): boolean {
let idsToCheck = new Set<string>([id]);
let localId = isLocalId(id, this.#virtualNetwork)
? id
: this.#idResolver.getLocalId(id);
let localId = isLocalId(id) ? id : this.#idResolver.getLocalId(id);
if (localId) {
idsToCheck.add(localId);
for (let remoteId of this.#idResolver.getRemoteIds(localId)) {
Expand Down
1 change: 0 additions & 1 deletion packages/host/app/routes/render/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export default class RenderMetaRoute extends Route<Model> {
// relationships, so omit query fields here (the relationship data is
// stripped below regardless).
omitQueryFields: true,
virtualNetwork: vn,
maybeRelativeReference: (reference: string) =>
maybeRelativeReference(
vn.toURL(reference),
Expand Down
3 changes: 1 addition & 2 deletions packages/host/app/services/card-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,10 @@ export default class CardService extends Service {

async serializeCard(
card: CardDef,
opts?: Omit<SerializeOpts, 'virtualNetwork'> & { withIncluded?: true },
opts?: SerializeOpts & { withIncluded?: true },
): Promise<LooseSingleCardDocument> {
let api = await this.getAPI();
let serialized = api.serializeCard(card, {
virtualNetwork: this.network.virtualNetwork,
...opts,
});
if (!opts?.withIncluded) {
Expand Down
Loading
Loading