125 lines
3.0 KiB
TypeScript
125 lines
3.0 KiB
TypeScript
import {
|
|
MarkdownDocumentParseError,
|
|
renderMarkdown
|
|
} from "@md-to-pdf/renderer";
|
|
import {
|
|
createThemeRegistry,
|
|
type ThemeRegistryOptions
|
|
} from "./theme-registry.js";
|
|
import {
|
|
createImageResourceResolver,
|
|
type ImageResolutionContext,
|
|
type ImageResourceResolverOptions
|
|
} from "./image-resources.js";
|
|
|
|
export const MAXIMUM_MARKDOWN_LENGTH = 1_500_000;
|
|
|
|
export interface MarkdownRenderRequest {
|
|
markdown?: unknown;
|
|
language?: unknown;
|
|
resources?: unknown;
|
|
}
|
|
|
|
export class ApplicationRequestError extends Error {
|
|
constructor(
|
|
readonly statusCode: 400 | 404 | 413,
|
|
readonly code: string,
|
|
message: string
|
|
) {
|
|
super(message);
|
|
this.name = "ApplicationRequestError";
|
|
}
|
|
}
|
|
|
|
export interface ApplicationServiceOptions
|
|
extends ThemeRegistryOptions,
|
|
ImageResourceResolverOptions {}
|
|
|
|
export function createApplicationService(
|
|
options: ApplicationServiceOptions
|
|
) {
|
|
const themes = createThemeRegistry(options);
|
|
const resolveImages = createImageResourceResolver(options);
|
|
|
|
async function render(
|
|
request: MarkdownRenderRequest,
|
|
context: ImageResolutionContext = {}
|
|
) {
|
|
const { markdown, language, resources } = request;
|
|
if (typeof markdown !== "string") {
|
|
throw new ApplicationRequestError(
|
|
400,
|
|
"INVALID_MARKDOWN",
|
|
"markdown 必须是字符串"
|
|
);
|
|
}
|
|
if (markdown.length > MAXIMUM_MARKDOWN_LENGTH) {
|
|
throw new ApplicationRequestError(
|
|
413,
|
|
"MARKDOWN_TOO_LARGE",
|
|
"Markdown 内容不能超过 1.5 MB"
|
|
);
|
|
}
|
|
if (language !== undefined && typeof language !== "string") {
|
|
throw new ApplicationRequestError(
|
|
400,
|
|
"INVALID_LANGUAGE",
|
|
"language 必须是字符串"
|
|
);
|
|
}
|
|
|
|
try {
|
|
const images = await resolveImages(markdown, resources, context);
|
|
return renderMarkdown(markdown, {
|
|
...(language ? { language } : {}),
|
|
imageSourceMap: images.sources,
|
|
warnings: images.warnings
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof MarkdownDocumentParseError) {
|
|
throw new ApplicationRequestError(
|
|
400,
|
|
error.code,
|
|
error.message
|
|
);
|
|
}
|
|
if (error instanceof Error) {
|
|
const tooLarge = /超过|大小/u.test(error.message);
|
|
throw new ApplicationRequestError(
|
|
tooLarge ? 413 : 400,
|
|
tooLarge
|
|
? "IMAGE_RESOURCES_TOO_LARGE"
|
|
: "INVALID_IMAGE_RESOURCES",
|
|
error.message
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function listThemes() {
|
|
return {
|
|
themes: (await themes.list()).map(({ manifest, source }) => ({
|
|
id: manifest.id,
|
|
name: manifest.name,
|
|
version: manifest.version,
|
|
description: manifest.description,
|
|
bundled: manifest.bundled,
|
|
source
|
|
}))
|
|
};
|
|
}
|
|
|
|
return {
|
|
getThemeAsset: themes.getAsset,
|
|
getThemeCss: themes.getCss,
|
|
invalidateThemes: themes.invalidate,
|
|
listThemes,
|
|
render
|
|
};
|
|
}
|
|
|
|
export type ApplicationService = ReturnType<
|
|
typeof createApplicationService
|
|
>;
|