mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-22 22:32:17 +10:00
193 lines
6.1 KiB
TypeScript
193 lines
6.1 KiB
TypeScript
import type { StylesheetPreflightRunner } from "@reactive-resume/pdf/server";
|
|
import type { CompileStylesheetResult } from "@reactive-resume/resume/stylesheet";
|
|
import type { ResumeData } from "@reactive-resume/schema/resume/data";
|
|
import type { SemanticStylesheet, StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
|
|
import type { StylesheetSnapshot } from "./stylesheet-service";
|
|
import { ORPCError } from "@orpc/client";
|
|
import { compileStylesheet } from "@reactive-resume/resume/stylesheet";
|
|
import { EMPTY_SEMANTIC_CSS_SOURCE } from "@reactive-resume/schema/resume/stylesheet";
|
|
import { recordSemanticCssEvent } from "./stylesheet-observability";
|
|
|
|
type PrepareImportedResumeDataInput = {
|
|
data: ResumeData;
|
|
resumeId: string;
|
|
revision: number;
|
|
runner?: StylesheetPreflightRunner | undefined;
|
|
};
|
|
|
|
type ValidateHistoricalStylesheetInput = {
|
|
data: ResumeData;
|
|
resumeId: string;
|
|
revision: number;
|
|
stylesheet: SemanticStylesheet;
|
|
runner?: StylesheetPreflightRunner | undefined;
|
|
};
|
|
|
|
const byteCount = (source: StylesheetSource): number => new TextEncoder().encode(source.text).byteLength;
|
|
|
|
const observeCompile = (
|
|
input: Pick<PrepareImportedResumeDataInput, "data" | "resumeId" | "revision">,
|
|
source: StylesheetSource,
|
|
): CompileStylesheetResult => {
|
|
const startedAt = performance.now();
|
|
try {
|
|
const result = compileStylesheet(source);
|
|
recordSemanticCssEvent({
|
|
name: "semantic_css.compile",
|
|
resumeId: input.resumeId,
|
|
durationMs: performance.now() - startedAt,
|
|
languageVersion: source.languageVersion,
|
|
sourceBytes: byteCount(source),
|
|
template: input.data.metadata.template,
|
|
diagnosticCodes: result.diagnostics.map(({ code }) => code),
|
|
pageCount: null,
|
|
revision: input.revision,
|
|
success: result.program !== null,
|
|
});
|
|
return result;
|
|
} catch (error) {
|
|
recordSemanticCssEvent({
|
|
name: "semantic_css.compile",
|
|
resumeId: input.resumeId,
|
|
durationMs: performance.now() - startedAt,
|
|
languageVersion: source.languageVersion,
|
|
sourceBytes: byteCount(source),
|
|
template: input.data.metadata.template,
|
|
diagnosticCodes: [],
|
|
pageCount: null,
|
|
revision: input.revision,
|
|
success: false,
|
|
});
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const unavailableError = () =>
|
|
new ORPCError("SEMANTIC_STYLESHEET_UNAVAILABLE", {
|
|
status: 503,
|
|
message: "Semantic stylesheet PDF preflight is unavailable.",
|
|
});
|
|
|
|
const validationError = (message: string) =>
|
|
new ORPCError("STYLESHEET_VALIDATION_FAILED", {
|
|
status: 400,
|
|
message,
|
|
});
|
|
|
|
const runPreflight = async (
|
|
input: Pick<PrepareImportedResumeDataInput, "data" | "resumeId" | "revision" | "runner">,
|
|
stylesheet: StylesheetSource,
|
|
) => {
|
|
const startedAt = performance.now();
|
|
try {
|
|
if (!input.runner) throw unavailableError();
|
|
const result = await input.runner.run({
|
|
data: input.data,
|
|
template: input.data.metadata.template,
|
|
stylesheet,
|
|
});
|
|
recordSemanticCssEvent({
|
|
name: "semantic_css.preflight",
|
|
resumeId: input.resumeId,
|
|
durationMs: performance.now() - startedAt,
|
|
languageVersion: stylesheet.languageVersion,
|
|
sourceBytes: byteCount(stylesheet),
|
|
template: input.data.metadata.template,
|
|
diagnosticCodes: result.diagnostics.map(({ code }) => code),
|
|
pageCount: result.ok ? result.pageCount : null,
|
|
revision: input.revision,
|
|
success: result.ok,
|
|
});
|
|
return result;
|
|
} catch (error) {
|
|
recordSemanticCssEvent({
|
|
name: "semantic_css.preflight",
|
|
resumeId: input.resumeId,
|
|
durationMs: performance.now() - startedAt,
|
|
languageVersion: stylesheet.languageVersion,
|
|
sourceBytes: byteCount(stylesheet),
|
|
template: input.data.metadata.template,
|
|
diagnosticCodes: [],
|
|
pageCount: null,
|
|
revision: input.revision,
|
|
success: false,
|
|
});
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const validApplied = async (input: PrepareImportedResumeDataInput, stylesheet: StylesheetSource): Promise<boolean> => {
|
|
const compiled = observeCompile(input, stylesheet);
|
|
return compiled.program !== null && (await runPreflight(input, stylesheet)).ok;
|
|
};
|
|
|
|
export async function prepareImportedResumeData(input: PrepareImportedResumeDataInput): Promise<ResumeData> {
|
|
const stylesheet = input.data.metadata.stylesheet;
|
|
if (stylesheet?.mode !== "semantic") return input.data;
|
|
|
|
let applied = stylesheet.source;
|
|
if (!(await validApplied(input, stylesheet.source))) {
|
|
applied = stylesheet.applied;
|
|
if (!(await validApplied(input, stylesheet.applied))) {
|
|
applied = { languageVersion: 1, text: EMPTY_SEMANTIC_CSS_SOURCE };
|
|
if (!(await validApplied(input, applied))) {
|
|
throw validationError("The empty stylesheet failed PDF preflight.");
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
...input.data,
|
|
metadata: {
|
|
...input.data.metadata,
|
|
stylesheet: { ...stylesheet, applied },
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function validateHistoricalStylesheet(
|
|
input: ValidateHistoricalStylesheetInput,
|
|
): Promise<SemanticStylesheet> {
|
|
const compiled = observeCompile(input, input.stylesheet.applied);
|
|
if (!compiled.program) throw validationError("The historical applied stylesheet is invalid.");
|
|
if (!(await runPreflight(input, input.stylesheet.applied)).ok) {
|
|
throw validationError("The historical applied stylesheet failed PDF preflight.");
|
|
}
|
|
return input.stylesheet;
|
|
}
|
|
|
|
export async function convertLegacyStylesheet(snapshot: StylesheetSnapshot): Promise<StylesheetSource> {
|
|
const startedAt = performance.now();
|
|
try {
|
|
const { convertLegacyStyleRules } = await import("@reactive-resume/pdf/semantic");
|
|
const source = convertLegacyStyleRules(snapshot.data).source;
|
|
recordSemanticCssEvent({
|
|
name: "semantic_css.convert_legacy",
|
|
resumeId: snapshot.id,
|
|
durationMs: performance.now() - startedAt,
|
|
languageVersion: source.languageVersion,
|
|
sourceBytes: byteCount(source),
|
|
template: snapshot.data.metadata.template,
|
|
diagnosticCodes: [],
|
|
pageCount: null,
|
|
revision: snapshot.stylesheetRevision,
|
|
success: true,
|
|
});
|
|
return source;
|
|
} catch (error) {
|
|
recordSemanticCssEvent({
|
|
name: "semantic_css.convert_legacy",
|
|
resumeId: snapshot.id,
|
|
durationMs: performance.now() - startedAt,
|
|
languageVersion: 1,
|
|
sourceBytes: 0,
|
|
template: snapshot.data.metadata.template,
|
|
diagnosticCodes: [],
|
|
pageCount: null,
|
|
revision: snapshot.stylesheetRevision,
|
|
success: false,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|