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
Binary file added docs/tessar-10x-performance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2,947 changes: 2,947 additions & 0 deletions docs/tessar-10x-performance.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4,412 changes: 4,412 additions & 0 deletions docs/tessar-benchmark-results.json

Large diffs are not rendered by default.

262 changes: 262 additions & 0 deletions docs/tessar-performance-report.md

Large diffs are not rendered by default.

477 changes: 477 additions & 0 deletions docs/tessar-query-materialization-plan.md

Large diffs are not rendered by default.

305 changes: 301 additions & 4 deletions packages/base/card-api.gts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions packages/base/card-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ export interface SerializeOpts {
export interface DeserializeOpts {
ignoreBrokenLinks?: true;
dependencyTrackingContext?: RuntimeDependencyTrackingContext;
// Internal Tessar read mode, supplied only after the caller verifies server
// provenance/completeness. Never inferred from authored attributes. Contained
// paths use dots and '*' for containsMany entries; links remain lazy.
tessarSnapshot?: {
computedFields: string[];
queryFields: string[];
scope?: { active: boolean };
};
}

// --- Serialization Symbols ---
Expand Down
72 changes: 72 additions & 0 deletions packages/base/field-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,74 @@ const deserializedData = initSharedState(
'deserializedData',
() => new WeakMap<BaseDef, Map<string, any>>(),
);

// Tessar's reader overlay is separate from authored/default values. The caller
// must validate the indexed revision before requesting snapshot deserialization.
// A contained edit leaves snapshot mode for the entire owner graph.
export interface TessarSnapshotScope {
active: boolean;
pending?: boolean;
}
const tessarSnapshots = initSharedState(
'tessarSnapshots',
() =>
new WeakMap<
BaseDef,
{
scope: TessarSnapshotScope;
values: Map<string, unknown>;
queryFields: Set<string>;
}
>(),
);

export function setTessarSnapshot(
instance: BaseDef,
snapshot?: {
scope: TessarSnapshotScope;
values: Map<string, unknown>;
queryFields: Set<string>;
},
): void {
let previous = tessarSnapshots.get(instance);
if (previous && previous.scope !== snapshot?.scope)
previous.scope.active = false;
if (snapshot) tessarSnapshots.set(instance, snapshot);
else tessarSnapshots.delete(instance);
}

export function leaveTessarSnapshot(instance: BaseDef): void {
let snapshot = tessarSnapshots.get(instance);
if (snapshot) snapshot.scope.active = false;
}

export function hasTessarSnapshot(instance: BaseDef): boolean {
return Boolean(tessarSnapshots.get(instance)?.scope.active);
}

export function tessarSnapshotState(
instance: BaseDef,
): 'live' | 'ready' | 'pending' {
entangleWithCardTracking(instance);
let scope = tessarSnapshots.get(instance)?.scope;
return scope?.active ? (scope.pending ? 'pending' : 'ready') : 'live';
}

export function markTessarPending(instance: BaseDef): void {
let scope = tessarSnapshots.get(instance)?.scope;
if (scope?.active && !scope.pending) {
scope.pending = true;
notifyCardTracking(instance);
}
}

export function hasTessarQueryMembership(
instance: BaseDef,
fieldName: string,
): boolean {
let snapshot = tessarSnapshots.get(instance);
return Boolean(snapshot?.scope.active && snapshot.queryFields.has(fieldName));
}
// Cache for resolved field configurations per instance/field
const fieldConfigurationCache = initSharedState(
'fieldConfigurationCache',
Expand Down Expand Up @@ -162,6 +230,10 @@ export function getter<CardT extends BaseDefConstructor>(
cardTracking.get(instance);

if (field.computeVia) {
let snapshot = tessarSnapshots.get(instance);
if (snapshot?.scope.active && snapshot.values.has(field.name)) {
return snapshot.values.get(field.name) as BaseInstanceType<CardT>;
}
// Fast path when no pass is open: skip the counter + memo entirely
// so production reads pay only one branch on the module-local null
// check. JIT branch-predicts this and the original behaviour is
Expand Down
30 changes: 30 additions & 0 deletions packages/base/query-field-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,36 @@ function resolveInstancePathValue(instance: BaseDef, path: string): any {
return current;
}

export function tessarQueryWatch(
store: CardStore,
instance: BaseDef,
field: Field,
) {
let definition = buildFieldDefinition(field);
if (!definition)
throw new Error(`Tessar cannot resolve query field '${field.name}'`);
let normalized = resolveQueryAndRealm(store, instance, field, definition);
if (!normalized)
throw new Error(
`Tessar cannot resolve query parameters for '${field.name}'`,
);
let realm = (instance as any)[realmURLSymbol] as URL | undefined;
if (
!realm ||
normalized.realmHrefs.length !== 1 ||
normalized.realmHrefs[0] !== realm.href
) {
throw new Error(
'Tessar materialization currently requires same-realm queries',
);
}
return {
fieldPath: field.name,
query: normalized.query,
searchURL: normalized.searchURL,
};
}

function buildFieldDefinition(field: Field): FieldDefinition | undefined {
let ref = identifyCard(field.card);
if (!ref) {
Expand Down
4 changes: 4 additions & 0 deletions packages/host/app/components/card-prerender.gts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ export default class CardPrerender extends Component {
): RenderRouteOptions => {
let out: RenderRouteOptions = {
...baseOptions,
tessarUseSnapshot:
visitType === 'prerender-html' && pass === 'cardRender'
? true
: undefined,
// The fused index pass carries both flags — the render route
// serves it from the card branch and folds the file extract into
// the render.meta payload.
Expand Down
19 changes: 15 additions & 4 deletions packages/host/app/lib/gc-card-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
type SingleFileMetaDocument,
type VirtualNetwork,
} from '@cardstack/runtime-common';
import { currentTessarInputSnapshot } from '@cardstack/runtime-common/tessar-materialization';

import type {
BaseDef,
Expand Down Expand Up @@ -131,9 +132,14 @@ function currentRenderScope(): string | undefined {
if (g.__boxelRenderContext !== true || typeof g.__boxelJobId !== 'string') {
return undefined;
}
return typeof g.__boxelRenderScope === 'string'
? g.__boxelRenderScope
: g.__boxelJobId;
let scope =
typeof g.__boxelRenderScope === 'string'
? g.__boxelRenderScope
: g.__boxelJobId;
let tessar = currentTessarInputSnapshot();
return tessar
? `${scope}:tessar:${tessar.realmURL}:${tessar.generation}`
: scope;
}

// we use this 2 way mapping between local ID and remote ID because if we end up
Expand Down Expand Up @@ -570,7 +576,12 @@ export default class CardStoreWithGarbageCollection implements CardStore {
}
return await promise;
}
promise = loadCardDocument(this.#fetch, url, this.#virtualNetwork);
promise = loadCardDocument(
this.#fetch,
url,
this.#virtualNetwork,
currentTessarInputSnapshot(),
);
// Held locally as well as in the map: a scope boundary clears the map
// mid-flight, so reading it back in the `finally` would time this load
// against a newer load's start — or find nothing and drop the entry.
Expand Down
14 changes: 13 additions & 1 deletion packages/host/app/lib/prerender-fetch-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {
X_BOXEL_JOB_ID_HEADER,
X_BOXEL_LOGGING_CORRELATION_ID_HEADER,
} from '@cardstack/runtime-common';
import {
currentTessarInputSnapshot,
TESSAR_INPUT_GENERATION_HEADER,
} from '@cardstack/runtime-common/tessar-materialization';

// Set by the prerender server's `evaluateOnNewDocument` before the
// SPA boots, and also by the host's prerender-shaped routes
Expand All @@ -16,7 +20,15 @@ import {
export function duringPrerenderHeaders(): Record<string, string> {
let flag = (globalThis as unknown as { __boxelRenderContext?: boolean })
.__boxelRenderContext;
return flag === true ? { [DURING_PRERENDER_HEADER]: '1' } : {};
let tessar = currentTessarInputSnapshot();
return flag === true
? {
[DURING_PRERENDER_HEADER]: '1',
...(tessar
? { [TESSAR_INPUT_GENERATION_HEADER]: String(tessar.generation) }
: {}),
}
: {};
}

// The same marker, for a card write rather than a search. Gated on the
Expand Down
70 changes: 63 additions & 7 deletions packages/host/app/routes/render.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type Controller from '@ember/controller';

import { registerDestructor } from '@ember/destroyable';
import { action } from '@ember/object';
import Route from '@ember/routing/route';
Expand Down Expand Up @@ -38,6 +39,10 @@ import {
coerceErrorMessage,
serializableError,
} from '@cardstack/runtime-common/error';
import {
currentTessarInputSnapshot,
TESSAR_INPUT_GENERATION_HEADER,
} from '@cardstack/runtime-common/tessar-materialization';

import {
windowErrorHandler,
Expand Down Expand Up @@ -559,13 +564,23 @@ export default class RenderRoute extends Route<Model> {
(globalThis as any).__renderModel = undefined;

(globalThis as any).__boxelSetRenderStage?.('buildModel:fetching-source');
let tessarInput = currentTessarInputSnapshot();
let response: Response;
try {
response = await this.#authGuard.race(() =>
this.network.authedFetch(id, {
this.network.authedFetch(tessarInput ? id.replace(/\.json$/, '') : id, {
method: 'GET',
headers: {
Accept: SupportedMimeType.CardSource,
Accept: tessarInput
? SupportedMimeType.CardJson
: SupportedMimeType.CardSource,
...(tessarInput
? {
[TESSAR_INPUT_GENERATION_HEADER]: String(
tessarInput.generation,
),
}
: {}),
},
}),
);
Expand All @@ -581,6 +596,17 @@ export default class RenderRoute extends Route<Model> {
let lastModified = new Date(response.headers.get('last-modified')!);
let doc: LooseSingleCardDocument | CardErrorsJSONAPI =
await response.json();
if (tessarInput && 'data' in doc && doc.data.meta.tessar) {
// This is the owner being recomputed. Its previous membership is an
// output, never an input seed for the new generation's query.
for (let field of doc.data.meta.tessar.queryFields) {
for (let key of Object.keys(doc.data.relationships ?? {})) {
if (key === field || key.startsWith(`${field}.`))
delete doc.data.relationships![key];
}
}
delete doc.data.meta.tessar;
}
let canonicalId = id.replace(/\.json$/, '');

let state = new TrackedMap<string, unknown>();
Expand Down Expand Up @@ -623,29 +649,58 @@ export default class RenderRoute extends Route<Model> {
throw new Error(JSON.stringify(doc.errors[0], null, 2));
}
(globalThis as any).__boxelSetRenderStage?.('buildModel:deriving-type');
let sourceDoc = doc;
let { derivedCardType, hydratedInstance } = await this.#authGuard.race(
async () => {
let renderDoc: LooseSingleCardDocument = sourceDoc;
let tessarUseSnapshot: true | undefined;
let derivedCardType = await deriveCardTypeFromDoc(
doc,
renderDoc,
id,
this.loaderService.loader,
);

if (parsedOptions.tessarUseSnapshot && !tessarInput) {
let Klass = await loadCardDef(renderDoc.data.meta.adoptsFrom, {
loader: this.loaderService.loader,
relativeTo: this.network.virtualNetwork.toURL(canonicalId),
});
if ((Klass as typeof CardDef).tessarMaterialized) {
let indexedResponse = await this.network.authedFetch(
canonicalId,
{
headers: { Accept: SupportedMimeType.CardJson },
},
);
let indexed = await indexedResponse.json();
if (
!indexedResponse.ok ||
indexed.data?.meta?.tessar?.state !== 'ready'
) {
throw new Error(
'Tessar HTML requires a ready published materialization',
);
}
renderDoc = indexed as LooseSingleCardDocument;
tessarUseSnapshot = true;
}
}

await this.realm.ensureRealmMeta(realmURL);
let screenshotsMeta = await this.declarationScreenshotsMeta(
doc,
renderDoc,
canonicalId,
realmURL,
);

let enhancedDoc: LooseSingleCardDocument = {
...doc,
...renderDoc,
data: {
...doc.data,
...renderDoc.data,
id: canonicalId,
type: 'card',
meta: {
...doc.data.meta,
...renderDoc.data.meta,
lastModified: lastModified.getTime(),
realmURL: realmURL as RealmIdentifier,
realmInfo: { ...this.realm.info(id) },
Expand All @@ -659,6 +714,7 @@ export default class RenderRoute extends Route<Model> {
relativeTo: rri(id),
realm: realmURL,
doNotPersist: true,
tessarUseSnapshot,
});
(globalThis as any).__boxelSetRenderStage?.(
'buildModel:store-settle',
Expand Down
Loading